update resource table with details

This commit is contained in:
Brandon Egger 2023-06-06 01:31:53 -05:00
parent 0a42d34bb4
commit 1be8f722b1
4 changed files with 73 additions and 62 deletions

View File

@ -3,16 +3,19 @@ import Image from "next/image";
import { PencilSquareIcon } from "@heroicons/react/24/solid"; import { PencilSquareIcon } from "@heroicons/react/24/solid";
import { ChevronDownIcon } from "@heroicons/react/24/outline"; import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { import {
MultiSelector,
MultiSelectorMany, MultiSelectorMany,
MultiSelectorOption, MultiSelectorOption,
SelectedUniqueContext, SelectedManyContext,
SimpleSelectorManyOption, SimpleSelectorManyOption,
} from "../../forms/selectors"; } from "../../forms/selectors";
import { InfoInputLine } from "~/components/forms/textInput"; import { InfoInputLine } from "~/components/forms/textInput";
import { PriceIcon } from "~/prices/Icons"; import { PriceIcon } from "~/prices/Icons";
import { useState } from "react"; import { useState } from "react";
import { type UseFormRegister } from "react-hook-form"; import {
type UseFormReturn,
FormProvider,
useFormContext,
} from "react-hook-form";
import { type RouterInputs } from "~/utils/api"; import { type RouterInputs } from "~/utils/api";
export type ResourceUpdateInput = RouterInputs["auditoryResource"]["update"]; export type ResourceUpdateInput = RouterInputs["auditoryResource"]["update"];
@ -79,11 +82,11 @@ const PaymentTypeOption = ({
}) => { }) => {
return ( return (
<MultiSelectorOption value={type}> <MultiSelectorOption value={type}>
<SelectedUniqueContext.Consumer> <SelectedManyContext.Consumer>
{(selected) => ( {(selected) => (
<div <div
className={ className={
(selected === type ? "bg-stone-800" : "bg-white") + (selected.includes(type) ? "bg-stone-800" : "bg-white") +
" flex flex-row space-x-2 whitespace-nowrap rounded-xl border border-neutral-400 py-[1px] pl-[1px] pr-2" " flex flex-row space-x-2 whitespace-nowrap rounded-xl border border-neutral-400 py-[1px] pl-[1px] pr-2"
} }
> >
@ -92,7 +95,7 @@ const PaymentTypeOption = ({
</span> </span>
<span <span
className={ className={
(selected === type ? "text-white" : "text black") + (selected.includes(type) ? "text-white" : "text black") +
" my-auto inline-block whitespace-nowrap text-sm" " my-auto inline-block whitespace-nowrap text-sm"
} }
> >
@ -100,7 +103,7 @@ const PaymentTypeOption = ({
</span> </span>
</div> </div>
)} )}
</SelectedUniqueContext.Consumer> </SelectedManyContext.Consumer>
</MultiSelectorOption> </MultiSelectorOption>
); );
}; };
@ -109,12 +112,12 @@ const PaymentTypeOption = ({
* Resource summary inputs - ie description, manufacturer, etc. * Resource summary inputs - ie description, manufacturer, etc.
*/ */
function ResourceSummarySubForm({ function ResourceSummarySubForm({
register,
resource, resource,
}: { }: {
register: UseFormRegister<ResourceUpdateInput>;
resource?: ResourceUpdateInput; resource?: ResourceUpdateInput;
}) { }) {
const { register } = useFormContext<ResourceUpdateInput>();
return ( return (
<div className="space-y-4 px-4"> <div className="space-y-4 px-4">
<div className="flex flex-row space-x-4 sm:mt-4"> <div className="flex flex-row space-x-4 sm:mt-4">
@ -144,10 +147,11 @@ function ResourceSummarySubForm({
</span> </span>
</div> </div>
</div> </div>
<MultiSelector <MultiSelectorMany
details={register("payment_options", { required: true })}
label="Price Category" label="Price Category"
defaultValue={ defaultValues={
resource?.payment_options?.toString() ?? PaymentType.FREE.toString() resource?.payment_options ?? [PaymentType.FREE.toString()]
} }
> >
<PaymentTypeOption type={PaymentType.FREE} label="Free" /> <PaymentTypeOption type={PaymentType.FREE} label="Free" />
@ -159,9 +163,10 @@ function ResourceSummarySubForm({
type={PaymentType.SUBSCRIPTION_WEEKLY} type={PaymentType.SUBSCRIPTION_WEEKLY}
label="Weekly Subscription" label="Weekly Subscription"
/> />
</MultiSelector> </MultiSelectorMany>
<MultiSelectorMany <MultiSelectorMany
details={register("skill_levels", { required: true })}
label="Skill Level" label="Skill Level"
defaultValues={resource?.skill_levels ?? []} defaultValues={resource?.skill_levels ?? []}
> >
@ -177,6 +182,7 @@ function ResourceSummarySubForm({
</MultiSelectorMany> </MultiSelectorMany>
<MultiSelectorMany <MultiSelectorMany
details={register("skills", { required: true })}
label="Skills Covered" label="Skills Covered"
defaultValues={resource?.skills ?? []} defaultValues={resource?.skills ?? []}
> >
@ -194,14 +200,9 @@ function ResourceSummarySubForm({
); );
} }
const ResourceDescriptionSubForm = ({ const ResourceDescriptionSubForm = () => {
register,
resource,
}: {
register: UseFormRegister<ResourceUpdateInput>;
resource?: ResourceUpdateInput;
}) => {
const [dropdownOpen, toggleDropdown] = useState(false); const [dropdownOpen, toggleDropdown] = useState(false);
const { register } = useFormContext();
return ( return (
<div className="mx-4"> <div className="mx-4">
@ -241,29 +242,31 @@ const ResourceDescriptionSubForm = ({
}; };
const ResourceForm = ({ const ResourceForm = ({
methods,
resource, resource,
register,
error, error,
}: { }: {
resource?: ResourceUpdateInput; resource?: ResourceUpdateInput;
register: UseFormRegister<ResourceUpdateInput>; methods: UseFormReturn<ResourceUpdateInput>;
error?: string; error?: string;
}) => { }) => {
return ( return (
<form className="mx-auto flex max-w-2xl flex-col flex-col-reverse py-1 sm:flex-row sm:divide-x sm:py-4"> <FormProvider {...methods}>
<div className="my-5 mr-4 flex flex-col text-lg font-bold"> <form className="mx-auto flex max-w-2xl flex-col flex-col-reverse py-1 sm:flex-row sm:divide-x sm:py-4">
<ResourceLinkSubForm /> {/** //resource={resource} /> */} <div className="my-5 mr-4 flex flex-col text-lg font-bold">
</div> <ResourceLinkSubForm /> {/** //resource={resource} /> */}
<div>
<h1 className="mx-4 mb-2 border-b border-neutral-400 text-xl font-bold sm:hidden">
General
</h1>
<div className="justify-left mx-auto flex max-w-lg flex-col space-y-4 pb-5">
<ResourceSummarySubForm register={register} resource={resource} />
<ResourceDescriptionSubForm register={register} resource={resource} />
</div> </div>
</div> <div>
</form> <h1 className="mx-4 mb-2 border-b border-neutral-400 text-xl font-bold sm:hidden">
General
</h1>
<div className="justify-left mx-auto flex max-w-lg flex-col space-y-4 pb-5">
<ResourceSummarySubForm resource={resource} />
<ResourceDescriptionSubForm />
</div>
</div>
</form>
</FormProvider>
); );
}; };

View File

@ -1,4 +1,5 @@
import { createContext, useContext, useState } from "react"; import { createContext, useContext, useState } from "react";
import { useFormContext, type UseFormRegisterReturn } from "react-hook-form";
// generics // generics
interface ToStringable { interface ToStringable {
@ -22,11 +23,14 @@ function MultiSelectorMany<T extends ToStringable>({
label, label,
defaultValues, defaultValues,
children, children,
details,
}: { }: {
label: string; label: string;
defaultValues: T[]; defaultValues: T[];
children: undefined | JSX.Element | JSX.Element[]; children: undefined | JSX.Element | JSX.Element[];
details: UseFormRegisterReturn<string>;
}) { }) {
const { setValue } = useFormContext();
const [selected, setSelected] = useState<string[]>( const [selected, setSelected] = useState<string[]>(
defaultValues.map((value) => { defaultValues.map((value) => {
return value.toString(); return value.toString();
@ -35,14 +39,16 @@ function MultiSelectorMany<T extends ToStringable>({
const updateCallback = (value: string) => { const updateCallback = (value: string) => {
if (selected.includes(value)) { if (selected.includes(value)) {
setSelected( const filteredSelected = selected.filter((selectedValue) => {
selected.filter((selectedValue) => { return selectedValue !== value;
return selectedValue !== value; });
})
); setValue(details.name, filteredSelected);
setSelected(filteredSelected);
return; return;
} }
setValue(details.name, [value, ...selected]);
setSelected([value, ...selected]); setSelected([value, ...selected]);
}; };
@ -54,12 +60,7 @@ function MultiSelectorMany<T extends ToStringable>({
<span className="block text-sm italic text-neutral-400"> <span className="block text-sm italic text-neutral-400">
Select all that apply Select all that apply
</span> </span>
<input <input {...details} readOnly type="text" className="hidden" />
readOnly
type="text"
className="hidden"
value={selected ?? ""}
/>
<div className="mt-2 space-x-2 space-y-2 overflow-x-auto"> <div className="mt-2 space-x-2 space-y-2 overflow-x-auto">
{children} {children}
</div> </div>
@ -73,16 +74,25 @@ function MultiSelector<T extends ToStringable>({
label, label,
defaultValue, defaultValue,
children, children,
details,
}: { }: {
label: string; label: string;
defaultValue: T; defaultValue: T;
children: undefined | JSX.Element | JSX.Element[]; children: undefined | JSX.Element | JSX.Element[];
details: UseFormRegisterReturn<string>;
}) { }) {
const [selected, setSelected] = useState<string>(defaultValue.toString()); const [selected, setSelected] = useState<string>(defaultValue.toString());
const { setValue } = useFormContext();
return ( return (
<SelectorContext.Provider <SelectorContext.Provider
value={{ type: "one", updateCallback: setSelected }} value={{
type: "one",
updateCallback: (value) => {
setSelected(value);
setValue(details.name, value);
},
}}
> >
<SelectedUniqueContext.Provider value={selected}> <SelectedUniqueContext.Provider value={selected}>
<div className="flex flex-col"> <div className="flex flex-col">
@ -90,12 +100,7 @@ function MultiSelector<T extends ToStringable>({
<span className="block text-sm italic text-neutral-400"> <span className="block text-sm italic text-neutral-400">
Select one from below Select one from below
</span> </span>
<input <input {...details} readOnly type="text" className="hidden" />
readOnly
type="text"
className="hidden"
value={selected ?? ""}
/>
<div className="space-x-2 space-y-2 overflow-x-auto">{children}</div> <div className="space-x-2 space-y-2 overflow-x-auto">{children}</div>
</div> </div>
</SelectedUniqueContext.Provider> </SelectedUniqueContext.Provider>

View File

@ -47,7 +47,7 @@ const EditResourcePage = (
) => { ) => {
const { resource } = props; const { resource } = props;
const [serverError, setServerError] = useState<string | undefined>(undefined); const [serverError, setServerError] = useState<string | undefined>(undefined);
const { register, getValues } = useForm<ResourceUpdateInput>({ const formMethods = useForm<ResourceUpdateInput>({
defaultValues: resource as ResourceUpdateInput, defaultValues: resource as ResourceUpdateInput,
}); });
@ -90,7 +90,7 @@ const EditResourcePage = (
} }
label="Save" label="Save"
onClick={() => { onClick={() => {
onSubmit(getValues()); onSubmit(formMethods.getValues());
}} }}
/>, />,
<AdminActionLink <AdminActionLink
@ -103,7 +103,7 @@ const EditResourcePage = (
> >
<main className="mb-12"> <main className="mb-12">
<ResourceForm <ResourceForm
register={register} methods={formMethods}
error={serverError} error={serverError}
resource={resource as ResourceUpdateInput} resource={resource as ResourceUpdateInput}
/> />

View File

@ -34,14 +34,14 @@ export const auditoryResourceRouter = createTRPCRouter({
.input( .input(
z.object({ z.object({
id: z.string(), id: z.string(),
icon: z.string().optional(), icon: z.string().min(1).optional(),
name: z.string().optional(), name: z.string().min(1).optional(),
description: z.string().optional(), description: z.string().min(1).optional(),
manufacturer: z manufacturer: z
.object({ .object({
name: z.string(), name: z.string().min(1),
required: z.boolean(), required: z.boolean(),
notice: z.string().optional().nullable(), notice: z.string().min(1).optional().nullable(),
}) })
.optional(), .optional(),
ages: z ages: z
@ -52,7 +52,10 @@ export const auditoryResourceRouter = createTRPCRouter({
payment_options: z.array(z.nativeEnum(PaymentType)).optional(), payment_options: z.array(z.nativeEnum(PaymentType)).optional(),
platform_links: z platform_links: z
.array( .array(
z.object({ platform: z.nativeEnum(Platform), link: z.string() }) z.object({
platform: z.nativeEnum(Platform),
link: z.string().min(1),
})
) )
.optional(), .optional(),
}) })