refactor: extract component

This commit is contained in:
monoid 2024-05-03 01:25:03 +09:00
parent 2aa123ef98
commit 9ca305de9e
6 changed files with 275 additions and 254 deletions

View File

@ -0,0 +1,89 @@
import { Button } from "@/components/ui/button";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { Input } from "@/components/ui/input";
import { useAddMenuItem } from "@/hooks/useMenu";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { MenuItemColumns } from "src/db";
import { MenuInput } from "./MenuInput.tsx";
export function AddMenuItem() {
const queryClient = useQueryClient();
const { mutate, isPending } = useAddMenuItem(queryClient);
const [drawerOpen, setDrawerOpen] = useState(false);
const [data, setData] = useState<MenuItemColumns>({
name: "",
category: "",
HOT: null,
COLD: null,
price: null,
});
return (
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
<DrawerTrigger asChild>
<div className="border p-4 hover:bg-accent flex items-center justify-center">
<h2 className="text-xl font-bold"> </h2>
</div>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle> </DrawerTitle>
<DrawerDescription> .</DrawerDescription>
</DrawerHeader>
<div className="m-4">
<div className="mb-2">
<div className="text-muted-foreground"> </div>
<Input
value={data.name}
onChange={(e) =>
setData({
...data,
name: e.target.value,
})}
/>
</div>
<MenuInput
data={data}
onChange={(c) => {
setData({
...data,
...c,
});
}}
/>
</div>
<DrawerFooter>
<Button
variant="default"
disabled={isPending}
onClick={() => {
mutate(data, {
onSuccess: () => {
setData({
name: "",
category: "",
HOT: null,
COLD: null,
price: null,
});
setDrawerOpen(false);
},
});
}}
>
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}

View File

@ -0,0 +1,63 @@
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { UpdateMenuItemType } from "src/schema";
import { onlyNumber } from "../../lib/onlyNumber.tsx";
export function MenuInput({
data,
onChange,
className = "",
}: {
data: UpdateMenuItemType;
onChange: (data: UpdateMenuItemType) => void;
className?: string;
}) {
return (
<div className={cn("grid gap-2", className)}>
<div>
<div className="text-muted-foreground"></div>
<Input
value={data.category}
onChange={(e) =>
onChange({
...data,
category: e.target.value,
})}
/>
</div>
<div>
<div className="text-muted-foreground">Hot </div>
<Input
value={data.HOT ? (data.HOT * 1000).toString() : ""}
onChange={(e) =>
onChange({
...data,
HOT: e.target.value ? parseInt(onlyNumber(e.target.value)) / 1000 : null,
})}
/>
</div>
<div>
<div className="text-muted-foreground">Ice </div>
<Input
value={data.COLD ? (data.COLD * 1000).toString() : ""}
onChange={(e) =>
onChange({
...data,
COLD: e.target.value ? parseInt(onlyNumber(e.target.value)) / 1000 : null,
})}
/>
</div>
<div>
<div className="text-muted-foreground"> </div>
<Input
value={data.price ? (data.price * 1000).toString() : ""}
onChange={(e) =>
onChange({
...data,
price: e.target.value ? parseInt(onlyNumber(e.target.value)) / 1000 : null,
})}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,111 @@
import { Button } from "@/components/ui/button";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { useDeleteMenuItem, useUpdateMenuItem } from "@/hooks/useMenu";
import { TrashIcon } from "@radix-ui/react-icons";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { MenuItemColumns } from "src/db";
import { UpdateMenuItemType } from "src/schema";
import { currencyFormat } from "../../lib/currencyFormat.tsx";
import { MenuInput } from "./MenuInput.tsx";
export function MenuItem({
item,
}: {
item: MenuItemColumns;
}) {
const [drawerOpen, setDrawerOpen] = useState(false);
const [deleteDrawerOpen, setDeleteDrawerOpen] = useState(false);
const queryClient = useQueryClient();
const { mutate: mutateUpdate, isPending: isUpdatePending } = useUpdateMenuItem(queryClient);
const { mutate: mutateDelete, isPending: isDeletePending } = useDeleteMenuItem(queryClient);
const [data, setData] = useState<UpdateMenuItemType>({
category: item.category,
HOT: item.HOT,
COLD: item.COLD,
price: item.price,
});
return (
<>
<Drawer open={deleteDrawerOpen} onOpenChange={setDeleteDrawerOpen}>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>{item.name} </DrawerTitle>
<DrawerDescription> .</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Button
variant="destructive"
disabled={isDeletePending}
onClick={() => {
mutateDelete(item.name, {
onSuccess: () => {
setDeleteDrawerOpen(false);
},
});
}}
>
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
<DrawerTrigger asChild>
<div className="border p-4 hover:bg-accent relative">
<Button
variant="ghost"
disabled={isDeletePending}
className="absolute top-4 right-2"
onClick={(e) => {
setDeleteDrawerOpen(true);
e.stopPropagation();
}}
>
<TrashIcon />
</Button>
<span className="text-muted-foreground font-light text-sm leading-3">{item.category}</span>
<h2 className="text-xl font-bold">{item.name}</h2>
{item.HOT && <p className="text-sm text-muted-foreground">HOT {currencyFormat(item.HOT * 1000)}</p>}
{item.COLD && <p className="text-sm text-muted-foreground">COLD {currencyFormat(item.COLD * 1000)}</p>}
{item.price && <p className="text-sm text-muted-foreground">{currencyFormat(item.price * 1000)}</p>}
</div>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>{item.name} </DrawerTitle>
<DrawerDescription> .</DrawerDescription>
</DrawerHeader>
<MenuInput className="m-4" data={data} onChange={setData} />
<DrawerFooter>
<Button
variant="default"
disabled={isUpdatePending}
onClick={() => {
mutateUpdate({
...item,
...data,
}, {
onSuccess: () => {
setDrawerOpen(false);
},
});
}}
>
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
</>
);
}

View File

@ -0,0 +1,6 @@
export function currencyFormat(price: number) {
return price.toLocaleString("ko-KR", {
style: "currency",
currency: "KRW",
});
}

View File

@ -0,0 +1,3 @@
export function onlyNumber(str: string) {
return str.replace(/[^0-9]/g, "");
}

View File

@ -1,260 +1,9 @@
import { Button } from "@/components/ui/button"; import { useMenu } from "@/hooks/useMenu";
import { import { AddMenuItem } from "../components/menu/AddMenuItem";
Drawer, import { MenuItem } from "../components/menu/MenuItem";
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { Input } from "@/components/ui/input";
import { useAddMenuItem, useDeleteMenuItem, useMenu, useUpdateMenuItem } from "@/hooks/useMenu";
import { cn } from "@/lib/utils";
import { TrashIcon } from "@radix-ui/react-icons";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { MenuItemColumns } from "src/db";
import { UpdateMenuItemType } from "src/schema";
import ErrorMessage from "./ErrorPage"; import ErrorMessage from "./ErrorPage";
import LoadingPage from "./Loading"; import LoadingPage from "./Loading";
function currencyFormat(price: number) {
return price.toLocaleString("ko-KR", {
style: "currency",
currency: "KRW",
});
}
function onlyNumber(str: string) {
return str.replace(/[^0-9]/g, "");
}
function MenuInput({
data,
onChange,
className = "",
}: {
data: UpdateMenuItemType;
onChange: (data: UpdateMenuItemType) => void;
className?: string;
}) {
return (
<div className={cn("grid gap-2", className)}>
<div>
<div className="text-muted-foreground"></div>
<Input
value={data.category}
onChange={(e) =>
onChange({
...data,
category: e.target.value,
})}
/>
</div>
<div>
<div className="text-muted-foreground">Hot </div>
<Input
value={data.HOT ? (data.HOT * 1000).toString() : ""}
onChange={(e) =>
onChange({
...data,
HOT: e.target.value ? parseInt(onlyNumber(e.target.value)) / 1000 : null,
})}
/>
</div>
<div>
<div className="text-muted-foreground">Ice </div>
<Input
value={data.COLD ? (data.COLD * 1000).toString() : ""}
onChange={(e) =>
onChange({
...data,
COLD: e.target.value ? parseInt(onlyNumber(e.target.value)) / 1000 : null,
})}
/>
</div>
<div>
<div className="text-muted-foreground"> </div>
<Input
value={data.price ? (data.price * 1000).toString() : ""}
onChange={(e) =>
onChange({
...data,
price: e.target.value ? parseInt(onlyNumber(e.target.value)) / 1000 : null,
})}
/>
</div>
</div>
);
}
function MenuItem({
item,
}: {
item: MenuItemColumns;
}) {
const [drawerOpen, setDrawerOpen] = useState(false);
const [deleteDrawerOpen, setDeleteDrawerOpen] = useState(false);
const queryClient = useQueryClient();
const { mutate: mutateUpdate, isPending: isUpdatePending } = useUpdateMenuItem(queryClient);
const { mutate: mutateDelete, isPending: isDeletePending } = useDeleteMenuItem(queryClient);
const [data, setData] = useState<UpdateMenuItemType>({
category: item.category,
HOT: item.HOT,
COLD: item.COLD,
price: item.price,
});
return (
<>
<Drawer open={deleteDrawerOpen} onOpenChange={setDeleteDrawerOpen}>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>{item.name} </DrawerTitle>
<DrawerDescription> .</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Button
variant="destructive"
disabled={isDeletePending}
onClick={() => {
mutateDelete(item.name, {
onSuccess: () => {
setDeleteDrawerOpen(false);
},
});
}}
>
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
<DrawerTrigger asChild>
<div className="border p-4 hover:bg-accent relative">
<Button
variant="ghost"
disabled={isDeletePending}
className="absolute top-4 right-2"
onClick={(e) => {
setDeleteDrawerOpen(true);
e.stopPropagation();
}}
>
<TrashIcon />
</Button>
<span className="text-muted-foreground font-light text-sm leading-3">{item.category}</span>
<h2 className="text-xl font-bold">{item.name}</h2>
{item.HOT && <p className="text-sm text-muted-foreground">HOT {currencyFormat(item.HOT * 1000)}</p>}
{item.COLD && <p className="text-sm text-muted-foreground">COLD {currencyFormat(item.COLD * 1000)}</p>}
{item.price && <p className="text-sm text-muted-foreground">{currencyFormat(item.price * 1000)}</p>}
</div>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>{item.name} </DrawerTitle>
<DrawerDescription> .</DrawerDescription>
</DrawerHeader>
<MenuInput className="m-4" data={data} onChange={setData} />
<DrawerFooter>
<Button
variant="default"
disabled={isUpdatePending}
onClick={() => {
mutateUpdate({
...item,
...data,
}, {
onSuccess: () => {
setDrawerOpen(false);
},
});
}}
>
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
</>
);
}
function AddMenuItem() {
const queryClient = useQueryClient();
const { mutate, isPending } = useAddMenuItem(queryClient);
const [drawerOpen, setDrawerOpen] = useState(false);
const [data, setData] = useState<MenuItemColumns>({
name: "",
category: "",
HOT: null,
COLD: null,
price: null,
});
return (
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
<DrawerTrigger asChild>
<div className="border p-4 hover:bg-accent flex items-center justify-center">
<h2 className="text-xl font-bold"> </h2>
</div>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle> </DrawerTitle>
<DrawerDescription> .</DrawerDescription>
</DrawerHeader>
<div className="m-4">
<div className="mb-2">
<div className="text-muted-foreground"> </div>
<Input
value={data.name}
onChange={(e) =>
setData({
...data,
name: e.target.value,
})}
/>
</div>
<MenuInput
data={data}
onChange={(c) => {
setData({
...data,
...c,
});
}}
/>
</div>
<DrawerFooter>
<Button
variant="default"
disabled={isPending}
onClick={() => {
mutate(data, {
onSuccess: () => {
setData({
name: "",
category: "",
HOT: null,
COLD: null,
price: null,
});
setDrawerOpen(false);
},
});
}}
>
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}
export default function MenuPage() { export default function MenuPage() {
const { data: menuItems, status } = useMenu(); const { data: menuItems, status } = useMenu();
if (status === "pending") { if (status === "pending") {