diff options
Diffstat (limited to 'lib/rfqs/table/update-rfq-sheet.tsx')
| -rw-r--r-- | lib/rfqs/table/update-rfq-sheet.tsx | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/lib/rfqs/table/update-rfq-sheet.tsx b/lib/rfqs/table/update-rfq-sheet.tsx new file mode 100644 index 00000000..769f25e7 --- /dev/null +++ b/lib/rfqs/table/update-rfq-sheet.tsx @@ -0,0 +1,283 @@ +"use client" + +import * as React from "react" +import { zodResolver } from "@hookform/resolvers/zod" +import { Loader } from "lucide-react" +import { useForm } from "react-hook-form" +import { toast } from "sonner" +import { useSession } from "next-auth/react" + +import { Button } from "@/components/ui/button" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet" +import { Input } from "@/components/ui/input" + +import { Rfq, RfqWithItemCount } from "@/db/schema/rfq" +import { RfqType, updateRfqSchema, type UpdateRfqSchema } from "../validations" +import { modifyRfq } from "../service" +import { ProjectSelector } from "@/components/ProjectSelector" +import { type Project } from "../service" +import { BudgetaryRfqSelector } from "./BudgetaryRfqSelector" + +interface UpdateRfqSheetProps + extends React.ComponentPropsWithRef<typeof Sheet> { + rfq: RfqWithItemCount | null + rfqType?: RfqType; +} + + +interface BudgetaryRfq { + id: number; + rfqCode: string; + description: string | null; +} + + +export function UpdateRfqSheet({ rfq,rfqType = RfqType.PURCHASE , ...props }: UpdateRfqSheetProps) { + const [isUpdatePending, startUpdateTransition] = React.useTransition() + const { data: session } = useSession() + const userId = Number(session?.user?.id || 1) + const [selectedBudgetaryRfq, setSelectedBudgetaryRfq] = React.useState<BudgetaryRfq | null>(null) + + // RHF setup + const form = useForm<UpdateRfqSchema>({ + resolver: zodResolver(updateRfqSchema), + defaultValues: { + id: rfq?.rfqId ?? 0, // PK + rfqCode: rfq?.rfqCode ?? "", + description: rfq?.description ?? "", + projectId: rfq?.projectId, // 프로젝트 ID + dueDate: rfq?.dueDate ?? undefined, // null을 undefined로 변환 + status: rfq?.status ?? "DRAFT", + createdBy: rfq?.createdBy ?? userId, + }, + }); + + // 프로젝트 선택 처리 + const handleProjectSelect = (project: Project) => { + form.setValue("projectId", project.id); + }; + + async function onSubmit(input: UpdateRfqSchema) { + startUpdateTransition(async () => { + if (!rfq) return + + const { error } = await modifyRfq({ + ...input, + }) + + if (error) { + toast.error(error) + return + } + + form.reset() + props.onOpenChange?.(false) // close the sheet + toast.success("RFQ updated!") + }) + } + + return ( + <Sheet {...props}> + <SheetContent className="flex flex-col gap-6 sm:max-w-md"> + <SheetHeader className="text-left"> + <SheetTitle>Update RFQ</SheetTitle> + <SheetDescription> + Update the RFQ details and save the changes + </SheetDescription> + </SheetHeader> + + {/* RHF Form */} + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4"> + + {/* Hidden or code-based id field */} + <FormField + control={form.control} + name="id" + render={({ field }) => ( + <input type="hidden" {...field} /> + )} + /> + + {/* Project Selector - 재사용 컴포넌트 사용 */} + <FormField + control={form.control} + name="projectId" + render={({ field }) => ( + <FormItem> + <FormLabel>Project</FormLabel> + <FormControl> + <ProjectSelector + selectedProjectId={field.value} + onProjectSelect={handleProjectSelect} + placeholder="프로젝트 선택..." + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* Budgetary RFQ Selector - 구매용 RFQ 생성 시에만 표시 */} + {rfqType === RfqType.PURCHASE && ( + <FormField + control={form.control} + name="parentRfqId" + render={({ field }) => ( + <FormItem> + <FormLabel>Budgetary RFQ (Optional)</FormLabel> + <FormControl> + <BudgetaryRfqSelector + selectedRfqId={field.value as number | undefined} + onRfqSelect={(rfq) => { + setSelectedBudgetaryRfq(rfq as any); + form.setValue("parentRfqId", rfq?.id); + }} + placeholder="Budgetary RFQ 선택..." + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + )} + + + {/* rfqCode */} + <FormField + control={form.control} + name="rfqCode" + render={({ field }) => ( + <FormItem> + <FormLabel>RFQ Code</FormLabel> + <FormControl> + <Input placeholder="e.g. RFQ-2025-001" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* description */} + <FormField + control={form.control} + name="description" + render={({ field }) => ( + <FormItem> + <FormLabel>Description</FormLabel> + <FormControl> + <Input placeholder="Description" {...field} value={field.value || ""} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + + + {/* dueDate (type="date") */} + <FormField + control={form.control} + name="dueDate" + render={({ field }) => ( + <FormItem> + <FormLabel>Due Date</FormLabel> + <FormControl> + <Input + type="date" + // convert Date -> yyyy-mm-dd + value={field.value ? field.value.toISOString().slice(0, 10) : ""} + onChange={(e) => { + const val = e.target.value + field.onChange(val ? new Date(val + "T00:00:00") : undefined) + }} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* status (Select) */} + <FormField + control={form.control} + name="status" + render={({ field }) => ( + <FormItem> + <FormLabel>Status</FormLabel> + <FormControl> + <Select + onValueChange={field.onChange} + value={field.value ?? "DRAFT"} + > + <SelectTrigger className="capitalize"> + <SelectValue placeholder="Select status" /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + {["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"].map((item) => ( + <SelectItem key={item} value={item} className="capitalize"> + {item} + </SelectItem> + ))} + </SelectGroup> + </SelectContent> + </Select> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* createdBy (hidden or read-only) */} + <FormField + control={form.control} + name="createdBy" + render={({ field }) => ( + <input type="hidden" {...field} /> + )} + /> + + <SheetFooter className="gap-2 pt-2 sm:space-x-0"> + <SheetClose asChild> + <Button type="button" variant="outline"> + Cancel + </Button> + </SheetClose> + <Button disabled={isUpdatePending}> + {isUpdatePending && ( + <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" /> + )} + Save + </Button> + </SheetFooter> + </form> + </Form> + </SheetContent> + </Sheet> + ) +}
\ No newline at end of file |
