diff options
Diffstat (limited to 'lib/b-rfq/initial/add-initial-rfq-dialog.tsx')
| -rw-r--r-- | lib/b-rfq/initial/add-initial-rfq-dialog.tsx | 326 |
1 files changed, 188 insertions, 138 deletions
diff --git a/lib/b-rfq/initial/add-initial-rfq-dialog.tsx b/lib/b-rfq/initial/add-initial-rfq-dialog.tsx index d0924be2..58a091ac 100644 --- a/lib/b-rfq/initial/add-initial-rfq-dialog.tsx +++ b/lib/b-rfq/initial/add-initial-rfq-dialog.tsx @@ -1,4 +1,3 @@ -// add-initial-rfq-dialog.tsx "use client" import * as React from "react" @@ -45,6 +44,7 @@ import { Checkbox } from "@/components/ui/checkbox" import { cn, formatDate } from "@/lib/utils" import { addInitialRfqRecord, getIncotermsForSelection, getVendorsForSelection } from "../service" import { Calendar } from "@/components/ui/calendar" +import { InitialRfqDetailView } from "@/db/schema" // Initial RFQ 추가 폼 스키마 const addInitialRfqSchema = z.object({ @@ -70,22 +70,30 @@ const addInitialRfqSchema = z.object({ returnRevision: z.number().default(0), }) -type AddInitialRfqFormData = z.infer<typeof addInitialRfqSchema> +export type AddInitialRfqFormData = z.infer<typeof addInitialRfqSchema> interface Vendor { id: number vendorName: string vendorCode: string country: string + taxId: string status: string } +interface Incoterm { + id: number + code: string + description: string +} + interface AddInitialRfqDialogProps { rfqId: number onSuccess?: () => void + defaultValues?: InitialRfqDetailView // 선택된 항목의 기본값 } -export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogProps) { +export function AddInitialRfqDialog({ rfqId, onSuccess, defaultValues }: AddInitialRfqDialogProps) { const [open, setOpen] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false) const [vendors, setVendors] = React.useState<Vendor[]>([]) @@ -95,16 +103,38 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro const [incotermsLoading, setIncotermsLoading] = React.useState(false) const [incotermsSearchOpen, setIncotermsSearchOpen] = React.useState(false) - const form = useForm<AddInitialRfqFormData>({ - resolver: zodResolver(addInitialRfqSchema), - defaultValues: { + // 기본값 설정 (선택된 항목이 있으면 해당 값 사용, 없으면 일반 기본값) + const getDefaultFormValues = React.useCallback((): Partial<AddInitialRfqFormData> => { + if (defaultValues) { + return { + vendorId: defaultValues.vendorId, + initialRfqStatus: "DRAFT", // 새로 추가할 때는 항상 DRAFT로 시작 + dueDate: defaultValues.dueDate || new Date(), + validDate: defaultValues.validDate, + incotermsCode: defaultValues.incotermsCode || "", + classification: defaultValues.classification || "", + sparepart: defaultValues.sparepart || "", + shortList: false, // 새로 추가할 때는 기본적으로 false + returnYn: false, + cpRequestYn: defaultValues.cpRequestYn || false, + prjectGtcYn: defaultValues.prjectGtcYn || false, + returnRevision: 0, + } + } + + return { initialRfqStatus: "DRAFT", shortList: false, returnYn: false, cpRequestYn: false, prjectGtcYn: false, returnRevision: 0, - }, + } + }, [defaultValues]) + + const form = useForm<AddInitialRfqFormData>({ + resolver: zodResolver(addInitialRfqSchema), + defaultValues: getDefaultFormValues(), }) // 벤더 목록 로드 @@ -121,23 +151,27 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro } }, []) - // Incoterms 목록 로드 - const loadIncoterms = React.useCallback(async () => { - setIncotermsLoading(true) - try { - const incotermsList = await getIncotermsForSelection() - setIncoterms(incotermsList) - } catch (error) { - console.error("Failed to load incoterms:", error) - toast.error("Incoterms 목록을 불러오는데 실패했습니다.") - } finally { - setIncotermsLoading(false) - } - }, []) + // Incoterms 목록 로드 + const loadIncoterms = React.useCallback(async () => { + setIncotermsLoading(true) + try { + const incotermsList = await getIncotermsForSelection() + setIncoterms(incotermsList) + } catch (error) { + console.error("Failed to load incoterms:", error) + toast.error("Incoterms 목록을 불러오는데 실패했습니다.") + } finally { + setIncotermsLoading(false) + } + }, []) - // 다이얼로그 열릴 때 벤더 목록 로드 + // 다이얼로그 열릴 때 실행 React.useEffect(() => { if (open) { + // 폼을 기본값으로 리셋 + form.reset(getDefaultFormValues()) + + // 데이터 로드 if (vendors.length === 0) { loadVendors() } @@ -145,12 +179,12 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro loadIncoterms() } } - }, [open, vendors.length, incoterms.length, loadVendors, loadIncoterms]) + }, [open, vendors.length, incoterms.length, loadVendors, loadIncoterms, form, getDefaultFormValues]) // 다이얼로그 닫기 핸들러 const handleOpenChange = (newOpen: boolean) => { if (!newOpen && !isSubmitting) { - form.reset() + form.reset(getDefaultFormValues()) } setOpen(newOpen) } @@ -167,7 +201,7 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro if (result.success) { toast.success(result.message || "초기 RFQ가 성공적으로 추가되었습니다.") - form.reset() + form.reset(getDefaultFormValues()) handleOpenChange(false) onSuccess?.() } else { @@ -186,20 +220,32 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro const selectedVendor = vendors.find(vendor => vendor.id === form.watch("vendorId")) const selectedIncoterm = incoterms.find(incoterm => incoterm.code === form.watch("incotermsCode")) + // 기본값이 있을 때 버튼 텍스트 변경 + const buttonText = defaultValues ? "유사 항목 추가" : "초기 RFQ 추가" + const dialogTitle = defaultValues ? "유사 초기 RFQ 추가" : "초기 RFQ 추가" + const dialogDescription = defaultValues + ? "선택된 항목을 기본값으로 하여 새로운 초기 RFQ를 추가합니다." + : "새로운 벤더를 대상으로 하는 초기 RFQ를 추가합니다." + return ( <Dialog open={open} onOpenChange={handleOpenChange}> <DialogTrigger asChild> <Button variant="outline" size="sm" className="gap-2"> <Plus className="size-4" aria-hidden="true" /> - <span className="hidden sm:inline">초기 RFQ 추가</span> + <span className="hidden sm:inline">{buttonText}</span> </Button> </DialogTrigger> <DialogContent className="sm:max-w-[600px] max-h-[80vh] overflow-y-auto"> <DialogHeader> - <DialogTitle>초기 RFQ 추가</DialogTitle> + <DialogTitle>{dialogTitle}</DialogTitle> <DialogDescription> - 새로운 벤더를 대상으로 하는 초기 RFQ를 추가합니다. + {dialogDescription} + {defaultValues && ( + <div className="mt-2 p-2 bg-muted rounded-md text-sm"> + <strong>기본값 출처:</strong> {defaultValues.vendorName} ({defaultValues.vendorCode}) + </div> + )} </DialogDescription> </DialogHeader> @@ -263,7 +309,7 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro {vendor.vendorName} </div> <div className="text-sm text-muted-foreground"> - {vendor.vendorCode} • {vendor.country} + {vendor.vendorCode} • {vendor.country} • {vendor.taxId} </div> </div> <Check @@ -287,98 +333,98 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro {/* 날짜 필드들 */} <div className="grid grid-cols-2 gap-4"> - <FormField - control={form.control} - name="dueDate" - render={({ field }) => ( - <FormItem className="flex flex-col"> - <FormLabel>견적 마감일</FormLabel> - <Popover> - <PopoverTrigger asChild> - <FormControl> - <Button - variant="outline" - className={cn( - "w-full pl-3 text-left font-normal", - !field.value && "text-muted-foreground" - )} - > - {field.value ? ( - formatDate(field.value,"KR") - ) : ( - <span>견적 유효일을 선택하세요</span> - )} - <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> - </Button> - </FormControl> - </PopoverTrigger> - <PopoverContent className="w-auto p-0" align="start"> - <Calendar - mode="single" - selected={field.value} - onSelect={field.onChange} - disabled={(date) => - date < new Date() || date < new Date("1900-01-01") - } - initialFocus - /> - </PopoverContent> - </Popover> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name="validDate" - render={({ field }) => ( - <FormItem className="flex flex-col"> - <FormLabel>견적 유효일</FormLabel> - <Popover> - <PopoverTrigger asChild> - <FormControl> - <Button - variant="outline" - className={cn( - "w-full pl-3 text-left font-normal", - !field.value && "text-muted-foreground" - )} - > - {field.value ? ( - formatDate(field.value,"KR") - ) : ( - <span>견적 유효일을 선택하세요</span> - )} - <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> - </Button> - </FormControl> - </PopoverTrigger> - <PopoverContent className="w-auto p-0" align="start"> - <Calendar - mode="single" - selected={field.value} - onSelect={field.onChange} - disabled={(date) => - date < new Date() || date < new Date("1900-01-01") - } - initialFocus - /> - </PopoverContent> - </Popover> - <FormMessage /> - </FormItem> - )} - /> + <FormField + control={form.control} + name="dueDate" + render={({ field }) => ( + <FormItem className="flex flex-col"> + <FormLabel>견적 마감일 *</FormLabel> + <Popover> + <PopoverTrigger asChild> + <FormControl> + <Button + variant="outline" + className={cn( + "w-full pl-3 text-left font-normal", + !field.value && "text-muted-foreground" + )} + > + {field.value ? ( + formatDate(field.value, "KR") + ) : ( + <span>견적 마감일을 선택하세요</span> + )} + <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> + </Button> + </FormControl> + </PopoverTrigger> + <PopoverContent className="w-auto p-0" align="start"> + <Calendar + mode="single" + selected={field.value} + onSelect={field.onChange} + disabled={(date) => + date < new Date() || date < new Date("1900-01-01") + } + initialFocus + /> + </PopoverContent> + </Popover> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="validDate" + render={({ field }) => ( + <FormItem className="flex flex-col"> + <FormLabel>견적 유효일</FormLabel> + <Popover> + <PopoverTrigger asChild> + <FormControl> + <Button + variant="outline" + className={cn( + "w-full pl-3 text-left font-normal", + !field.value && "text-muted-foreground" + )} + > + {field.value ? ( + formatDate(field.value, "KR") + ) : ( + <span>견적 유효일을 선택하세요</span> + )} + <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> + </Button> + </FormControl> + </PopoverTrigger> + <PopoverContent className="w-auto p-0" align="start"> + <Calendar + mode="single" + selected={field.value} + onSelect={field.onChange} + disabled={(date) => + date < new Date() || date < new Date("1900-01-01") + } + initialFocus + /> + </PopoverContent> + </Popover> + <FormMessage /> + </FormItem> + )} + /> </div> - {/* Incoterms 및 GTC */} - <div className="grid grid-cols-2 gap-4"> + {/* Incoterms 선택 */} <FormField control={form.control} - name="vendorId" + name="incotermsCode" render={({ field }) => ( <FormItem className="flex flex-col"> - <FormLabel>Incoterms *</FormLabel> + <FormLabel>Incoterms</FormLabel> <Popover open={incotermsSearchOpen} onOpenChange={setIncotermsSearchOpen}> <PopoverTrigger asChild> <FormControl> @@ -391,9 +437,8 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro > {selectedIncoterm ? ( <div className="flex items-center gap-2"> - <Building className="h-4 w-4" /> <span className="truncate"> - {selectedIncoterm.code} ({selectedIncoterm.description}) + {selectedIncoterm.code} - {selectedIncoterm.description} </span> </div> ) : ( @@ -419,18 +464,20 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro key={incoterm.id} value={`${incoterm.code} ${incoterm.description}`} onSelect={() => { - field.onChange(vendor.id) - setVendorSearchOpen(false) + field.onChange(incoterm.code) + setIncotermsSearchOpen(false) }} > <div className="flex items-center gap-2 w-full"> + <div className="flex-1 min-w-0"> <div className="font-medium truncate"> - {incoterm.code} {incoterm.description} + {incoterm.code} - {incoterm.description} </div> + </div> <Check className={cn( "ml-auto h-4 w-4", - incoterm.id === field.value ? "opacity-100" : "opacity-0" + incoterm.code === field.value ? "opacity-100" : "opacity-0" )} /> </div> @@ -445,34 +492,41 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro </FormItem> )} /> - </div> - {/* GTC 정보 */} + {/* 옵션 체크박스 */} <div className="grid grid-cols-2 gap-4"> <FormField control={form.control} - name="gtc" + name="cpRequestYn" render={({ field }) => ( - <FormItem> - <FormLabel>GTC</FormLabel> + <FormItem className="flex flex-row items-start space-x-3 space-y-0"> <FormControl> - <Input placeholder="GTC 정보" {...field} /> + <Checkbox + checked={field.value} + onCheckedChange={field.onChange} + /> </FormControl> - <FormMessage /> + <div className="space-y-1 leading-none ml-2"> + <FormLabel>CP 요청</FormLabel> + </div> </FormItem> )} /> <FormField control={form.control} - name="gtcValidDate" + name="prjectGtcYn" render={({ field }) => ( - <FormItem> - <FormLabel>GTC 유효일</FormLabel> + <FormItem className="flex flex-row items-start space-x-3 space-y-0"> <FormControl> - <Input placeholder="GTC 유효일" {...field} /> + <Checkbox + checked={field.value} + onCheckedChange={field.onChange} + /> </FormControl> - <FormMessage /> + <div className="space-y-1 leading-none ml-2"> + <FormLabel>Project용 GTC 사용</FormLabel> + </div> </FormItem> )} /> @@ -501,7 +555,7 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro <FormItem> <FormLabel>Spare part</FormLabel> <FormControl> - <Input placeholder="예비부품 정보" {...field} /> + <Input placeholder="O1, O2" {...field} /> </FormControl> <FormMessage /> </FormItem> @@ -509,8 +563,6 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro /> </div> - - <DialogFooter> <Button type="button" @@ -529,6 +581,4 @@ export function AddInitialRfqDialog({ rfqId, onSuccess }: AddInitialRfqDialogPro </DialogContent> </Dialog> ) -} - - +}
\ No newline at end of file |
