summaryrefslogtreecommitdiff
path: root/lib/b-rfq/initial/add-initial-rfq-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/b-rfq/initial/add-initial-rfq-dialog.tsx')
-rw-r--r--lib/b-rfq/initial/add-initial-rfq-dialog.tsx326
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