update resource table with details
This commit is contained in:
parent
0a42d34bb4
commit
1be8f722b1
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -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(),
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user