diff --git a/components/DatePickerGroup.tsx b/components/DatePickerGroup.tsx index ce072d8..dafeb63 100644 --- a/components/DatePickerGroup.tsx +++ b/components/DatePickerGroup.tsx @@ -5,6 +5,7 @@ import { useState, type FC, type ComponentPropsWithoutRef, + useMemo, } from "react"; import { FormGroup, Text, DatePicker } from "smarthr-ui"; import { @@ -12,6 +13,7 @@ import { type UseFormRegister, type UseFormSetValue, type Control, + useFormContext, } from "react-hook-form"; import { addYears, format } from "date-fns"; import styled from "styled-components"; @@ -23,29 +25,46 @@ type OnlyStringValueItemSchema = OmitByValue; export const DatePickerGroup: FC<{ label: string; required?: boolean; - error?: string; + // error?: string; hint?: string; readOnly?: boolean; - register: UseFormRegister; + // register: UseFormRegister; registerName: keyof OnlyStringValueItemSchema; - control: Control; - setValue: UseFormSetValue; + // control: Control; + // setValue: UseFormSetValue; }> = ({ label, required, - error, + // error, hint, readOnly, - register, + // register, registerName, - control, - setValue: setFormValue, + // control, + // setValue: setFormValue, }) => { const id = useId(); - const reactHookFormValue = useWatch({ - control, - name: registerName, - }); + + const { + watch, + register, + setValue: setFormValue, + formState: { errors }, + } = useFormContext(); + + const error = useMemo(() => { + const e = errors[registerName]?.message; + if (typeof e === "string") return e; + console.log(e); + return ""; + }, [errors, registerName]); + + // const reactHookFormValue = useWatch({ + // control, + // name: registerName, + // }); + + const reactHookFormValue = watch(registerName) as string; const formatter = (date: Date | string) => { if (typeof date === "string") { diff --git a/components/Form.tsx b/components/Form.tsx index 0ddc853..0a83301 100644 --- a/components/Form.tsx +++ b/components/Form.tsx @@ -9,27 +9,35 @@ import { itemSchemaForCreate, type ItemSchemaForCreate } from "../lib/item"; import { formatISO } from "date-fns"; import { TextareaGroup } from "./TextareaGroup"; -const expires_at = formatISO(getNextExpiresDate(new Date())); - -const defaultValues = { - notes: "", - location: "スクエア廊下", - expires_at: expires_at.includes("+") ? expires_at.split("+")[0] : expires_at, -}; - interface Props { defaultValues?: Partial; onSubmit: Parameters>[0]; } export const Form: FC = (props) => { + const expires_at = useMemo( + () => formatISO(getNextExpiresDate(new Date())), + [] + ); + + const defaultValues = useMemo( + () => ({ + notes: "", + location: "スクエア廊下", + expires_at: expires_at.includes("+") + ? expires_at.split("+")[0] + : expires_at, + }), + [expires_at] + ); + const defaultValuesForReset = useMemo( () => ({ id: itemId(), ...defaultValues, ...props.defaultValues, }), - [props.defaultValues] + [defaultValues, props.defaultValues] ); const { @@ -55,62 +63,29 @@ export const Form: FC = (props) => { })} > - + - + - + @@ -118,9 +93,6 @@ export const Form: FC = (props) => { label="責任者の所属" hint="「E1 イスを作ろう」のように学年学科や部活名、テーマ名など入力してください" required - error={errors.chief_department?.message} - // @ts-expect-error - register={register} registerName="chief_department" /> @@ -128,9 +100,6 @@ export const Form: FC = (props) => { label="責任者のメールアドレス" hint="「e17-abcd」のように@kure.kosen-ac.jpで終わるメールアドレスの最初のみ入力してください" required - error={errors.chief_email?.message} - // @ts-expect-error - register={register} registerName="chief_email" trailingVisual="@kure.kosen-ac.jp" /> @@ -139,22 +108,13 @@ export const Form: FC = (props) => { label="保管期限" hint="通常は学期末(夏休み前、春休み前)1週間前までです" required - // @ts-expect-error - register={register} registerName="expires_at" - // @ts-expect-error - control={control} - // @ts-expect-error - setValue={setValue} /> diff --git a/components/Form/NewForm.tsx b/components/Form/NewForm.tsx new file mode 100644 index 0000000..93f1c49 --- /dev/null +++ b/components/Form/NewForm.tsx @@ -0,0 +1,65 @@ +import { useCallback, useMemo, type FC } from "react"; +import { useForm, FormProvider } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + ItemSchema, + ItemSchemaForCreate, + itemSchemaForCreate, +} from "../../lib/item"; +import { Form } from "./presentation"; + +type EditableFormItem = Pick< + ItemSchema, + | "id" + | "name" + | "notes" + | "location" + | "chief_id" + | "chief_name" + | "chief_department" + | "chief_email" + | "expires_at" + | "confirmed_ta_name" +>; + +export const NewForm = () => { + // const defaultValuesForReset = useMemo( + // () => ({ + // id: itemId(), + // ...defaultValues, + // ...props.defaultValues, + // }), + // [props.defaultValues] + // ); + + const { + register, + control, + setValue, + reset, + handleSubmit: submit, + formState: { errors }, + } = useForm({ + // defaultValues: defaultValuesForReset, + defaultValues: {}, + resolver: zodResolver(itemSchemaForCreate, {}, { mode: "sync" }), + }); + + const handleReset = useCallback(() => { + // reset({ ...defaultValuesForReset }); + reset(); + }, [ + reset, + /*, defaultValuesForReset*/ + ]); + + // const Form = buildForm({}); + + return ( + <> + +
+
+ + ); +}; diff --git a/components/Form/presentation.tsx b/components/Form/presentation.tsx new file mode 100644 index 0000000..5761837 --- /dev/null +++ b/components/Form/presentation.tsx @@ -0,0 +1,245 @@ +import { type FC, type FormEventHandler, type MouseEventHandler } from "react"; +import { Stack, Cluster, Button } from "smarthr-ui"; +import { InputGroup } from "../InputGroup"; +import { DatePickerGroup } from "../DatePickerGroup"; +import { TextareaGroup } from "../TextareaGroup"; +import { type UseFormUnregister } from "react-hook-form"; + +interface Props { + onSubmit: FormEventHandler; + onReset: MouseEventHandler; + data: { + id: { + register: UseFormUnregister; + }; + }; +} + +export const Form: FC = (props) => { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +}; + +// import { +// createElement, +// ReactElement, +// ReactNode, +// useCallback, +// useMemo, +// type FC, +// } from "react"; +// import { Stack, Cluster, Button } from "smarthr-ui"; +// import { useForm, type UseFormHandleSubmit } from "react-hook-form"; +// import { zodResolver } from "@hookform/resolvers/zod"; +// import { InputGroup } from "../InputGroup"; +// import { DatePickerGroup } from "../DatePickerGroup"; +// import { TextareaGroup } from "../TextareaGroup"; +// import { ItemSchema } from "../../lib/item"; + +// type FormItem = ( +// | { type: "input" } +// | { type: "textarea" } +// | { type: "date" } +// ) & { +// required: boolean; +// label: string; +// hint: string; +// trailingVisual?: string; +// registerName: string; +// readOnly: boolean; +// }; + +// const Components: Record = { +// input: InputGroup, +// textarea: TextareaGroup, +// date: DatePickerGroup, +// }; + +// export type Props = { +// defaultValues?: Partial; +// forms: FormItem[]; +// errors: any; +// register: any; +// control: any; +// setValue: any; +// onSubmit: Parameters>[0]; +// }; + +// export const buildForm = ( +// props: Props +// ) => { +// // const defaultValuesForReset = useMemo( +// // () => ({ +// // id: itemId(), +// // ...defaultValues, +// // ...props.defaultValues, +// // }), +// // [props.defaultValues] +// // ); +// // const { +// // register, +// // control, +// // setValue, +// // reset, +// // handleSubmit: submit, +// // formState: { errors }, +// // } = useForm({ +// // defaultValues: defaultValuesForReset, +// // resolver: zodResolver(itemSchemaForCreate, {}, { mode: "sync" }), +// // }); +// // const handleReset = useCallback(() => { +// // reset({ ...defaultValuesForReset }); +// // }, [reset, defaultValuesForReset]); + +// const handleReset = () => {}; + +// return ( +//
{ +// // console.log(errors); +// // })} +// > +// +// {props.forms.map((formItem) => { +// return createElement(Components[formItem.type], { +// key: formItem.label, +// label: formItem.label, +// hint: formItem.hint, +// trailingVisual: formItem.trailingVisual, +// error: props.errors[formItem.registerName]?.message, +// required: formItem.required, +// readOnly: formItem.readOnly, +// registerName: formItem.registerName, +// register: props.register, +// control: props.control, +// setValue: props.setValue, +// }); +// })} + +// +// +// +// +// +//
+// ); +// }; diff --git a/components/InputGroup.tsx b/components/InputGroup.tsx index 22db3d4..8e0bd05 100644 --- a/components/InputGroup.tsx +++ b/components/InputGroup.tsx @@ -1,28 +1,39 @@ -import { useId, type FC } from "react"; +import { useId, useMemo, type FC } from "react"; import { FormGroup, Input, Text } from "smarthr-ui"; -import { type UseFormRegister } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; import { type ItemSchema } from "../lib/item"; -export const InputGroup: FC<{ +interface Props { label: string; required?: boolean; - error?: string; hint?: string; readOnly?: boolean; trailingVisual?: string; - register?: UseFormRegister; - registerName?: keyof ItemSchema; -}> = ({ + registerName: keyof ItemSchema; +} + +export const InputGroup: FC = ({ label, required, - error, hint, readOnly, - register, registerName, trailingVisual, }) => { + const { + register, + formState: { errors }, + } = useFormContext(); + + const error = useMemo(() => { + const e = errors[registerName]?.message; + if (typeof e === "string") return e; + console.log(e); + return ""; + }, [errors, registerName]); + const id = useId(); + const statusLabelProps = required ? [ { @@ -32,8 +43,6 @@ export const InputGroup: FC<{ ] : undefined; - const form = (register && registerName && register(registerName)) ?? {}; - return (
{hint} diff --git a/components/TextareaGroup.tsx b/components/TextareaGroup.tsx index a0d3e90..3a04d1a 100644 --- a/components/TextareaGroup.tsx +++ b/components/TextareaGroup.tsx @@ -5,52 +5,65 @@ import { useId, type FC, type ComponentPropsWithoutRef, + useMemo, } from "react"; import { FormGroup, Textarea, Text } from "smarthr-ui"; import { useWatch, + useFormContext, type UseFormRegister, type UseFormSetValue, type Control, } from "react-hook-form"; -import { type ItemSchema } from "../lib/item"; +import { ItemSchemaForCreate, type ItemSchema } from "../lib/item"; import styled from "styled-components"; export const TextareaGroup: FC<{ label: string; required?: boolean; - error?: string; + // error?: string; hint?: string; readOnly?: boolean; - register: UseFormRegister; + // register: UseFormRegister; registerName: keyof ItemSchema; - control: Control; - setValue: UseFormSetValue; + // control: Control; + // setValue: UseFormSetValue; }> = ({ label, required, - error, + // error, hint, readOnly, - register, + // register, registerName, - control, - setValue: setFormValue, + // control, + // setValue: setFormValue, }) => { const id = useId(); - const reactHookFormValue = useWatch({ - control, - name: registerName, - }); + const { + watch, + register, + setValue, + formState: { errors }, + } = useFormContext(); + + const error = useMemo(() => { + const e = errors[registerName]?.message; + if (typeof e === "string") return e; + console.log(e); + return ""; + }, [errors, registerName]); + + const reactHookFormValue = watch(registerName) as string; const handleChange: NonNullable< ComponentPropsWithoutRef["onChange"] > = useCallback( (e) => { - setFormValue(registerName, e.target.value); + setValue(registerName, e.target.value); }, - [registerName, setFormValue] + [registerName, setValue] ); const statusLabelProps = required @@ -80,7 +93,6 @@ export const TextareaGroup: FC<{ readOnly={readOnly} disabled={readOnly} error={!!error} - // @ts-expect-error value={reactHookFormValue} onChange={handleChange} {...form} diff --git a/lib/gas/index.ts b/lib/gas/index.ts index 960a3fe..8f2a8b8 100644 --- a/lib/gas/index.ts +++ b/lib/gas/index.ts @@ -40,6 +40,19 @@ export const getRequestSchema = z.object({ }), }); +export const updateRequestSchema = z.object({ + type: z.literal("update"), + payload: itemSchema.pick({ + id: true, + name: true, + notes: true, + location: true, + status: true, + confirmed_ta_name: true, + expires_at: true, + }), +}); + export const getRequestPayloadSchemaOmittedToken = getRequestSchema.shape.payload.omit({ token: true }); diff --git a/pages/new.tsx b/pages/new.tsx index 34d2527..caa10df 100644 --- a/pages/new.tsx +++ b/pages/new.tsx @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; import Head from "next/head"; import { useRouter } from "next/router"; import { Heading } from "smarthr-ui"; @@ -6,8 +6,12 @@ import { toast } from "react-toastify"; import { type NextPageWithLayout } from "./_app"; import { getCenterLayout } from "../layouts/Center"; import { Form } from "../components/Form"; -import { type ItemSchemaForCreate } from "../lib/item"; +import { itemSchemaForCreate, type ItemSchemaForCreate } from "../lib/item"; import { client } from "../lib/next/apiClient"; +import { FormProvider, useForm } from "react-hook-form"; +import { getNextExpiresDate, itemId } from "../lib/item/utils"; +import { formatISO } from "date-fns"; +import { zodResolver } from "@hookform/resolvers/zod"; const sleep = async (ms: number) => { return new Promise((resolve) => { @@ -38,6 +42,40 @@ const New: NextPageWithLayout = () => { [router] ); + const expires_at = useMemo( + () => formatISO(getNextExpiresDate(new Date())), + [] + ); + + const defaultValues = useMemo( + () => ({ + notes: "", + location: "スクエア廊下", + expires_at: expires_at.includes("+") + ? expires_at.split("+")[0] + : expires_at, + }), + [expires_at] + ); + + const defaultValuesForReset = useMemo( + () => ({ + id: itemId(), + ...defaultValues, + // ...props.defaultValues, + }), + [defaultValues] + ); + + const methods = useForm({ + defaultValues: defaultValuesForReset, + resolver: zodResolver(itemSchemaForCreate, {}, { mode: "sync" }), + }); + + const handleReset = useCallback(() => { + methods.reset({ ...defaultValuesForReset }); + }, [methods, defaultValuesForReset]); + return ( <> @@ -47,7 +85,13 @@ const New: NextPageWithLayout = () => { 新規登録 -
+ + { + console.log(errors); + })} + /> + ); };