diff options
Diffstat (limited to 'lib/rfqs/table/update-rfq-sheet.tsx')
| -rw-r--r-- | lib/rfqs/table/update-rfq-sheet.tsx | 406 |
1 files changed, 0 insertions, 406 deletions
diff --git a/lib/rfqs/table/update-rfq-sheet.tsx b/lib/rfqs/table/update-rfq-sheet.tsx deleted file mode 100644 index 22ca2c37..00000000 --- a/lib/rfqs/table/update-rfq-sheet.tsx +++ /dev/null @@ -1,406 +0,0 @@ -"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, getBudgetaryRfqs } from "../service" -import { ProjectSelector } from "@/components/ProjectSelector" -import { type Project } from "../service" -import { ParentRfqSelector } from "./ParentRfqSelector" - -interface UpdateRfqSheetProps - extends React.ComponentPropsWithRef<typeof Sheet> { - rfq: RfqWithItemCount | null -} - -// 부모 RFQ 정보 타입 정의 -interface ParentRfq { - id: number; - rfqCode: string; - description: string | null; - rfqType: RfqType; - projectId: number | null; - projectCode: string | null; - projectName: string | null; -} - -export function UpdateRfqSheet({ rfq, ...props }: UpdateRfqSheetProps) { - const [isUpdatePending, startUpdateTransition] = React.useTransition() - const { data: session } = useSession() - const userId = Number(session?.user?.id || 1) - const [selectedParentRfq, setSelectedParentRfq] = React.useState<ParentRfq | null>(null) - - // RFQ의 타입 가져오기 - const rfqType = rfq?.rfqType || RfqType.PURCHASE; - - // 초기 부모 RFQ ID 가져오기 - const initialParentRfqId = rfq?.parentRfqId; - - // 현재 RFQ 타입에 따라 선택 가능한 부모 RFQ 타입들 결정 - const getParentRfqTypes = (): RfqType[] => { - switch(rfqType) { - case RfqType.PURCHASE: - // PURCHASE는 BUDGETARY와 PURCHASE_BUDGETARY를 부모로 가질 수 있음 - return [RfqType.BUDGETARY, RfqType.PURCHASE_BUDGETARY]; - case RfqType.PURCHASE_BUDGETARY: - // PURCHASE_BUDGETARY는 BUDGETARY만 부모로 가질 수 있음 - return [RfqType.BUDGETARY]; - default: - return []; - } - }; - - // 부모 RFQ 타입들 - const parentRfqTypes = getParentRfqTypes(); - - // 부모 RFQ를 보여줄지 결정 - const shouldShowParentRfqSelector = rfqType === RfqType.PURCHASE || rfqType === RfqType.PURCHASE_BUDGETARY; - - // 타입에 따른 타이틀 생성 - const getTypeTitle = () => { - switch(rfqType) { - case RfqType.PURCHASE: - return "Purchase RFQ"; - case RfqType.BUDGETARY: - return "Budgetary RFQ"; - case RfqType.PURCHASE_BUDGETARY: - return "Purchase Budgetary RFQ"; - default: - return "RFQ"; - } - }; - - // 타입 설명 가져오기 - const getTypeDescription = () => { - switch(rfqType) { - case RfqType.PURCHASE: - return "실제 구매 발주 전에 가격을 요청"; - case RfqType.BUDGETARY: - return "기술영업 단계에서 입찰가 산정을 위한 견적 요청"; - case RfqType.PURCHASE_BUDGETARY: - return "프로젝트 수주 후, 공식 입찰 전 예산 책정을 위한 가격 요청"; - default: - return ""; - } - }; - - // 부모 RFQ 선택기 레이블 및 설명 가져오기 - const getParentRfqSelectorLabel = () => { - if (rfqType === RfqType.PURCHASE) { - return "부모 RFQ (BUDGETARY/PURCHASE_BUDGETARY)"; - } else if (rfqType === RfqType.PURCHASE_BUDGETARY) { - return "부모 RFQ (BUDGETARY)"; - } - return "부모 RFQ"; - }; - - const getParentRfqDescription = () => { - if (rfqType === RfqType.PURCHASE) { - return "BUDGETARY 또는 PURCHASE_BUDGETARY 타입의 RFQ를 부모로 선택할 수 있습니다."; - } else if (rfqType === RfqType.PURCHASE_BUDGETARY) { - return "BUDGETARY 타입의 RFQ만 부모로 선택할 수 있습니다."; - } - return ""; - }; - - // 초기 부모 RFQ 로드 - React.useEffect(() => { - if (initialParentRfqId && shouldShowParentRfqSelector) { - const loadInitialParentRfq = async () => { - try { - const result = await getBudgetaryRfqs({ - rfqId: initialParentRfqId - }); - - if ('rfqs' in result && result.rfqs && result.rfqs.length > 0) { - setSelectedParentRfq(result.rfqs[0] as unknown as ParentRfq); - } - } catch (error) { - console.error("부모 RFQ 로드 오류:", error); - } - }; - - loadInitialParentRfq(); - } - }, [initialParentRfqId, shouldShowParentRfqSelector]); - - // 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 - parentRfqId: rfq?.parentRfqId, // 부모 RFQ ID - dueDate: rfq?.dueDate ?? undefined, // null을 undefined로 변환 - status: rfq?.status ?? "DRAFT", - createdBy: rfq?.createdBy ?? userId, - }, - }); - - // 프로젝트 선택 처리 - const handleProjectSelect = (project: Project | null) => { - if (project === null) { - return; - } - form.setValue("projectId", project.id); - }; - - // 부모 RFQ 선택 처리 - const handleParentRfqSelect = (rfq: ParentRfq | null) => { - setSelectedParentRfq(rfq); - form.setValue("parentRfqId", rfq?.id); - }; - - async function onSubmit(input: UpdateRfqSchema) { - startUpdateTransition(async () => { - if (!rfq) return - - const { error } = await modifyRfq({ - ...input, - rfqType: rfqType as RfqType, - - }) - - 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 {getTypeTitle()}</SheetTitle> - <SheetDescription> - Update the {getTypeTitle()} details and save the changes - <div className="mt-1 text-xs text-muted-foreground"> - {getTypeDescription()} - </div> - </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} /> - )} - /> - - {/* Hidden rfqType field */} - {/* <FormField - control={form.control} - name="rfqType" - 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> - )} - /> - - {/* Parent RFQ Selector - PURCHASE 또는 PURCHASE_BUDGETARY 타입일 때만 표시 */} - {shouldShowParentRfqSelector && ( - <FormField - control={form.control} - name="parentRfqId" - render={({ field }) => ( - <FormItem> - <FormLabel>{getParentRfqSelectorLabel()}</FormLabel> - <FormControl> - <ParentRfqSelector - selectedRfqId={field.value as number | undefined} - onRfqSelect={handleParentRfqSelect} - rfqType={rfqType} - parentRfqTypes={parentRfqTypes} - placeholder={ - rfqType === RfqType.PURCHASE - ? "BUDGETARY 또는 PURCHASE_BUDGETARY RFQ 선택..." - : "BUDGETARY RFQ 선택..." - } - /> - </FormControl> - <div className="text-xs text-muted-foreground mt-1"> - {getParentRfqDescription()} - </div> - <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 |
