From a11f8bb4803626f9b9a91207d5fb5a7057ae1343 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:58:57 +0900 Subject: [PATCH 01/18] =?UTF-8?q?refactor:=20Product=E3=81=AE=E4=BD=9C?= =?UTF-8?q?=E6=88=90=E3=83=BB=E6=9B=B4=E6=96=B0=E3=81=AB=E4=BD=BF=E3=81=86?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0=E3=81=AE=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=82=92=E4=BD=9C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/molecules/FieldInfo.tsx | 20 ++++ .../organisms/register/ProductForm.tsx | 106 ++++++++++++++++++ bun.lockb | Bin 373363 -> 378260 bytes package.json | 2 + 4 files changed, 128 insertions(+) create mode 100644 app/components/molecules/FieldInfo.tsx create mode 100644 app/components/organisms/register/ProductForm.tsx diff --git a/app/components/molecules/FieldInfo.tsx b/app/components/molecules/FieldInfo.tsx new file mode 100644 index 0000000..0ba00be --- /dev/null +++ b/app/components/molecules/FieldInfo.tsx @@ -0,0 +1,20 @@ +import { FormErrorMessage, FormHelperText } from "@chakra-ui/react" +import { FieldApi } from "@tanstack/react-form" +import { FC, ReactNode } from "react" + +export type FieldInfoProps = { + field: FieldApi + helperText?: ReactNode +} + +export const FieldInfo: FC = ({ field, helperText }) => { + return ( + <> + {field.state.meta.isTouched && field.state.meta.errors.length ? ( + {field.state.meta.errors.join(",")} + ) : ( + {helperText} + )} + + ) +} diff --git a/app/components/organisms/register/ProductForm.tsx b/app/components/organisms/register/ProductForm.tsx new file mode 100644 index 0000000..2a000b0 --- /dev/null +++ b/app/components/organisms/register/ProductForm.tsx @@ -0,0 +1,106 @@ +import { Form } from "@remix-run/react" +import { FormApi, ReactFormApi } from "@tanstack/react-form" +import type { FC } from "react" +import { TypeProduct } from "~/type/typeproduct" +import { Button, FormControl, FormLabel, Input } from "@chakra-ui/react" +import { FieldInfo } from "~/components/molecules/FieldInfo" + +export type ProductFormValues = Omit + +export type ProductFormProps = { + form: FormApi & ReactFormApi + submittingText?: string + submitText?: string +} + +export const ProductForm: FC = ({ + form, + submitText, + submittingText, +}) => { + return ( +
form.handleSubmit()}> + + {(field) => ( + 0}> + 商品名 + field.handleChange(e.target.value)} + /> + + + )} + + + {(field) => ( + 0}> + 価格 + field.handleChange(e.target.valueAsNumber)} + /> + + + )} + + + {(field) => ( + 0}> + 在庫 + field.handleChange(e.target.valueAsNumber)} + /> + + + )} + + + {(field) => ( + 0}> + 商品画像 + field.handleChange(e.target.value)} + /> + + + )} + + [formState.canSubmit, formState.isSubmitting]} + > + {([canSubmit, isSubmitting]) => ( + + )} + +
+ ) +} diff --git a/bun.lockb b/bun.lockb index 693a3fc099c77cc01f48b85e39c07903a027dfb5..bf12c41dabb2c6eaa7316f2e2bec9d744d006a93 100755 GIT binary patch delta 75909 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
jxo(I!HXr4>wde3Wco6_0h|*DS99w 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 E0Yi6{djJ3c delta 73321 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 AJk-ZD>pB>JzJ`AUS$DUa)z3PZ2r` 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 diff --git a/package.json b/package.json index 1886895..7d532bd 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "@remix-run/node": "^2.12.0", "@remix-run/react": "^2.12.0", "@remix-run/serve": "^2.12.0", + "@tanstack/react-form": "^0.36.0", + "@tanstack/valibot-form-adapter": "^0.35.0", "framer-motion": "^11.11.7", "isbot": "^4.1.0", "react": "^18.2.0", From 675f92148121d55a9904a0d068b73029063aec29 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:00:29 +0900 Subject: [PATCH 02/18] =?UTF-8?q?fix:=20=E3=83=87=E3=82=B6=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=82=92=E6=97=A2=E5=AD=98=E3=81=AE=E3=82=82=E3=81=AE?= =?UTF-8?q?=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organisms/register/ProductForm.tsx | 169 +++++++++--------- 1 file changed, 87 insertions(+), 82 deletions(-) diff --git a/app/components/organisms/register/ProductForm.tsx b/app/components/organisms/register/ProductForm.tsx index 2a000b0..c88404a 100644 --- a/app/components/organisms/register/ProductForm.tsx +++ b/app/components/organisms/register/ProductForm.tsx @@ -2,7 +2,7 @@ import { Form } from "@remix-run/react" import { FormApi, ReactFormApi } from "@tanstack/react-form" import type { FC } from "react" import { TypeProduct } from "~/type/typeproduct" -import { Button, FormControl, FormLabel, Input } from "@chakra-ui/react" +import { Button, FormControl, FormLabel, Input, VStack } from "@chakra-ui/react" import { FieldInfo } from "~/components/molecules/FieldInfo" export type ProductFormValues = Omit @@ -20,87 +20,92 @@ export const ProductForm: FC = ({ }) => { return (
form.handleSubmit()}> - - {(field) => ( - 0}> - 商品名 - field.handleChange(e.target.value)} - /> - - - )} - - - {(field) => ( - 0}> - 価格 - field.handleChange(e.target.valueAsNumber)} - /> - - - )} - - - {(field) => ( - 0}> - 在庫 - field.handleChange(e.target.valueAsNumber)} - /> - - - )} - - - {(field) => ( - 0}> - 商品画像 - field.handleChange(e.target.value)} - /> - - - )} - - [formState.canSubmit, formState.isSubmitting]} - > - {([canSubmit, isSubmitting]) => ( - - )} - + + + {(field) => ( + 0}> + 商品名 + field.handleChange(e.target.value)} + /> + + + )} + + + {(field) => ( + 0}> + 価格 + field.handleChange(e.target.valueAsNumber)} + /> + + + )} + + + {(field) => ( + 0}> + 在庫 + field.handleChange(e.target.valueAsNumber)} + /> + + + )} + + + {(field) => ( + 0}> + 商品画像 + field.handleChange(e.target.value)} + /> + + + )} + + [ + formState.canSubmit, + formState.isSubmitting, + ]} + > + {([canSubmit, isSubmitting]) => ( + + )} + +
) } From 1583f609b52d2dd23732bcd96e522100c3954146 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 00:27:09 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20=E5=95=86=E5=93=81=E7=99=BB?= =?UTF-8?q?=E9=8C=B2=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0=E3=82=92=E4=BD=9C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/atoms/Form.tsx | 4 + .../organisms/register/ProductForm.tsx | 9 +- app/routes/debug.tsx | 64 ++++++++++++++ app/routes/register_new.tsx | 84 +++++++++++++++++++ 4 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 app/components/atoms/Form.tsx create mode 100644 app/routes/debug.tsx create mode 100644 app/routes/register_new.tsx diff --git a/app/components/atoms/Form.tsx b/app/components/atoms/Form.tsx new file mode 100644 index 0000000..d0792a1 --- /dev/null +++ b/app/components/atoms/Form.tsx @@ -0,0 +1,4 @@ +import { chakra } from "@chakra-ui/react" +import { Form as RemixForm } from "@remix-run/react" + +export const Form = chakra(RemixForm) diff --git a/app/components/organisms/register/ProductForm.tsx b/app/components/organisms/register/ProductForm.tsx index c88404a..78d3e8d 100644 --- a/app/components/organisms/register/ProductForm.tsx +++ b/app/components/organisms/register/ProductForm.tsx @@ -1,4 +1,4 @@ -import { Form } from "@remix-run/react" +import { Form } from "~/components/atoms/Form" import { FormApi, ReactFormApi } from "@tanstack/react-form" import type { FC } from "react" import { TypeProduct } from "~/type/typeproduct" @@ -9,8 +9,8 @@ export type ProductFormValues = Omit export type ProductFormProps = { form: FormApi & ReactFormApi - submittingText?: string - submitText?: string + submittingText: string + submitText: string } export const ProductForm: FC = ({ @@ -19,7 +19,7 @@ export const ProductForm: FC = ({ submittingText, }) => { return ( -
form.handleSubmit()}> + form.handleSubmit()} w="full"> {(field) => ( @@ -100,6 +100,7 @@ export const ProductForm: FC = ({ isLoading={isSubmitting} loadingText={submittingText} disabled={!canSubmit} + alignSelf="end" > {submitText} diff --git a/app/routes/debug.tsx b/app/routes/debug.tsx new file mode 100644 index 0000000..3a0d5aa --- /dev/null +++ b/app/routes/debug.tsx @@ -0,0 +1,64 @@ +import { ActionFunctionArgs } from "@remix-run/node" +import { useActionData } from "@remix-run/react" +import { mergeForm, useForm, useTransform } from "@tanstack/react-form" + +import { + ServerValidateError, + createServerValidate, + formOptions, + initialFormState, +} from "@tanstack/react-form/remix" +import { + ProductForm, + ProductFormValues, +} from "~/components/organisms/register/ProductForm" + +const formOpts = formOptions({ + defaultValues: { + product_name: "", + price: 0, + stock: 0, + image: "", + } satisfies ProductFormValues, +}) + +const serverValidate = createServerValidate({ + ...formOpts, + onServerValidate: ({ value }) => { + if (value.stock < 12) { + return "Server validation: You must be at least 12 to sign up" + } + }, +}) + +export default function Debug() { + const actionData = useActionData() + + const form = useForm({ + ...formOpts, + transform: useTransform( + (baseForm) => mergeForm(baseForm, actionData ?? {}), + [actionData], + ), + }) + const formErrors = form.useStore((formState) => formState.errors) + + return +} + +export async function action({ request }: ActionFunctionArgs) { + const formData = await request.formData() + try { + await serverValidate(formData) + } catch (e) { + if (e instanceof ServerValidateError) { + return e.formState + } + + // Some other error occurred while validating your form + throw e + } + + // Your form has successfully validated! + return null +} diff --git a/app/routes/register_new.tsx b/app/routes/register_new.tsx new file mode 100644 index 0000000..b3b9f15 --- /dev/null +++ b/app/routes/register_new.tsx @@ -0,0 +1,84 @@ +import { Heading, VStack } from "@chakra-ui/react" +import { ActionFunctionArgs } from "@remix-run/node" +import { json, useActionData, useLoaderData } from "@remix-run/react" +import { mergeForm, useForm, useTransform } from "@tanstack/react-form" +import { + createServerValidate, + ServerValidateError, +} from "@tanstack/react-form/remix" +import { FC } from "react" +import { + ProductForm, + ProductFormValues, +} from "~/components/organisms/register/ProductForm" +import { readProduct } from "~/crud/crud_products" + +export const loader = async () => { + const products = await readProduct() + return json({ products }) +} + +const Register = () => { + const { products } = useLoaderData() + + return ( + + + + ) +} +export default Register + +export const action = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData() + try { + await serverValidate(formData) + } catch (e) { + if (e instanceof ServerValidateError) { + return e.formState + } + + // Some other error occurred while validating your form + throw e + } + + // Your form has successfully validated! + return null +} + +const serverValidate = createServerValidate({ + onServerValidate: ({ value }) => { + if (value.stock < 12) { + return "Server validation: You must be at least 12 to sign up" + } + }, +}) + +const CreateProductForm: FC = () => { + const actionData = useActionData() + const form = useForm({ + defaultValues: { + product_name: "", + price: 0, + stock: 0, + image: "", + }, + transform: useTransform( + (baseForm) => mergeForm(baseForm, actionData ?? {}), + [actionData], + ), + }) + + return ( + + + 商品登録フォーム + + + + ) +} From f3faae3d3a3dac1c25b828540676be2dae727d9d Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 00:38:52 +0900 Subject: [PATCH 04/18] fix: remove default values --- app/routes/register_new.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/routes/register_new.tsx b/app/routes/register_new.tsx index b3b9f15..22ef261 100644 --- a/app/routes/register_new.tsx +++ b/app/routes/register_new.tsx @@ -57,12 +57,6 @@ const serverValidate = createServerValidate({ const CreateProductForm: FC = () => { const actionData = useActionData() const form = useForm({ - defaultValues: { - product_name: "", - price: 0, - stock: 0, - image: "", - }, transform: useTransform( (baseForm) => mergeForm(baseForm, actionData ?? {}), [actionData], From f54456e9c621d2e74254a7f71d7e6bd39043e126 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 02:10:28 +0900 Subject: [PATCH 05/18] =?UTF-8?q?fix:=20@tanstack/form=E3=81=8C=E4=B8=8A?= =?UTF-8?q?=E6=89=8B=E3=81=8F=E5=8B=95=E3=81=8B=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=81=9F=E3=82=81conform=E3=81=AB=E7=A7=BB=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/molecules/FieldInfo.tsx | 9 +- .../organisms/register/ProductForm.tsx | 185 +++++++++--------- app/routes/register_new.tsx | 43 +--- bun.lockb | Bin 378260 -> 374965 bytes package.json | 7 +- 5 files changed, 101 insertions(+), 143 deletions(-) diff --git a/app/components/molecules/FieldInfo.tsx b/app/components/molecules/FieldInfo.tsx index 0ba00be..d652a5d 100644 --- a/app/components/molecules/FieldInfo.tsx +++ b/app/components/molecules/FieldInfo.tsx @@ -1,17 +1,16 @@ import { FormErrorMessage, FormHelperText } from "@chakra-ui/react" -import { FieldApi } from "@tanstack/react-form" import { FC, ReactNode } from "react" export type FieldInfoProps = { - field: FieldApi + errors?: string[] | undefined helperText?: ReactNode } -export const FieldInfo: FC = ({ field, helperText }) => { +export const FieldInfo: FC = ({ errors, helperText }) => { return ( <> - {field.state.meta.isTouched && field.state.meta.errors.length ? ( - {field.state.meta.errors.join(",")} + {errors ? ( + {errors.join(",")} ) : ( {helperText} )} diff --git a/app/components/organisms/register/ProductForm.tsx b/app/components/organisms/register/ProductForm.tsx index 78d3e8d..07254b1 100644 --- a/app/components/organisms/register/ProductForm.tsx +++ b/app/components/organisms/register/ProductForm.tsx @@ -1,111 +1,108 @@ import { Form } from "~/components/atoms/Form" -import { FormApi, ReactFormApi } from "@tanstack/react-form" import type { FC } from "react" import { TypeProduct } from "~/type/typeproduct" import { Button, FormControl, FormLabel, Input, VStack } from "@chakra-ui/react" import { FieldInfo } from "~/components/molecules/FieldInfo" +import * as v from "valibot" +import { parseWithValibot } from "conform-to-valibot" +import { useForm } from "@conform-to/react" export type ProductFormValues = Omit +export const productSchema = v.object({ + product_name: v.pipe( + v.string("商品名は文字列で入力してください"), + v.minLength(1, "商品名は1文字以上で入力してください"), + v.maxLength(255, "商品名は255文字以下で入力してください"), + ), + price: v.pipe( + v.number("価格は数字で入力してください"), + v.integer("価格は整数で入力してください"), + v.minValue(0, "価格は0以上で入力してください"), + ), + stock: v.pipe( + v.number("在庫数は数字で入力してください"), + v.integer("在庫数は整数で入力してください"), + v.minValue(0, "在庫数は0以上で入力してください"), + ), + image: v.pipe( + v.string("商品画像は文字列で入力してください"), + v.url("商品画像は画像URLで入力してください"), + ), +}) + export type ProductFormProps = { - form: FormApi & ReactFormApi - submittingText: string submitText: string } -export const ProductForm: FC = ({ - form, - submitText, - submittingText, -}) => { +export const ProductForm: FC = ({ submitText }) => { + const [form, fields] = useForm({ + shouldValidate: "onBlur", + shouldRevalidate: "onInput", + onValidate: ({ formData }) => { + return parseWithValibot(formData, { schema: productSchema }) + }, + }) + return ( - form.handleSubmit()} w="full"> + - - {(field) => ( - 0}> - 商品名 - field.handleChange(e.target.value)} - /> - - - )} - - - {(field) => ( - 0}> - 価格 - field.handleChange(e.target.valueAsNumber)} - /> - - - )} - - - {(field) => ( - 0}> - 在庫 - field.handleChange(e.target.valueAsNumber)} - /> - - - )} - - - {(field) => ( - 0}> - 商品画像 - field.handleChange(e.target.value)} - /> - - - )} - - [ - formState.canSubmit, - formState.isSubmitting, - ]} - > - {([canSubmit, isSubmitting]) => ( - - )} - + + 商品名 + + + + + 価格 + + + + + 在庫 + + + + + 商品画像 + + + + ) diff --git a/app/routes/register_new.tsx b/app/routes/register_new.tsx index 22ef261..cc87459 100644 --- a/app/routes/register_new.tsx +++ b/app/routes/register_new.tsx @@ -1,16 +1,8 @@ import { Heading, VStack } from "@chakra-ui/react" import { ActionFunctionArgs } from "@remix-run/node" import { json, useActionData, useLoaderData } from "@remix-run/react" -import { mergeForm, useForm, useTransform } from "@tanstack/react-form" -import { - createServerValidate, - ServerValidateError, -} from "@tanstack/react-form/remix" import { FC } from "react" -import { - ProductForm, - ProductFormValues, -} from "~/components/organisms/register/ProductForm" +import { ProductForm } from "~/components/organisms/register/ProductForm" import { readProduct } from "~/crud/crud_products" export const loader = async () => { @@ -30,49 +22,18 @@ const Register = () => { export default Register export const action = async ({ request }: ActionFunctionArgs) => { - const formData = await request.formData() - try { - await serverValidate(formData) - } catch (e) { - if (e instanceof ServerValidateError) { - return e.formState - } - - // Some other error occurred while validating your form - throw e - } - - // Your form has successfully validated! return null } -const serverValidate = createServerValidate({ - onServerValidate: ({ value }) => { - if (value.stock < 12) { - return "Server validation: You must be at least 12 to sign up" - } - }, -}) - const CreateProductForm: FC = () => { const actionData = useActionData() - const form = useForm({ - transform: useTransform( - (baseForm) => mergeForm(baseForm, actionData ?? {}), - [actionData], - ), - }) return ( 商品登録フォーム - + ) } diff --git a/bun.lockb b/bun.lockb index bf12c41dabb2c6eaa7316f2e2bec9d744d006a93..212fb9aedae2b789396bd5085d998dc08d7f9a92 100755 GIT binary patch delta 75331 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

-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≫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 R3+Uufp=2eZ)OP*+{|D!=-b?@h delta 76625 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 gF$*Yog|! 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 G?*9X-LJmIw diff --git a/package.json b/package.json index 7d532bd..52794fb 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,6 @@ "@remix-run/node": "^2.12.0", "@remix-run/react": "^2.12.0", "@remix-run/serve": "^2.12.0", - "@tanstack/react-form": "^0.36.0", - "@tanstack/valibot-form-adapter": "^0.35.0", "framer-motion": "^11.11.7", "isbot": "^4.1.0", "react": "^18.2.0", @@ -31,12 +29,14 @@ "recharts": "^2.13.0" }, "devDependencies": { + "@conform-to/react": "^1.2.2", "@remix-run/dev": "^2.12.0", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", "autoprefixer": "^10.4.19", + "conform-to-valibot": "^1.11.0", "eslint": "^8.38.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.28.1", @@ -44,10 +44,11 @@ "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.38", - "prisma": "^5.20.0", "prettier": "^3.3.3", + "prisma": "^5.20.0", "tailwindcss": "^3.4.4", "typescript": "^5.1.6", + "valibot": "^1.0.0-beta.7", "vite": "^5.1.0", "vite-tsconfig-paths": "^4.2.1" }, From 8a2232f93fb2f75a54b18a71960a413867960bd0 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 02:43:05 +0900 Subject: [PATCH 06/18] =?UTF-8?q?fix:=20=E3=83=95=E3=82=A9=E3=83=BC?= =?UTF-8?q?=E3=83=A0=E6=8F=90=E5=87=BA=E6=88=90=E5=8A=9F=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0=E3=82=92=E3=83=AA=E3=82=BB?= =?UTF-8?q?=E3=83=83=E3=83=88=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organisms/register/ProductForm.tsx | 42 +++++++++---------- app/routes/register_new.tsx | 17 ++++++-- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/app/components/organisms/register/ProductForm.tsx b/app/components/organisms/register/ProductForm.tsx index 07254b1..e153d21 100644 --- a/app/components/organisms/register/ProductForm.tsx +++ b/app/components/organisms/register/ProductForm.tsx @@ -1,11 +1,12 @@ import { Form } from "~/components/atoms/Form" -import type { FC } from "react" +import { useEffect, type FC } from "react" import { TypeProduct } from "~/type/typeproduct" import { Button, FormControl, FormLabel, Input, VStack } from "@chakra-ui/react" import { FieldInfo } from "~/components/molecules/FieldInfo" import * as v from "valibot" -import { parseWithValibot } from "conform-to-valibot" -import { useForm } from "@conform-to/react" +import { getValibotConstraint, parseWithValibot } from "conform-to-valibot" +import { getInputProps, SubmissionResult, useForm } from "@conform-to/react" +import { useActionData, useNavigation } from "@remix-run/react" export type ProductFormValues = Omit @@ -33,10 +34,17 @@ export const productSchema = v.object({ export type ProductFormProps = { submitText: string + lastResult?: SubmissionResult | null | undefined } -export const ProductForm: FC = ({ submitText }) => { +export const ProductForm: FC = ({ + submitText, + lastResult, +}) => { + const navigation = useNavigation() const [form, fields] = useForm({ + constraint: getValibotConstraint(productSchema), + lastResult: navigation.state === "idle" ? lastResult : null, shouldValidate: "onBlur", shouldRevalidate: "onInput", onValidate: ({ formData }) => { @@ -56,51 +64,43 @@ export const ProductForm: FC = ({ submitText }) => { 商品名 価格 在庫 商品画像 - diff --git a/app/routes/register_new.tsx b/app/routes/register_new.tsx index cc87459..335f886 100644 --- a/app/routes/register_new.tsx +++ b/app/routes/register_new.tsx @@ -1,8 +1,12 @@ import { Heading, VStack } from "@chakra-ui/react" import { ActionFunctionArgs } from "@remix-run/node" import { json, useActionData, useLoaderData } from "@remix-run/react" -import { FC } from "react" -import { ProductForm } from "~/components/organisms/register/ProductForm" +import { parseWithValibot } from "conform-to-valibot" +import { FC, useEffect } from "react" +import { + ProductForm, + productSchema, +} from "~/components/organisms/register/ProductForm" import { readProduct } from "~/crud/crud_products" export const loader = async () => { @@ -22,7 +26,12 @@ const Register = () => { export default Register export const action = async ({ request }: ActionFunctionArgs) => { - return null + const formData = await request.formData() + const product = parseWithValibot(formData, { schema: productSchema }) + if (product.status !== "success") { + return json(product.reply()) + } + return json(product.reply({ resetForm: true })) } const CreateProductForm: FC = () => { @@ -33,7 +42,7 @@ const CreateProductForm: FC = () => { 商品登録フォーム - + ) } From b6a3720b3abea70ee2b551ecc0360867c12380de Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 03:31:06 +0900 Subject: [PATCH 07/18] =?UTF-8?q?feat:=20=E5=95=86=E5=93=81=E4=B8=80?= =?UTF-8?q?=E8=A6=A7=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organisms/register/ProductCard.tsx | 10 +- app/routes/register_new.tsx | 160 ++++++++++++++++-- 2 files changed, 153 insertions(+), 17 deletions(-) diff --git a/app/components/organisms/register/ProductCard.tsx b/app/components/organisms/register/ProductCard.tsx index dc51f56..5fbd623 100644 --- a/app/components/organisms/register/ProductCard.tsx +++ b/app/components/organisms/register/ProductCard.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Stack, Text } from "@chakra-ui/react" +import { Box, Button, Image, Stack, Text } from "@chakra-ui/react" import { FC, memo } from "react" import { TypeProduct } from "~/type/typeproduct" import PropTypes from "prop-types" @@ -18,7 +18,7 @@ export const ProductCard: FC = memo((props) => { maxW="300px" h="fit-content" bg={"white"} - borderRadius="10px" + borderRadius="lg" shadow="md" p={4} > @@ -27,7 +27,11 @@ export const ProductCard: FC = memo((props) => { {product.product_name} 価格:{product.price} 在庫:{product.stock} - {`${product.product_name}の商品画像`} + {`${product.product_name}の商品画像`} diff --git a/app/routes/register_new.tsx b/app/routes/register_new.tsx index 335f886..23295d1 100644 --- a/app/routes/register_new.tsx +++ b/app/routes/register_new.tsx @@ -1,30 +1,36 @@ -import { Heading, VStack } from "@chakra-ui/react" +import { + Button, + Heading, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + useDisclosure, + VStack, + Wrap, + WrapItem, +} from "@chakra-ui/react" import { ActionFunctionArgs } from "@remix-run/node" import { json, useActionData, useLoaderData } from "@remix-run/react" import { parseWithValibot } from "conform-to-valibot" -import { FC, useEffect } from "react" +import { FC, useState } from "react" +import { Form } from "~/components/atoms/Form" +import { ProductCard } from "~/components/organisms/register/ProductCard" import { ProductForm, productSchema, } from "~/components/organisms/register/ProductForm" import { readProduct } from "~/crud/crud_products" +import { TypeProduct } from "~/type/typeproduct" export const loader = async () => { const products = await readProduct() return json({ products }) } -const Register = () => { - const { products } = useLoaderData() - - return ( - - - - ) -} -export default Register - export const action = async ({ request }: ActionFunctionArgs) => { const formData = await request.formData() const product = parseWithValibot(formData, { schema: productSchema }) @@ -34,11 +40,31 @@ export const action = async ({ request }: ActionFunctionArgs) => { return json(product.reply({ resetForm: true })) } +const Register = () => { + return ( + + + + + ) +} +export default Register + const CreateProductForm: FC = () => { const actionData = useActionData() return ( - + 商品登録フォーム @@ -46,3 +72,109 @@ const CreateProductForm: FC = () => { ) } + +const Products: FC = () => { + const { products } = useLoaderData() + const [isOpen, setOpen] = useState(false) + + return ( + + {isOpen ? ( + + ) : ( + + )} + {isOpen && ( + + {products.map((product) => ( + + + + ))} + + )} + + ) +} + +const EditableProductCard: FC<{ + product: TypeProduct +}> = ({ product }) => { + const changeModal = useDisclosure() + const deleteModal = useDisclosure() + + return ( + <> + + + + + ) +} + +const ChangeProductModal: FC<{ + product: TypeProduct + isOpen: boolean + onClose: () => void +}> = ({ isOpen, onClose }) => { + const actionData = useActionData() + + return ( + + + + + 商品情報変更 + + + + + + ) +} + +const DeleteProductModal: FC<{ + product: TypeProduct + isOpen: boolean + onClose: () => void +}> = ({ product, isOpen, onClose }) => { + return ( + + + + + 商品削除 + +

{product.product_name}を本当に削除しますか?

+
+
+ + + +
+ + + + + ) +} From 0c270969d5a24dd70190032ba694a7c677114ca9 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:35:09 +0900 Subject: [PATCH 08/18] =?UTF-8?q?fix:=20=E5=89=8A=E9=99=A4=E3=83=BB?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organisms/register/ProductForm.tsx | 67 +++- app/routes/debug.tsx | 64 ---- app/routes/register_new.tsx | 318 +++++++++++++++--- 3 files changed, 334 insertions(+), 115 deletions(-) delete mode 100644 app/routes/debug.tsx diff --git a/app/components/organisms/register/ProductForm.tsx b/app/components/organisms/register/ProductForm.tsx index e153d21..6f67041 100644 --- a/app/components/organisms/register/ProductForm.tsx +++ b/app/components/organisms/register/ProductForm.tsx @@ -1,16 +1,22 @@ import { Form } from "~/components/atoms/Form" -import { useEffect, type FC } from "react" +import { type FC } from "react" import { TypeProduct } from "~/type/typeproduct" import { Button, FormControl, FormLabel, Input, VStack } from "@chakra-ui/react" import { FieldInfo } from "~/components/molecules/FieldInfo" import * as v from "valibot" import { getValibotConstraint, parseWithValibot } from "conform-to-valibot" import { getInputProps, SubmissionResult, useForm } from "@conform-to/react" -import { useActionData, useNavigation } from "@remix-run/react" +import { useNavigation } from "@remix-run/react" -export type ProductFormValues = Omit +export type ProductFormValues = ( + | Omit + | TypeProduct +) & { + _method: "create" | "update" +} -export const productSchema = v.object({ +export const createProductSchema = v.object({ + _method: v.literal("create"), product_name: v.pipe( v.string("商品名は文字列で入力してください"), v.minLength(1, "商品名は1文字以上で入力してください"), @@ -20,11 +26,19 @@ export const productSchema = v.object({ v.number("価格は数字で入力してください"), v.integer("価格は整数で入力してください"), v.minValue(0, "価格は0以上で入力してください"), + v.maxValue( + Number.MAX_SAFE_INTEGER, + `価格は${Number.MAX_SAFE_INTEGER}以下で入力してください`, + ), ), stock: v.pipe( v.number("在庫数は数字で入力してください"), v.integer("在庫数は整数で入力してください"), v.minValue(0, "在庫数は0以上で入力してください"), + v.maxValue( + Number.MAX_SAFE_INTEGER, + `在庫数は${Number.MAX_SAFE_INTEGER}以下で入力してください`, + ), ), image: v.pipe( v.string("商品画像は文字列で入力してください"), @@ -32,7 +46,23 @@ export const productSchema = v.object({ ), }) +export const updateProductSchema = v.object({ + ...createProductSchema.entries, + _method: v.literal("update"), + product_id: v.pipe( + v.number("製品IDは数字で入力してください"), + v.integer("製品IDは整数で入力してください"), + v.minValue(0, "製品IDは0以上で入力してください"), + v.maxValue( + Number.MAX_SAFE_INTEGER, + `製品IDは${Number.MAX_SAFE_INTEGER}以下で入力してください`, + ), + ), +}) + export type ProductFormProps = { + _method: "create" | "update" + defaultValue?: Partial | null | undefined submitText: string lastResult?: SubmissionResult | null | undefined } @@ -40,15 +70,26 @@ export type ProductFormProps = { export const ProductForm: FC = ({ submitText, lastResult, + _method, + defaultValue, }) => { const navigation = useNavigation() - const [form, fields] = useForm({ - constraint: getValibotConstraint(productSchema), + const schema = + _method === "update" ? updateProductSchema : createProductSchema + const [form, fields] = useForm({ + defaultValue: { + _method, + ...defaultValue, + }, + constraint: getValibotConstraint(schema), lastResult: navigation.state === "idle" ? lastResult : null, shouldValidate: "onBlur", shouldRevalidate: "onInput", + onSubmit: () => { + console.log("onSubmit") + }, onValidate: ({ formData }) => { - return parseWithValibot(formData, { schema: productSchema }) + return parseWithValibot(formData, { schema }) }, }) @@ -61,6 +102,18 @@ export const ProductForm: FC = ({ noValidate > + + {_method === "update" && ( + + )} 商品名 { - if (value.stock < 12) { - return "Server validation: You must be at least 12 to sign up" - } - }, -}) - -export default function Debug() { - const actionData = useActionData() - - const form = useForm({ - ...formOpts, - transform: useTransform( - (baseForm) => mergeForm(baseForm, actionData ?? {}), - [actionData], - ), - }) - const formErrors = form.useStore((formState) => formState.errors) - - return -} - -export async function action({ request }: ActionFunctionArgs) { - const formData = await request.formData() - try { - await serverValidate(formData) - } catch (e) { - if (e instanceof ServerValidateError) { - return e.formState - } - - // Some other error occurred while validating your form - throw e - } - - // Your form has successfully validated! - return null -} diff --git a/app/routes/register_new.tsx b/app/routes/register_new.tsx index 23295d1..51342a1 100644 --- a/app/routes/register_new.tsx +++ b/app/routes/register_new.tsx @@ -8,36 +8,185 @@ import { ModalContent, ModalHeader, ModalOverlay, + Text, useDisclosure, VStack, Wrap, WrapItem, } from "@chakra-ui/react" -import { ActionFunctionArgs } from "@remix-run/node" +import { ActionFunctionArgs, TypedResponse } from "@remix-run/node" import { json, useActionData, useLoaderData } from "@remix-run/react" import { parseWithValibot } from "conform-to-valibot" -import { FC, useState } from "react" +import { FC, useEffect, useMemo, useState } from "react" import { Form } from "~/components/atoms/Form" import { ProductCard } from "~/components/organisms/register/ProductCard" import { ProductForm, - productSchema, + createProductSchema, + updateProductSchema, } from "~/components/organisms/register/ProductForm" -import { readProduct } from "~/crud/crud_products" +import { + createProduct, + deleteProduct, + existProduct, + readProduct, + updateProduct, +} from "~/crud/crud_products" import { TypeProduct } from "~/type/typeproduct" +import * as v from "valibot" +import { SubmissionResult } from "@conform-to/react" +import { useMessage } from "~/hooks/useMessage" export const loader = async () => { const products = await readProduct() return json({ products }) } -export const action = async ({ request }: ActionFunctionArgs) => { +type ActionResponse = { + method?: string + success: boolean + message?: string + product_id?: number + result?: SubmissionResult +} + +export const action = async ({ + request, +}: ActionFunctionArgs): Promise> => { + if (request.method.toLocaleLowerCase() !== "post") { + return json( + { success: false, message: "不正なリクエストです" }, + { status: 400 }, + ) + } + const formData = await request.formData() - const product = parseWithValibot(formData, { schema: productSchema }) - if (product.status !== "success") { - return json(product.reply()) + const method = formData.get("_method") + switch (method) { + case "create": { + const product = parseWithValibot(formData, { + schema: createProductSchema, + }) + if (product.status !== "success") { + return json({ + method: "create", + success: false, + result: product.reply(), + }) + } + try { + const isExist = await existProduct(product.value.product_name) + if (isExist) { + return json({ + method: "create", + success: false, + result: product.reply({ + fieldErrors: { + product_name: ["同じ名前の商品がすでに存在します"], + }, + }), + }) + } + await createProduct({ + product_name: product.value.product_name, + price: product.value.price, + stock: product.value.stock, + image: product.value.image, + }) + return json({ + method: "create", + success: true, + result: product.reply({ resetForm: true }), + }) + } catch (error) { + return databaseErrorResponse("create", error) + } + } + + case "update": { + const product = parseWithValibot(formData, { + schema: updateProductSchema, + }) + if (product.status !== "success") { + return json({ + method: "update", + success: false, + result: product.reply(), + }) + } + try { + const isExist = await existProduct(product.value.product_name) + if (isExist) { + return json({ + method: "update", + success: false, + result: product.reply({ + fieldErrors: { + product_name: ["同じ名前の商品がすでに存在します"], + }, + }), + }) + } + await updateProduct(product.value.product_id, { + product_name: product.value.product_name, + price: product.value.price, + stock: product.value.stock, + image: product.value.image, + }) + return json({ + method: "update", + success: true, + result: product.reply({ resetForm: true }), + }) + } catch (error) { + return databaseErrorResponse("update", error) + } + } + + case "delete": { + const product = parseWithValibot(formData, { + schema: deleteProductSchema, + }) + + if (product.status !== "success") { + return json({ + method: "delete", + success: false, + result: product.reply(), + }) + } + try { + await deleteProduct(product.value.product_id) + return json({ + method: "delete", + success: true, + result: product.reply(), + }) + } catch (error) { + return databaseErrorResponse("delete", error) + } + } } - return json(product.reply({ resetForm: true })) + + return json( + { + method: method?.toString(), + success: false, + message: "不正なリクエストです", + }, + { status: 400 }, + ) +} + +const databaseErrorResponse = (method: string, error: unknown) => { + return json({ + method, + success: false, + message: + typeof error === "object" && error != null && "message" in error + ? String(error.message) + : "不明なデータベースエラーが発生しました", + } satisfies ActionResponse) } const Register = () => { @@ -52,6 +201,28 @@ export default Register const CreateProductForm: FC = () => { const actionData = useActionData() + const lastResult = useMemo(() => { + if ( + actionData != null && + "method" in actionData && + actionData?.method === "create" + ) { + return actionData.result + } + }, [actionData]) + const { showMessage } = useMessage() + + useEffect(() => { + if (actionData?.method !== "create") return + if (actionData.success) { + showMessage({ title: "登録完了", status: "success" }) + } else { + showMessage({ + title: actionData.message ? actionData.message : "登録失敗", + status: "error", + }) + } + }, [actionData]) return ( { 商品登録フォーム - + ) } @@ -76,6 +247,20 @@ const CreateProductForm: FC = () => { const Products: FC = () => { const { products } = useLoaderData() const [isOpen, setOpen] = useState(false) + const [changeProduct, setChangeProduct] = useState(null) + const [deleteProduct, setDeleteProduct] = useState(null) + const changeModal = useDisclosure() + const deleteModal = useDisclosure() + + const onClickChange = (product: TypeProduct) => { + setChangeProduct(product) + changeModal.onOpen() + } + + const onClickDelete = (product: TypeProduct) => { + setDeleteProduct(product) + deleteModal.onOpen() + } return ( @@ -89,51 +274,63 @@ const Products: FC = () => { )} {isOpen && ( - - {products.map((product) => ( - - - - ))} - + <> + + {products.map((product) => ( + + + + ))} + + )} - - ) -} - -const EditableProductCard: FC<{ - product: TypeProduct -}> = ({ product }) => { - const changeModal = useDisclosure() - const deleteModal = useDisclosure() - - return ( - <> - - + ) } const ChangeProductModal: FC<{ - product: TypeProduct + product: TypeProduct | null isOpen: boolean onClose: () => void -}> = ({ isOpen, onClose }) => { +}> = ({ product, isOpen, onClose }) => { const actionData = useActionData() + const lastResult = useMemo(() => { + if ( + actionData != null && + "method" in actionData && + actionData?.method === "update" + ) { + return actionData.result + } + }, [actionData]) + const { showMessage } = useMessage() + + useEffect(() => { + if (actionData?.method !== "update") return + if (actionData.success) { + showMessage({ title: "変更完了", status: "success" }) + onClose() + } else { + showMessage({ + title: actionData.message ? actionData.message : "変更失敗", + status: "error", + }) + } + }, [actionData]) return ( @@ -142,18 +339,47 @@ const ChangeProductModal: FC<{ 商品情報変更 - + ) } +const deleteProductSchema = v.object({ + product_id: v.pipe( + v.number("製品IDは数字で入力してください"), + v.integer("製品IDは整数で入力してください"), + v.minValue(0, "製品IDは0以上で入力してください"), + ), +}) + const DeleteProductModal: FC<{ - product: TypeProduct + product: TypeProduct | null isOpen: boolean onClose: () => void }> = ({ product, isOpen, onClose }) => { + const actionData = useActionData() + const { showMessage } = useMessage() + + useEffect(() => { + if (actionData?.method !== "delete") return + if (actionData.success) { + showMessage({ title: "削除完了", status: "success" }) + onClose() + } else { + showMessage({ + title: actionData.message ? actionData.message : "削除失敗", + status: "error", + }) + } + }, [actionData]) + return ( @@ -161,11 +387,15 @@ const DeleteProductModal: FC<{ 商品削除 -

{product.product_name}を本当に削除しますか?

+ {product?.product_name}を本当に削除しますか?
- - + + From 2be5d0d3cbe737cef3c2350a5d3f417e600797b1 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:39:22 +0900 Subject: [PATCH 09/18] refactor --- app/routes/register_new.tsx | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/app/routes/register_new.tsx b/app/routes/register_new.tsx index 51342a1..23e1e2b 100644 --- a/app/routes/register_new.tsx +++ b/app/routes/register_new.tsx @@ -201,15 +201,7 @@ export default Register const CreateProductForm: FC = () => { const actionData = useActionData() - const lastResult = useMemo(() => { - if ( - actionData != null && - "method" in actionData && - actionData?.method === "create" - ) { - return actionData.result - } - }, [actionData]) + const lastResult = useLastResult("create", actionData) const { showMessage } = useMessage() useEffect(() => { @@ -308,15 +300,7 @@ const ChangeProductModal: FC<{ onClose: () => void }> = ({ product, isOpen, onClose }) => { const actionData = useActionData() - const lastResult = useMemo(() => { - if ( - actionData != null && - "method" in actionData && - actionData?.method === "update" - ) { - return actionData.result - } - }, [actionData]) + const lastResult = useLastResult("update", actionData) const { showMessage } = useMessage() useEffect(() => { @@ -408,3 +392,18 @@ const DeleteProductModal: FC<{ ) } + +const useLastResult = ( + method: string, + actionData: ActionResponse | undefined, +) => { + return useMemo(() => { + if ( + actionData != null && + "method" in actionData && + actionData?.method === method + ) { + return actionData.result + } + }, [actionData]) +} From ce867027c0fb0d097eed3fcda42cac8008dd6bd6 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:40:04 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20`/register`=E3=81=A7=E4=BD=9C?= =?UTF-8?q?=E6=88=90=E3=81=97=E3=81=9F=E5=95=86=E5=93=81=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0=E3=82=92=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routes/register.tsx | 801 ++++++++++++++++-------------------- app/routes/register_new.tsx | 409 ------------------ 2 files changed, 344 insertions(+), 866 deletions(-) delete mode 100644 app/routes/register_new.tsx diff --git a/app/routes/register.tsx b/app/routes/register.tsx index f2fbff4..23e1e2b 100644 --- a/app/routes/register.tsx +++ b/app/routes/register.tsx @@ -1,522 +1,409 @@ import { - Box, Button, - FormControl, - FormHelperText, - FormLabel, Heading, Input, Modal, ModalBody, ModalCloseButton, ModalContent, - ModalFooter, ModalHeader, ModalOverlay, - Stack, + Text, useDisclosure, VStack, Wrap, WrapItem, } from "@chakra-ui/react" -import { ActionFunction, ActionFunctionArgs } from "@remix-run/node" -import { Form, json, useActionData, useLoaderData } from "@remix-run/react" -import React, { useState, useEffect } from "react" +import { ActionFunctionArgs, TypedResponse } from "@remix-run/node" +import { json, useActionData, useLoaderData } from "@remix-run/react" +import { parseWithValibot } from "conform-to-valibot" +import { FC, useEffect, useMemo, useState } from "react" +import { Form } from "~/components/atoms/Form" import { ProductCard } from "~/components/organisms/register/ProductCard" +import { + ProductForm, + createProductSchema, + updateProductSchema, +} from "~/components/organisms/register/ProductForm" import { createProduct, - deleteAllProducts, deleteProduct, existProduct, readProduct, updateProduct, } from "~/crud/crud_products" -import { useMessage } from "~/hooks/useMessage" import { TypeProduct } from "~/type/typeproduct" +import * as v from "valibot" +import { SubmissionResult } from "@conform-to/react" +import { useMessage } from "~/hooks/useMessage" -type ActionData = { - success: boolean - error?: string +export const loader = async () => { + const products = await readProduct() + return json({ products }) +} + +type ActionResponse = { method?: string + success: boolean + message?: string + product_id?: number + result?: SubmissionResult } -export default function Register() { - const [name, setName] = useState("") - const [price, setPrice] = useState("") - const [stock, setStock] = useState("") - const [image, setImage] = useState("") - const [isLoading, setLoading] = useState(false) - const actionData = useActionData() - const { showMessage } = useMessage() - const { products: products } = useLoaderData() - const [isOpen, setOpen] = useState(false) - const { - isOpen: isDeleteOpen, - onOpen: onDeleteOpen, - onClose: onDeleteClose, - } = useDisclosure() - const { - isOpen: isDeleteAllOpen, - onOpen: onDeleteAllOpen, - onClose: onDeleteAllClose, - } = useDisclosure() - const { - isOpen: isChangeOpen, - onOpen: onChangeOpen, - onClose: onChangeClose, - } = useDisclosure() - const [deleteProduct, setDeleteProduct] = useState(null) - const [changeProduct, setChangeProduct] = useState(null) +export const action = async ({ + request, +}: ActionFunctionArgs): Promise> => { + if (request.method.toLocaleLowerCase() !== "post") { + return json( + { success: false, message: "不正なリクエストです" }, + { status: 400 }, + ) + } - useEffect(() => { - if (actionData?.success && actionData?.method === "POST") { - setName("") - setPrice("") - setStock("") - setImage("") - setLoading(false) - showMessage({ title: "登録完了", status: "success" }) + const formData = await request.formData() + const method = formData.get("_method") + switch (method) { + case "create": { + const product = parseWithValibot(formData, { + schema: createProductSchema, + }) + if (product.status !== "success") { + return json({ + method: "create", + success: false, + result: product.reply(), + }) + } + try { + const isExist = await existProduct(product.value.product_name) + if (isExist) { + return json({ + method: "create", + success: false, + result: product.reply({ + fieldErrors: { + product_name: ["同じ名前の商品がすでに存在します"], + }, + }), + }) + } + await createProduct({ + product_name: product.value.product_name, + price: product.value.price, + stock: product.value.stock, + image: product.value.image, + }) + return json({ + method: "create", + success: true, + result: product.reply({ resetForm: true }), + }) + } catch (error) { + return databaseErrorResponse("create", error) + } } - if (actionData?.error === "isExist" && actionData?.method === "POST") { - showMessage({ title: "すでに登録済みです", status: "error" }) - } else if (actionData?.error && actionData?.method === "POST") { - showMessage({ title: "登録失敗", status: "error" }) + case "update": { + const product = parseWithValibot(formData, { + schema: updateProductSchema, + }) + if (product.status !== "success") { + return json({ + method: "update", + success: false, + result: product.reply(), + }) + } + try { + const isExist = await existProduct(product.value.product_name) + if (isExist) { + return json({ + method: "update", + success: false, + result: product.reply({ + fieldErrors: { + product_name: ["同じ名前の商品がすでに存在します"], + }, + }), + }) + } + await updateProduct(product.value.product_id, { + product_name: product.value.product_name, + price: product.value.price, + stock: product.value.stock, + image: product.value.image, + }) + return json({ + method: "update", + success: true, + result: product.reply({ resetForm: true }), + }) + } catch (error) { + return databaseErrorResponse("update", error) + } } - if (actionData?.success && actionData?.method === "delete") { - showMessage({ title: "削除完了", status: "success" }) - setDeleteProduct(null) - onDeleteClose() - } - if (actionData?.error && actionData?.method === "delete") { - showMessage({ title: "削除失敗", status: "error" }) - } - if (actionData?.success && actionData?.method === "update") { - showMessage({ title: "変更完了", status: "success" }) - setChangeProduct(null) - onChangeClose() - } - if (actionData?.error && actionData?.method === "update") { - showMessage({ title: "変更失敗", status: "error" }) - } - }, [actionData, onDeleteClose, onChangeClose, showMessage]) + case "delete": { + const product = parseWithValibot(formData, { + schema: deleteProductSchema, + }) - const nameChange = (e: React.ChangeEvent) => { - setName(e.target.value) + if (product.status !== "success") { + return json({ + method: "delete", + success: false, + result: product.reply(), + }) + } + try { + await deleteProduct(product.value.product_id) + return json({ + method: "delete", + success: true, + result: product.reply(), + }) + } catch (error) { + return databaseErrorResponse("delete", error) + } + } } - const priceChange = (e: React.ChangeEvent) => { - setPrice(e.target.value) - } + return json( + { + method: method?.toString(), + success: false, + message: "不正なリクエストです", + }, + { status: 400 }, + ) +} - const stockChange = (e: React.ChangeEvent) => { - setStock(e.target.value) - } +const databaseErrorResponse = (method: string, error: unknown) => { + return json({ + method, + success: false, + message: + typeof error === "object" && error != null && "message" in error + ? String(error.message) + : "不明なデータベースエラーが発生しました", + } satisfies ActionResponse) +} - const imageChange = (e: React.ChangeEvent) => { - setImage(e.target.value) - } +const Register = () => { + return ( + + + + + ) +} +export default Register - const productOpen = () => { - setOpen(true) - } +const CreateProductForm: FC = () => { + const actionData = useActionData() + const lastResult = useLastResult("create", actionData) + const { showMessage } = useMessage() - const productClose = () => { - setOpen(false) - } + useEffect(() => { + if (actionData?.method !== "create") return + if (actionData.success) { + showMessage({ title: "登録完了", status: "success" }) + } else { + showMessage({ + title: actionData.message ? actionData.message : "登録失敗", + status: "error", + }) + } + }, [actionData]) - const clickDelete = (product: TypeProduct) => { - setDeleteProduct(product) - onDeleteOpen() - } + return ( + + + 商品登録フォーム + + + + ) +} - const clickDeleteAll = () => { - onDeleteAllOpen() - } +const Products: FC = () => { + const { products } = useLoaderData() + const [isOpen, setOpen] = useState(false) + const [changeProduct, setChangeProduct] = useState(null) + const [deleteProduct, setDeleteProduct] = useState(null) + const changeModal = useDisclosure() + const deleteModal = useDisclosure() - const clickChange = (product: TypeProduct) => { + const onClickChange = (product: TypeProduct) => { setChangeProduct(product) - onChangeOpen() + changeModal.onOpen() } - const handleNameChange = (e: React.ChangeEvent) => { - if (changeProduct) { - setChangeProduct({ - ...changeProduct, - product_name: e.target.value, - }) - } + const onClickDelete = (product: TypeProduct) => { + setDeleteProduct(product) + deleteModal.onOpen() } - const handlePriceChange = (e: React.ChangeEvent) => { - if (changeProduct) { - setChangeProduct({ - ...changeProduct, - price: Number(e.target.value), - }) - } - } + return ( + + {isOpen ? ( + + ) : ( + + )} + {isOpen && ( + <> + + {products.map((product) => ( + + + + ))} + + + )} + + + + ) +} - const handleStockChange = (e: React.ChangeEvent) => { - if (changeProduct) { - setChangeProduct({ - ...changeProduct, - stock: Number(e.target.value), - }) - } - } +const ChangeProductModal: FC<{ + product: TypeProduct | null + isOpen: boolean + onClose: () => void +}> = ({ product, isOpen, onClose }) => { + const actionData = useActionData() + const lastResult = useLastResult("update", actionData) + const { showMessage } = useMessage() - const handleImageChange = (e: React.ChangeEvent) => { - if (changeProduct) { - setChangeProduct({ - ...changeProduct, - image: e.target.value, + useEffect(() => { + if (actionData?.method !== "update") return + if (actionData.success) { + showMessage({ title: "変更完了", status: "success" }) + onClose() + } else { + showMessage({ + title: actionData.message ? actionData.message : "変更失敗", + status: "error", }) } - } + }, [actionData]) return ( -
- - - - - 商品登録フォーム - - - - - 商品名 - - - - 価格(税込み) - - - - 在庫 - - - - 商品画像 - - - 商品画像は画像URLで入力してください - - - - - - - {isOpen ? ( - - ) : ( - - )} - {isOpen ? ( -
- - {products.map((product) => ( - - - - ))} - -
- ) : null} -
- - - - -

{deleteProduct?.product_name}を本当に削除しますか?

-
-
- - - -
- -
-
-
- - - - - -

本当にすべて削除しますか?

-
-
- - -
- -
-
-
- - - - - - -
- - - - - - 商品名 - - - - 価格(税込み) - - - - 在庫 - - - - 商品画像 - - - 商品画像は画像URLで入力してください - - - - - - - -
-
-
-
+ + + + + 商品情報変更 + + + + + ) } -export const loader = async () => { - const response = await readProduct() - return json({ products: response }) -} - -export const action: ActionFunction = async ({ - request, -}: ActionFunctionArgs) => { - const formData = await request.formData() - const method = formData.get("_method") - - if ( - request.method === "POST" && - method !== "delete" && - method !== "update" && - method !== "delete_all" - ) { - const product_name = formData.get("product_name") - const price = Number(formData.get("price")) - const stock = Number(formData.get("stock")) - const image = formData.get("image") - - if (typeof product_name === "string") { - const isExist = await existProduct(product_name) +const deleteProductSchema = v.object({ + product_id: v.pipe( + v.number("製品IDは数字で入力してください"), + v.integer("製品IDは整数で入力してください"), + v.minValue(0, "製品IDは0以上で入力してください"), + ), +}) - if (isExist) { - return json({ - success: false, - error: "isExist", - method: request.method, - }) - } - } - - const isValidInput = - typeof product_name === "string" && - !isNaN(price) && - !isNaN(stock) && - typeof image === "string" && - URL.canParse(image) - if (isValidInput) { - await createProduct({ - product_name, - price, - stock, - image, - }) +const DeleteProductModal: FC<{ + product: TypeProduct | null + isOpen: boolean + onClose: () => void +}> = ({ product, isOpen, onClose }) => { + const actionData = useActionData() + const { showMessage } = useMessage() - return json({ success: true, method: request.method }) - } else { - return json({ - success: false, - error: "Invalid input", - method: request.method, - }) - } - } else if (method === "delete") { - const product_id = Number(formData.get("product_id")) - console.log("Method:", method) - console.log("Product ID:", product_id) - if (!isNaN(product_id)) { - try { - await deleteProduct(product_id) - return json({ success: true, method: method }) - } catch (error) { - return json({ - success: false, - error: "Failed to delete product", - method: method, - }) - } + useEffect(() => { + if (actionData?.method !== "delete") return + if (actionData.success) { + showMessage({ title: "削除完了", status: "success" }) + onClose() } else { - return json({ - success: false, - error: "Invalid product ID", - method: method, + showMessage({ + title: actionData.message ? actionData.message : "削除失敗", + status: "error", }) } - } else if (method === "update") { - const product_id = Number(formData.get("product_id")) - const product_name = formData.get("product_name") - const price = Number(formData.get("price")) - const stock = Number(formData.get("stock")) - const image = formData.get("image") + }, [actionData]) - const isValidInput = - !isNaN(product_id) && - typeof product_name === "string" && - !isNaN(price) && - !isNaN(stock) && - typeof image === "string" && - URL.canParse(image) - if (isValidInput) { - await updateProduct(product_id, { - product_name, - price, - stock, - image, - }) - return json({ success: true, method: method }) - } else { - return json({ success: false, error: "no product_name", method: method }) + return ( + + + + + 商品削除 + + {product?.product_name}を本当に削除しますか? +
+
+ + + +
+ +
+
+
+ ) +} + +const useLastResult = ( + method: string, + actionData: ActionResponse | undefined, +) => { + return useMemo(() => { + if ( + actionData != null && + "method" in actionData && + actionData?.method === method + ) { + return actionData.result } - } else if (method == "delete_all") { - await deleteAllProducts() - return json({ success: true, method: method }) - } else { - return json({ success: false, error: "Unsupported request method" }) - } + }, [actionData]) } diff --git a/app/routes/register_new.tsx b/app/routes/register_new.tsx deleted file mode 100644 index 23e1e2b..0000000 --- a/app/routes/register_new.tsx +++ /dev/null @@ -1,409 +0,0 @@ -import { - Button, - Heading, - Input, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalHeader, - ModalOverlay, - Text, - useDisclosure, - VStack, - Wrap, - WrapItem, -} from "@chakra-ui/react" -import { ActionFunctionArgs, TypedResponse } from "@remix-run/node" -import { json, useActionData, useLoaderData } from "@remix-run/react" -import { parseWithValibot } from "conform-to-valibot" -import { FC, useEffect, useMemo, useState } from "react" -import { Form } from "~/components/atoms/Form" -import { ProductCard } from "~/components/organisms/register/ProductCard" -import { - ProductForm, - createProductSchema, - updateProductSchema, -} from "~/components/organisms/register/ProductForm" -import { - createProduct, - deleteProduct, - existProduct, - readProduct, - updateProduct, -} from "~/crud/crud_products" -import { TypeProduct } from "~/type/typeproduct" -import * as v from "valibot" -import { SubmissionResult } from "@conform-to/react" -import { useMessage } from "~/hooks/useMessage" - -export const loader = async () => { - const products = await readProduct() - return json({ products }) -} - -type ActionResponse = { - method?: string - success: boolean - message?: string - product_id?: number - result?: SubmissionResult -} - -export const action = async ({ - request, -}: ActionFunctionArgs): Promise> => { - if (request.method.toLocaleLowerCase() !== "post") { - return json( - { success: false, message: "不正なリクエストです" }, - { status: 400 }, - ) - } - - const formData = await request.formData() - const method = formData.get("_method") - switch (method) { - case "create": { - const product = parseWithValibot(formData, { - schema: createProductSchema, - }) - if (product.status !== "success") { - return json({ - method: "create", - success: false, - result: product.reply(), - }) - } - try { - const isExist = await existProduct(product.value.product_name) - if (isExist) { - return json({ - method: "create", - success: false, - result: product.reply({ - fieldErrors: { - product_name: ["同じ名前の商品がすでに存在します"], - }, - }), - }) - } - await createProduct({ - product_name: product.value.product_name, - price: product.value.price, - stock: product.value.stock, - image: product.value.image, - }) - return json({ - method: "create", - success: true, - result: product.reply({ resetForm: true }), - }) - } catch (error) { - return databaseErrorResponse("create", error) - } - } - - case "update": { - const product = parseWithValibot(formData, { - schema: updateProductSchema, - }) - if (product.status !== "success") { - return json({ - method: "update", - success: false, - result: product.reply(), - }) - } - try { - const isExist = await existProduct(product.value.product_name) - if (isExist) { - return json({ - method: "update", - success: false, - result: product.reply({ - fieldErrors: { - product_name: ["同じ名前の商品がすでに存在します"], - }, - }), - }) - } - await updateProduct(product.value.product_id, { - product_name: product.value.product_name, - price: product.value.price, - stock: product.value.stock, - image: product.value.image, - }) - return json({ - method: "update", - success: true, - result: product.reply({ resetForm: true }), - }) - } catch (error) { - return databaseErrorResponse("update", error) - } - } - - case "delete": { - const product = parseWithValibot(formData, { - schema: deleteProductSchema, - }) - - if (product.status !== "success") { - return json({ - method: "delete", - success: false, - result: product.reply(), - }) - } - try { - await deleteProduct(product.value.product_id) - return json({ - method: "delete", - success: true, - result: product.reply(), - }) - } catch (error) { - return databaseErrorResponse("delete", error) - } - } - } - - return json( - { - method: method?.toString(), - success: false, - message: "不正なリクエストです", - }, - { status: 400 }, - ) -} - -const databaseErrorResponse = (method: string, error: unknown) => { - return json({ - method, - success: false, - message: - typeof error === "object" && error != null && "message" in error - ? String(error.message) - : "不明なデータベースエラーが発生しました", - } satisfies ActionResponse) -} - -const Register = () => { - return ( - - - - - ) -} -export default Register - -const CreateProductForm: FC = () => { - const actionData = useActionData() - const lastResult = useLastResult("create", actionData) - const { showMessage } = useMessage() - - useEffect(() => { - if (actionData?.method !== "create") return - if (actionData.success) { - showMessage({ title: "登録完了", status: "success" }) - } else { - showMessage({ - title: actionData.message ? actionData.message : "登録失敗", - status: "error", - }) - } - }, [actionData]) - - return ( - - - 商品登録フォーム - - - - ) -} - -const Products: FC = () => { - const { products } = useLoaderData() - const [isOpen, setOpen] = useState(false) - const [changeProduct, setChangeProduct] = useState(null) - const [deleteProduct, setDeleteProduct] = useState(null) - const changeModal = useDisclosure() - const deleteModal = useDisclosure() - - const onClickChange = (product: TypeProduct) => { - setChangeProduct(product) - changeModal.onOpen() - } - - const onClickDelete = (product: TypeProduct) => { - setDeleteProduct(product) - deleteModal.onOpen() - } - - return ( - - {isOpen ? ( - - ) : ( - - )} - {isOpen && ( - <> - - {products.map((product) => ( - - - - ))} - - - )} - - - - ) -} - -const ChangeProductModal: FC<{ - product: TypeProduct | null - isOpen: boolean - onClose: () => void -}> = ({ product, isOpen, onClose }) => { - const actionData = useActionData() - const lastResult = useLastResult("update", actionData) - const { showMessage } = useMessage() - - useEffect(() => { - if (actionData?.method !== "update") return - if (actionData.success) { - showMessage({ title: "変更完了", status: "success" }) - onClose() - } else { - showMessage({ - title: actionData.message ? actionData.message : "変更失敗", - status: "error", - }) - } - }, [actionData]) - - return ( - - - - - 商品情報変更 - - - - - - ) -} - -const deleteProductSchema = v.object({ - product_id: v.pipe( - v.number("製品IDは数字で入力してください"), - v.integer("製品IDは整数で入力してください"), - v.minValue(0, "製品IDは0以上で入力してください"), - ), -}) - -const DeleteProductModal: FC<{ - product: TypeProduct | null - isOpen: boolean - onClose: () => void -}> = ({ product, isOpen, onClose }) => { - const actionData = useActionData() - const { showMessage } = useMessage() - - useEffect(() => { - if (actionData?.method !== "delete") return - if (actionData.success) { - showMessage({ title: "削除完了", status: "success" }) - onClose() - } else { - showMessage({ - title: actionData.message ? actionData.message : "削除失敗", - status: "error", - }) - } - }, [actionData]) - - return ( - - - - - 商品削除 - - {product?.product_name}を本当に削除しますか? -
-
- - - -
- -
-
-
- ) -} - -const useLastResult = ( - method: string, - actionData: ActionResponse | undefined, -) => { - return useMemo(() => { - if ( - actionData != null && - "method" in actionData && - actionData?.method === method - ) { - return actionData.result - } - }, [actionData]) -} From 085941b91acd2248bd6c25529764605e74b13a71 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:21:43 +0900 Subject: [PATCH 11/18] refactor --- .../organisms/register/ProductForm.tsx | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/app/components/organisms/register/ProductForm.tsx b/app/components/organisms/register/ProductForm.tsx index 6f67041..45902a2 100644 --- a/app/components/organisms/register/ProductForm.tsx +++ b/app/components/organisms/register/ProductForm.tsx @@ -5,7 +5,12 @@ import { Button, FormControl, FormLabel, Input, VStack } from "@chakra-ui/react" import { FieldInfo } from "~/components/molecules/FieldInfo" import * as v from "valibot" import { getValibotConstraint, parseWithValibot } from "conform-to-valibot" -import { getInputProps, SubmissionResult, useForm } from "@conform-to/react" +import { + getFormProps, + getInputProps, + SubmissionResult, + useForm, +} from "@conform-to/react" import { useNavigation } from "@remix-run/react" export type ProductFormValues = ( @@ -85,22 +90,11 @@ export const ProductForm: FC = ({ lastResult: navigation.state === "idle" ? lastResult : null, shouldValidate: "onBlur", shouldRevalidate: "onInput", - onSubmit: () => { - console.log("onSubmit") - }, - onValidate: ({ formData }) => { - return parseWithValibot(formData, { schema }) - }, + onValidate: ({ formData }) => parseWithValibot(formData, { schema }), }) return ( -
+ Date: Tue, 19 Nov 2024 17:07:34 +0900 Subject: [PATCH 12/18] =?UTF-8?q?feat:=20=E8=A8=88=E7=AE=97=E3=81=AE?= =?UTF-8?q?=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?gs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organisms/reception/Calculator.tsx | 104 +++++++++++++++++- app/lib/calculate.ts | 103 +++++++++++++++++ 2 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 app/lib/calculate.ts diff --git a/app/components/organisms/reception/Calculator.tsx b/app/components/organisms/reception/Calculator.tsx index b184cc4..da2e717 100644 --- a/app/components/organisms/reception/Calculator.tsx +++ b/app/components/organisms/reception/Calculator.tsx @@ -42,4 +42,106 @@ export const Calculator: FC = memo(() => { ) }) -Calculator.displayName = "Calculator" +type Token = number | "+" | "-" | "*" | "/" | "(" | ")" + +type Expression = Num | Operator + +type Num = { + type: "number" + value: number + lhs?: Expression + rhs?: Expression +} + +type Operator = { + type: "operator" + value: "+" | "-" | "*" | "/" + lhs: Expression + rhs: Expression +} + +const calculate = (tokens: Token[]): number => { + const expr = parse(tokens) + return calculate_expr(expr) +} + +const calculate_expr = (expr: Expression): number => { + if (expr.type === "number") { + return expr.value + } + + if (expr.type === "operator") { + const lhs = calculate_expr(expr.lhs) + const rhs = calculate_expr(expr.rhs) + switch (expr.value) { + case "+": + return lhs + rhs + case "-": + return lhs - rhs + case "*": + return lhs * rhs + case "/": + return lhs / rhs + } + } + throw new Error("Invalid expression") +} + +// primary = "(" expr ")" | number +const parse_primary = (tokens: Token[]): [Expression, Token[]] => { + const token = tokens[0] + if (token === "(") { + const [expr, rest] = parse_expr(tokens.slice(1)) + if (rest[0] !== ")") { + throw new Error("Invalid expression, expected ')'") + } + return [expr, rest.slice(1)] + } + if (typeof token === "number") { + return [{ type: "number", value: token }, tokens.slice(1)] + } + throw new Error("Invalid expression, expected number or '('") +} + +// mul = primary ("*" primary | "/" primary)* +const parse_mul = (tokens: Token[]): [Expression, Token[]] => { + let [lhs, rest] = parse_primary(tokens) + while (rest.length > 0) { + const token = rest[0] + if (token === "*" || token === "/") { + const [rhs, rest2] = parse_primary(rest.slice(1)) + lhs = { type: "operator", value: token, lhs, rhs } + rest = rest2 + } else { + break + } + } + return [lhs, rest] +} + +// expr = mul ("+" mul | "-" mul)* +const parse_expr = (tokens: Token[]): [Expression, Token[]] => { + let [lhs, rest] = parse_mul(tokens) + while (rest.length > 0) { + const token = rest[0] + if (token === "+" || token === "-") { + const [rhs, rest2] = parse_mul(rest.slice(1)) + lhs = { type: "operator", value: token, lhs, rhs } + rest = rest2 + } else { + break + } + } + return [lhs, rest] +} + +/** + * 数式のトークン列をパースして、計算順序を表す木構造を返す + */ +const parse = (tokens: Token[]): Expression => { + const [expr, rest] = parse_expr(tokens) + if (rest.length > 0) { + throw new Error("Invalid expression") + } + return expr +} diff --git a/app/lib/calculate.ts b/app/lib/calculate.ts new file mode 100644 index 0000000..e4f6d3c --- /dev/null +++ b/app/lib/calculate.ts @@ -0,0 +1,103 @@ +export type Token = number | "+" | "-" | "*" | "/" | "(" | ")" + +export type Expression = Num | Operator + +export type Num = { + type: "number" + value: number + lhs?: Expression + rhs?: Expression +} + +export type Operator = { + type: "operator" + value: "+" | "-" | "*" | "/" + lhs: Expression + rhs: Expression +} + +export const calculate = (tokens: Token[]): number => { + const expr = parse(tokens) + return calculate_expr(expr) +} + +const calculate_expr = (expr: Expression): number => { + if (expr.type === "number") { + return expr.value + } + + if (expr.type === "operator") { + const lhs = calculate_expr(expr.lhs) + const rhs = calculate_expr(expr.rhs) + switch (expr.value) { + case "+": + return lhs + rhs + case "-": + return lhs - rhs + case "*": + return lhs * rhs + case "/": + return lhs / rhs + } + } + throw new Error("Invalid expression") +} + +// primary = "(" expr ")" | number +const parse_primary = (tokens: Token[]): [Expression, Token[]] => { + const token = tokens[0] + if (token === "(") { + const [expr, rest] = parse_expr(tokens.slice(1)) + if (rest[0] !== ")") { + throw new Error("Invalid expression, expected ')'") + } + return [expr, rest.slice(1)] + } + if (typeof token === "number") { + return [{ type: "number", value: token }, tokens.slice(1)] + } + throw new Error("Invalid expression, expected number or '('") +} + +// mul = primary ("*" primary | "/" primary)* +const parse_mul = (tokens: Token[]): [Expression, Token[]] => { + let [lhs, rest] = parse_primary(tokens) + while (rest.length > 0) { + const token = rest[0] + if (token === "*" || token === "/") { + const [rhs, rest2] = parse_primary(rest.slice(1)) + lhs = { type: "operator", value: token, lhs, rhs } + rest = rest2 + } else { + break + } + } + return [lhs, rest] +} + +// expr = mul ("+" mul | "-" mul)* +const parse_expr = (tokens: Token[]): [Expression, Token[]] => { + let [lhs, rest] = parse_mul(tokens) + while (rest.length > 0) { + const token = rest[0] + if (token === "+" || token === "-") { + const [rhs, rest2] = parse_mul(rest.slice(1)) + lhs = { type: "operator", value: token, lhs, rhs } + rest = rest2 + } else { + break + } + } + return [lhs, rest] +} + +/** + * 数式のトークン列をパースして、計算順序を表す木構造を返す + */ +const parse = (tokens: Token[]): Expression => { + const [expr, rest] = parse_expr(tokens) + if (rest.length > 0) { + throw new Error("Invalid expression") + } + return expr +} From 255812cc70199d31026548592ce71ef1c657c2a7 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:19:28 +0900 Subject: [PATCH 13/18] =?UTF-8?q?fix:=20=E3=83=95=E3=82=A9=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=B5=E3=82=A4=E3=82=BA=E3=81=AB=E3=82=88=E3=82=8A?= =?UTF-8?q?=E5=B0=91=E3=81=97=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E3=81=8C=E5=B4=A9=E3=82=8C=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organisms/reception/Calculator.tsx | 160 +++--------------- 1 file changed, 24 insertions(+), 136 deletions(-) diff --git a/app/components/organisms/reception/Calculator.tsx b/app/components/organisms/reception/Calculator.tsx index da2e717..364d77f 100644 --- a/app/components/organisms/reception/Calculator.tsx +++ b/app/components/organisms/reception/Calculator.tsx @@ -1,147 +1,35 @@ -import { Box, Button, HStack, Text, VStack } from "@chakra-ui/react" +import { Box, Button, Grid, HStack, Text, VStack } from "@chakra-ui/react" import { FC, memo } from "react" export const Calculator: FC = memo(() => { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + ) }) - -type Token = number | "+" | "-" | "*" | "/" | "(" | ")" - -type Expression = Num | Operator - -type Num = { - type: "number" - value: number - lhs?: Expression - rhs?: Expression -} - -type Operator = { - type: "operator" - value: "+" | "-" | "*" | "/" - lhs: Expression - rhs: Expression -} - -const calculate = (tokens: Token[]): number => { - const expr = parse(tokens) - return calculate_expr(expr) -} - -const calculate_expr = (expr: Expression): number => { - if (expr.type === "number") { - return expr.value - } - - if (expr.type === "operator") { - const lhs = calculate_expr(expr.lhs) - const rhs = calculate_expr(expr.rhs) - switch (expr.value) { - case "+": - return lhs + rhs - case "-": - return lhs - rhs - case "*": - return lhs * rhs - case "/": - return lhs / rhs - } - } - throw new Error("Invalid expression") -} - -// primary = "(" expr ")" | number -const parse_primary = (tokens: Token[]): [Expression, Token[]] => { - const token = tokens[0] - if (token === "(") { - const [expr, rest] = parse_expr(tokens.slice(1)) - if (rest[0] !== ")") { - throw new Error("Invalid expression, expected ')'") - } - return [expr, rest.slice(1)] - } - if (typeof token === "number") { - return [{ type: "number", value: token }, tokens.slice(1)] - } - throw new Error("Invalid expression, expected number or '('") -} - -// mul = primary ("*" primary | "/" primary)* -const parse_mul = (tokens: Token[]): [Expression, Token[]] => { - let [lhs, rest] = parse_primary(tokens) - while (rest.length > 0) { - const token = rest[0] - if (token === "*" || token === "/") { - const [rhs, rest2] = parse_primary(rest.slice(1)) - lhs = { type: "operator", value: token, lhs, rhs } - rest = rest2 - } else { - break - } - } - return [lhs, rest] -} - -// expr = mul ("+" mul | "-" mul)* -const parse_expr = (tokens: Token[]): [Expression, Token[]] => { - let [lhs, rest] = parse_mul(tokens) - while (rest.length > 0) { - const token = rest[0] - if (token === "+" || token === "-") { - const [rhs, rest2] = parse_mul(rest.slice(1)) - lhs = { type: "operator", value: token, lhs, rhs } - rest = rest2 - } else { - break - } - } - return [lhs, rest] -} - -/** - * 数式のトークン列をパースして、計算順序を表す木構造を返す - */ -const parse = (tokens: Token[]): Expression => { - const [expr, rest] = parse_expr(tokens) - if (rest.length > 0) { - throw new Error("Invalid expression") - } - return expr -} From bd5a72087ceeab03335a57d2db1dce270c5fbafc Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:41:55 +0900 Subject: [PATCH 14/18] =?UTF-8?q?feat:=20=E9=9B=BB=E5=8D=93=E3=82=92?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organisms/reception/Calculator.tsx | 79 +++++++++++++------ app/hooks/useToken.ts | 46 +++++++++++ app/lib/calculate.ts | 26 +++--- app/routes/reception.tsx | 2 +- 4 files changed, 115 insertions(+), 38 deletions(-) create mode 100644 app/hooks/useToken.ts diff --git a/app/components/organisms/reception/Calculator.tsx b/app/components/organisms/reception/Calculator.tsx index 364d77f..86b77f9 100644 --- a/app/components/organisms/reception/Calculator.tsx +++ b/app/components/organisms/reception/Calculator.tsx @@ -1,35 +1,66 @@ -import { Box, Button, Grid, HStack, Text, VStack } from "@chakra-ui/react" +import { Button, Grid, Text, Tooltip, VStack } from "@chakra-ui/react" import { FC, memo } from "react" +import { useTokens } from "~/hooks/useToken" + +export type CalculatorProps = { + total: number +} + +export const Calculator: FC = memo(({ total }) => { + const { onInput, clear, tokens, input, calculate, pushToken } = useTokens() -export const Calculator: FC = memo(() => { return ( - - - - - - - - - - - - - - - - - - + + {tokens.join(" ")} + + {input} + + + + + + + + + + + + + + + + + + + + + + + ) }) +Calculator.displayName = "Calculator" diff --git a/app/hooks/useToken.ts b/app/hooks/useToken.ts new file mode 100644 index 0000000..dc4738e --- /dev/null +++ b/app/hooks/useToken.ts @@ -0,0 +1,46 @@ +import { useMemo, useState } from "react" +import { calculate as calculateTokens, Token } from "~/lib/calculate" + +export const useTokens = () => { + const [tokens, setTokens] = useState([]) + const [input, setInput] = useState(0) + + const pushToken = (token: Token) => setTokens((prev) => [...prev, token]) + const popToken = () => setTokens((prev) => prev.slice(0, -1)) + const clearTokens = () => setTokens([]) + + const onInput = (token: Token) => { + if (typeof token === "number") { + setInput((prev) => prev * 10 + token) + } else { + pushToken(input) + setInput(0) + pushToken(token) + } + } + + const clear = () => { + setInput(0) + clearTokens() + } + + const calculate = () => { + const output = calculateTokens([...tokens, input]) + clearTokens() + setInput(output) + return output + } + + return { + tokens, + setTokens, + pushToken, + popToken, + clearTokens, + input, + setInput, + onInput, + clear, + calculate, + } as const +} diff --git a/app/lib/calculate.ts b/app/lib/calculate.ts index e4f6d3c..a93539a 100644 --- a/app/lib/calculate.ts +++ b/app/lib/calculate.ts @@ -18,17 +18,17 @@ export type Operator = { export const calculate = (tokens: Token[]): number => { const expr = parse(tokens) - return calculate_expr(expr) + return calculateExpr(expr) } -const calculate_expr = (expr: Expression): number => { +const calculateExpr = (expr: Expression): number => { if (expr.type === "number") { return expr.value } if (expr.type === "operator") { - const lhs = calculate_expr(expr.lhs) - const rhs = calculate_expr(expr.rhs) + const lhs = calculateExpr(expr.lhs) + const rhs = calculateExpr(expr.rhs) switch (expr.value) { case "+": return lhs + rhs @@ -44,10 +44,10 @@ const calculate_expr = (expr: Expression): number => { } // primary = "(" expr ")" | number -const parse_primary = (tokens: Token[]): [Expression, Token[]] => { +const parsePrimary = (tokens: Token[]): [Expression, Token[]] => { const token = tokens[0] if (token === "(") { - const [expr, rest] = parse_expr(tokens.slice(1)) + const [expr, rest] = parseExpr(tokens.slice(1)) if (rest[0] !== ")") { throw new Error("Invalid expression, expected ')'") } @@ -60,12 +60,12 @@ const parse_primary = (tokens: Token[]): [Expression, Token[]] => { } // mul = primary ("*" primary | "/" primary)* -const parse_mul = (tokens: Token[]): [Expression, Token[]] => { - let [lhs, rest] = parse_primary(tokens) +const parseMul = (tokens: Token[]): [Expression, Token[]] => { + let [lhs, rest] = parsePrimary(tokens) while (rest.length > 0) { const token = rest[0] if (token === "*" || token === "/") { - const [rhs, rest2] = parse_primary(rest.slice(1)) + const [rhs, rest2] = parsePrimary(rest.slice(1)) lhs = { type: "operator", value: token, lhs, rhs } rest = rest2 } else { @@ -76,12 +76,12 @@ const parse_mul = (tokens: Token[]): [Expression, Token[]] => { } // expr = mul ("+" mul | "-" mul)* -const parse_expr = (tokens: Token[]): [Expression, Token[]] => { - let [lhs, rest] = parse_mul(tokens) +const parseExpr = (tokens: Token[]): [Expression, Token[]] => { + let [lhs, rest] = parseMul(tokens) while (rest.length > 0) { const token = rest[0] if (token === "+" || token === "-") { - const [rhs, rest2] = parse_mul(rest.slice(1)) + const [rhs, rest2] = parseMul(rest.slice(1)) lhs = { type: "operator", value: token, lhs, rhs } rest = rest2 } else { @@ -95,7 +95,7 @@ const parse_expr = (tokens: Token[]): [Expression, Token[]] => { * 数式のトークン列をパースして、計算順序を表す木構造を返す */ const parse = (tokens: Token[]): Expression => { - const [expr, rest] = parse_expr(tokens) + const [expr, rest] = parseExpr(tokens) if (rest.length > 0) { throw new Error("Invalid expression") } diff --git a/app/routes/reception.tsx b/app/routes/reception.tsx index 5a715b3..85533c5 100644 --- a/app/routes/reception.tsx +++ b/app/routes/reception.tsx @@ -226,7 +226,7 @@ export default function Reception() { - + From 718755cc3b75e343661b32f901c8c6b727461ba4 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:24:55 +0900 Subject: [PATCH 15/18] =?UTF-8?q?fix:=202=E6=A1=81=E4=BB=A5=E4=B8=8A?= =?UTF-8?q?=E3=81=AE=E6=95=B0=E5=80=A4=E3=82=92=E5=85=A5=E5=8A=9B=E3=81=97?= =?UTF-8?q?=E3=81=9F=E9=9A=9B=E3=81=AB=E5=A3=8A=E3=82=8C=E3=82=8B=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organisms/reception/Calculator.tsx | 9 ++++--- app/hooks/useToken.ts | 4 +-- app/lib/calculate.ts | 25 +++++++++++++++++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/app/components/organisms/reception/Calculator.tsx b/app/components/organisms/reception/Calculator.tsx index 86b77f9..05c9b15 100644 --- a/app/components/organisms/reception/Calculator.tsx +++ b/app/components/organisms/reception/Calculator.tsx @@ -1,6 +1,7 @@ import { Button, Grid, Text, Tooltip, VStack } from "@chakra-ui/react" import { FC, memo } from "react" import { useTokens } from "~/hooks/useToken" +import { renderToken } from "~/lib/calculate" export type CalculatorProps = { total: number @@ -29,7 +30,7 @@ export const Calculator: FC = memo(({ total }) => { rounded="md" width="full" > - {tokens.join(" ")} + {tokens.map(renderToken).join(" ")} {input} @@ -44,18 +45,18 @@ export const Calculator: FC = memo(({ total }) => { - + - + - + diff --git a/app/hooks/useToken.ts b/app/hooks/useToken.ts index dc4738e..5267914 100644 --- a/app/hooks/useToken.ts +++ b/app/hooks/useToken.ts @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react" +import { useState } from "react" import { calculate as calculateTokens, Token } from "~/lib/calculate" export const useTokens = () => { @@ -11,7 +11,7 @@ export const useTokens = () => { const onInput = (token: Token) => { if (typeof token === "number") { - setInput((prev) => prev * 10 + token) + setInput((prev) => Number.parseInt(prev.toString() + token.toString())) } else { pushToken(input) setInput(0) diff --git a/app/lib/calculate.ts b/app/lib/calculate.ts index a93539a..8a1e5e1 100644 --- a/app/lib/calculate.ts +++ b/app/lib/calculate.ts @@ -1,4 +1,8 @@ -export type Token = number | "+" | "-" | "*" | "/" | "(" | ")" +export type ParenthesisToken = "(" | ")" + +export type OperatorToken = "+" | "-" | "*" | "/" + +export type Token = number | OperatorToken | ParenthesisToken export type Expression = Num | Operator @@ -11,11 +15,28 @@ export type Num = { export type Operator = { type: "operator" - value: "+" | "-" | "*" | "/" + value: OperatorToken lhs: Expression rhs: Expression } +export const renderToken = (token: Token): string => { + switch (token) { + case "*": + return "×" + case "-": + return "−" + default: + return token.toString() + } +} + +export const isOperator = (token?: Token): token is OperatorToken => + token === "+" || token === "-" || token === "*" || token === "/" + +export const isNumber = (token?: Token): token is number => + typeof token === "number" + export const calculate = (tokens: Token[]): number => { const expr = parse(tokens) return calculateExpr(expr) From f8c7b1c2e5d5b8d030ec302332d0d6f7a9f426c7 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:32:10 +0900 Subject: [PATCH 16/18] =?UTF-8?q?chore:=20Lint=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=82=92=E8=A7=A3=E6=B1=BA=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81?= =?UTF-8?q?=E3=81=AB=E3=80=81TypeScript=E3=81=A7=E3=81=AFreact/prop-types?= =?UTF-8?q?=E3=83=AB=E3=83=BC=E3=83=AB=E3=82=92=E7=84=A1=E5=8A=B9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.cjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 62bbda1..5f5f4e9 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -71,6 +71,9 @@ module.exports = { "plugin:import/recommended", "plugin:import/typescript", ], + rules: { + "react/prop-types": "off", + }, }, // Node From 67ff14a077278bce074fedc1475db857eb185455 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:33:19 +0900 Subject: [PATCH 17/18] refactor: remove dead code --- app/components/organisms/reception/Calculator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/organisms/reception/Calculator.tsx b/app/components/organisms/reception/Calculator.tsx index 05c9b15..b91cc26 100644 --- a/app/components/organisms/reception/Calculator.tsx +++ b/app/components/organisms/reception/Calculator.tsx @@ -8,7 +8,7 @@ export type CalculatorProps = { } export const Calculator: FC = memo(({ total }) => { - const { onInput, clear, tokens, input, calculate, pushToken } = useTokens() + const { onInput, clear, tokens, input, calculate } = useTokens() return ( Date: Wed, 20 Nov 2024 00:19:31 +0900 Subject: [PATCH 18/18] =?UTF-8?q?refactor:=20=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E5=90=8D=E3=82=92=E3=82=88=E3=82=8A=E4=B8=80=E8=88=AC?= =?UTF-8?q?=E7=9A=84=E3=81=AA=E5=90=8D=E7=A7=B0=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/organisms/reception/Calculator.tsx | 6 +++--- app/hooks/{useToken.ts => useCalculator.ts} | 4 ++-- app/lib/{calculate.ts => calculator.ts} | 0 3 files changed, 5 insertions(+), 5 deletions(-) rename app/hooks/{useToken.ts => useCalculator.ts} (95%) rename app/lib/{calculate.ts => calculator.ts} (100%) diff --git a/app/components/organisms/reception/Calculator.tsx b/app/components/organisms/reception/Calculator.tsx index b91cc26..499551e 100644 --- a/app/components/organisms/reception/Calculator.tsx +++ b/app/components/organisms/reception/Calculator.tsx @@ -1,14 +1,14 @@ import { Button, Grid, Text, Tooltip, VStack } from "@chakra-ui/react" import { FC, memo } from "react" -import { useTokens } from "~/hooks/useToken" -import { renderToken } from "~/lib/calculate" +import { useCalculator } from "~/hooks/useCalculator" +import { renderToken } from "~/lib/calculator" export type CalculatorProps = { total: number } export const Calculator: FC = memo(({ total }) => { - const { onInput, clear, tokens, input, calculate } = useTokens() + const { onInput, clear, tokens, input, calculate } = useCalculator() return ( { +export const useCalculator = () => { const [tokens, setTokens] = useState([]) const [input, setInput] = useState(0) diff --git a/app/lib/calculate.ts b/app/lib/calculator.ts similarity index 100% rename from app/lib/calculate.ts rename to app/lib/calculator.ts