diff options
Diffstat (limited to 'components/bidding/manage/create-pre-quote-rfq-dialog.tsx')
| -rw-r--r-- | components/bidding/manage/create-pre-quote-rfq-dialog.tsx | 421 |
1 files changed, 184 insertions, 237 deletions
diff --git a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx index cdcf1ef1..de3c19ff 100644 --- a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx +++ b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx @@ -46,12 +46,14 @@ import { cn } from "@/lib/utils" import { toast } from "sonner"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Separator } from "@/components/ui/separator"
-import { createPreQuoteRfqAction, previewGeneralRfqCode } from "@/lib/bidding/service"
+import { createPreQuoteRfqAction } from "@/lib/bidding/pre-quote/service"
+import { previewGeneralRfqCode } from "@/lib/rfq-last/service"
import { MaterialGroupSelectorDialogSingle } from "@/components/common/material/material-group-selector-dialog-single"
import { MaterialSearchItem } from "@/lib/material/material-group-service"
import { MaterialSelectorDialogSingle } from "@/components/common/selectors/material/material-selector-dialog-single"
import { MaterialSearchItem as SAPMaterialSearchItem } from "@/components/common/selectors/material/material-service"
import { PurchaseGroupCodeSelector } from "@/components/common/selectors/purchase-group-code/purchase-group-code-selector"
+import type { PurchaseGroupCodeWithUser } from "@/components/common/selectors/purchase-group-code"
import { getBiddingById } from "@/lib/bidding/service"
// 아이템 스키마
@@ -67,14 +69,17 @@ const itemSchema = z.object({ // 사전견적용 일반견적 생성 폼 스키마
const createPreQuoteRfqSchema = z.object({
- rfqType: z.string().min(1, "견적 종류를 선택해주세요"),
+ rfqType: z.string().optional(),
rfqTitle: z.string().min(1, "견적명을 입력해주세요"),
dueDate: z.date({
required_error: "제출마감일을 선택해주세요",
- }),
+ }).optional(), // 필수값 해제
picUserId: z.number().optional(),
projectId: z.number().optional(),
remark: z.string().optional(),
+ biddingNumber: z.string().optional(), // 입찰 No. 추가
+ contractStartDate: z.date().optional(), // 계약기간 시작
+ contractEndDate: z.date().optional(), // 계약기간 종료
items: z.array(itemSchema).min(1, "최소 하나의 자재를 추가해주세요"),
})
@@ -122,7 +127,7 @@ export function CreatePreQuoteRfqDialog({ const [isLoading, setIsLoading] = React.useState(false)
const [previewCode, setPreviewCode] = React.useState("")
const [isLoadingPreview, setIsLoadingPreview] = React.useState(false)
- const [selectedBidPic, setSelectedBidPic] = React.useState<any | undefined>(undefined)
+ const [selectedBidPic, setSelectedBidPic] = React.useState<PurchaseGroupCodeWithUser | undefined>(undefined)
const { data: session } = useSession()
const userId = React.useMemo(() => {
@@ -165,24 +170,31 @@ export function CreatePreQuoteRfqDialog({ },
})
+ /*
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "items",
})
+ */
- // 입찰담당자 정보 로드
+ // 견적담당자 정보 로드
React.useEffect(() => {
const loadBiddingInfo = async () => {
if (!biddingId || !open) return
try {
const bidding = await getBiddingById(biddingId)
- if (bidding && bidding.bidPicId) {
- // 입찰담당자 정보를 로드하는 로직 추가 필요
- // 현재는 임시로 bidPicId를 사용
+ if (bidding) {
setSelectedBidPic({
- USER_ID: bidding.bidPicId,
- DISPLAY_NAME: bidding.bidPicName || '입찰담당자'
+ DISPLAY_NAME: bidding.bidPicName || '',
+ PURCHASE_GROUP_CODE: bidding.bidPicCode || '',
+ EMPLOYEE_NUMBER: '',
+ user: bidding.bidPicId ? {
+ id: bidding.bidPicId,
+ name: bidding.bidPicName || '',
+ email: '',
+ employeeNumber: null
+ } : undefined
})
}
} catch (error) {
@@ -192,17 +204,58 @@ export function CreatePreQuoteRfqDialog({ loadBiddingInfo()
}, [biddingId, open])
+
+ // 프로젝트 정보 상태 추가
+ const [projectInfo, setProjectInfo] = React.useState<string>("")
// 다이얼로그가 열릴 때 폼 초기화
React.useEffect(() => {
if (open) {
+ // 입찰 정보를 기반으로 기본값 설정
+ let rfqTitle = "";
+ let projectId: number | undefined = undefined;
+ let contractStartDate: Date | undefined = undefined;
+ let contractEndDate: Date | undefined = undefined;
+ let biddingNumber = "";
+
+ const loadDetailedBiddingInfo = async () => {
+ if (biddingId) {
+ try {
+ const bidding = await getBiddingById(biddingId)
+ if (bidding) {
+ rfqTitle = bidding.title;
+ biddingNumber = bidding.biddingNumber;
+
+ // 프로젝트 정보 설정
+ const pCode = bidding.projectCode || "";
+ const pName = bidding.projectName || "";
+ setProjectInfo(pCode && pName ? `${pCode} - ${pName}` : pCode || pName || "");
+
+ // 폼 값 설정
+ form.setValue("rfqTitle", rfqTitle);
+ form.setValue("rfqType", "pre_bidding"); // 기본값 설정
+ if (biddingNumber) form.setValue("biddingNumber", biddingNumber);
+
+ if (bidding.contractStartDate) form.setValue("contractStartDate", new Date(bidding.contractStartDate));
+ if (bidding.contractEndDate) form.setValue("contractEndDate", new Date(bidding.contractEndDate));
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ };
+ loadDetailedBiddingInfo();
+
form.reset({
- rfqType: "",
+ rfqType: "pre_bidding", // 기본값
rfqTitle: "",
- dueDate: undefined,
- picUserId: selectedBidPic?.USER_ID,
+ dueDate: undefined, // 필수값 해제되었으므로 undefined 가능
+ picUserId: selectedBidPic?.user?.id,
projectId: undefined,
remark: "",
+ biddingNumber: "",
+ contractStartDate: undefined,
+ contractEndDate: undefined,
items: initialItems.length > 0 ? initialItems : [
{
itemCode: "",
@@ -217,11 +270,11 @@ export function CreatePreQuoteRfqDialog({ })
setPreviewCode("")
}
- }, [open, initialItems, form, selectedBidPic])
+ }, [open, initialItems, form, selectedBidPic, biddingId])
// 견적담당자 선택 시 RFQ 코드 미리보기 생성
React.useEffect(() => {
- if (!selectedBidPic?.USER_ID) {
+ if (!selectedBidPic?.user?.id) {
setPreviewCode("")
return
}
@@ -230,7 +283,7 @@ export function CreatePreQuoteRfqDialog({ (async () => {
setIsLoadingPreview(true)
try {
- const code = await previewGeneralRfqCode(selectedBidPic.USER_ID)
+ const code = await previewGeneralRfqCode(selectedBidPic.user!.id)
setPreviewCode(code)
} catch (error) {
console.error("코드 미리보기 오류:", error)
@@ -277,25 +330,26 @@ export function CreatePreQuoteRfqDialog({ return
}
- if (!selectedBidPic?.USER_ID) {
- toast.error("입찰담당자를 선택해주세요")
- return
- }
- const picUserId = selectedBidPic.USER_ID
+
+ const picUserId = selectedBidPic?.user?.id || session?.user?.id
setIsLoading(true)
try {
// 서버 액션 호출 (입찰 조건 포함)
const result = await createPreQuoteRfqAction({
+ // biddingId, // createPreQuoteRfqAction 인터페이스 변경됨
biddingId,
- rfqType: data.rfqType,
+ rfqType: data.rfqType || "pre_bidding",
rfqTitle: data.rfqTitle,
- dueDate: data.dueDate,
+ dueDate: data.dueDate ? new Date(data.dueDate) : undefined, // optional이지만 submit시에는 값이 있을 수 있음 (없으면 서비스에서 처리)
picUserId,
projectId: data.projectId,
remark: data.remark || "",
+ biddingNumber: data.biddingNumber, // 추가
+ contractStartDate: data.contractStartDate, // 추가
+ contractEndDate: data.contractEndDate, // 추가
items: data.items as Array<{
itemCode: string;
itemName: string;
@@ -336,7 +390,8 @@ export function CreatePreQuoteRfqDialog({ }
}
- // 아이템 추가
+ // 아이템 추가 (사용안함)
+ /*
const handleAddItem = () => {
append({
itemCode: "",
@@ -348,6 +403,7 @@ export function CreatePreQuoteRfqDialog({ remark: "",
})
}
+ */
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -371,7 +427,7 @@ export function CreatePreQuoteRfqDialog({ <div className="grid grid-cols-2 gap-4">
{/* 견적 종류 */}
- <div className="space-y-2">
+ {/* <div className="space-y-2">
<FormField
control={form.control}
name="rfqType"
@@ -380,23 +436,14 @@ export function CreatePreQuoteRfqDialog({ <FormLabel>
견적 종류 <span className="text-red-500">*</span>
</FormLabel>
- <Select onValueChange={handleRfqTypeChange} value={field.value}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="견적 종류 선택" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- <SelectItem value="단가계약">단가계약</SelectItem>
- <SelectItem value="매각계약">매각계약</SelectItem>
- <SelectItem value="일반계약">일반계약</SelectItem>
- </SelectContent>
- </Select>
+ <FormControl>
+ <Input {...field} value="사전견적(입찰)" readOnly className="bg-muted" />
+ </FormControl>
<FormMessage />
</FormItem>
)}
/>
- </div>
+ </div> */}
{/* 제출마감일 */}
<FormField
@@ -405,7 +452,7 @@ export function CreatePreQuoteRfqDialog({ render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>
- 제출마감일 <span className="text-red-500">*</span>
+ 제출마감일
</FormLabel>
<Popover>
<PopoverTrigger asChild>
@@ -420,7 +467,7 @@ export function CreatePreQuoteRfqDialog({ {field.value ? (
format(field.value, "yyyy-MM-dd")
) : (
- <span>제출마감일을 선택하세요</span>
+ <span>제출마감일을 선택하세요 (선택)</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
@@ -468,23 +515,24 @@ export function CreatePreQuoteRfqDialog({ />
{/* 프로젝트 선택 */}
- <FormField
- control={form.control}
- name="projectId"
- render={({ field }) => (
+ <div className="space-y-2">
<FormItem className="flex flex-col">
<FormLabel>프로젝트</FormLabel>
<FormControl>
- {/* ProjectSelector는 별도 컴포넌트 필요 */}
<Input
- placeholder="프로젝트 ID (선택사항)"
- type="number"
- {...field}
- onChange={(e) => field.onChange(e.target.value ? Number(e.target.value) : undefined)}
+ value={projectInfo}
+ readOnly
+ className="bg-muted"
+ placeholder="프로젝트 정보 없음"
/>
</FormControl>
- <FormMessage />
</FormItem>
+ </div>
+ <FormField
+ control={form.control}
+ name="projectId"
+ render={({ field }) => (
+ <input type="hidden" {...field} value={field.value || ''} />
)}
/>
@@ -502,7 +550,7 @@ export function CreatePreQuoteRfqDialog({ selectedCode={selectedBidPic}
onCodeSelect={(code) => {
setSelectedBidPic(code)
- field.onChange(code.USER_ID)
+ field.onChange(code.user?.id)
}}
placeholder="입찰담당자 선택"
/>
@@ -526,6 +574,87 @@ export function CreatePreQuoteRfqDialog({ </div>
)}
+ {/* 계약기간 */}
+ <div className="grid grid-cols-2 gap-4">
+ <FormField
+ control={form.control}
+ name="contractStartDate"
+ 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 ? (
+ format(field.value, "yyyy-MM-dd")
+ ) : (
+ <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}
+ initialFocus
+ />
+ </PopoverContent>
+ </Popover>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="contractEndDate"
+ 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 ? (
+ format(field.value, "yyyy-MM-dd")
+ ) : (
+ <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}
+ initialFocus
+ />
+ </PopoverContent>
+ </Popover>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
{/* 비고 */}
<FormField
control={form.control}
@@ -549,192 +678,10 @@ export function CreatePreQuoteRfqDialog({ <Separator />
- {/* 아이템 정보 섹션 */}
- <div className="space-y-4">
- <div className="flex items-center justify-between">
- <h3 className="text-lg font-semibold">자재 정보</h3>
- <Button
- type="button"
- variant="outline"
- size="sm"
- onClick={handleAddItem}
- >
- <PlusCircle className="mr-2 h-4 w-4" />
- 자재 추가
- </Button>
- </div>
-
- <div className="space-y-3">
- {fields.map((field, index) => (
- <div key={field.id} className="border rounded-lg p-3 bg-gray-50/50">
- <div className="flex items-center justify-between mb-3">
- <span className="text-sm font-medium text-gray-700">
- 자재 #{index + 1}
- </span>
- {fields.length > 1 && (
- <Button
- type="button"
- variant="ghost"
- size="sm"
- onClick={() => remove(index)}
- className="h-6 w-6 p-0 text-destructive hover:text-destructive"
- >
- <Trash2 className="h-3 w-3" />
- </Button>
- )}
- </div>
-
- {/* 자재그룹 선택 */}
- <div className="mb-3">
- <FormLabel className="text-xs">
- 자재그룹(자재그룹명) <span className="text-red-500">*</span>
- </FormLabel>
- <div className="mt-1">
- <MaterialGroupSelectorDialogSingle
- triggerLabel="자재그룹 선택"
- selectedMaterial={(() => {
- const itemCode = form.watch(`items.${index}.itemCode`);
- const itemName = form.watch(`items.${index}.itemName`);
- if (itemCode && itemName) {
- return {
- materialGroupCode: itemCode,
- materialGroupDescription: itemName,
- displayText: `${itemCode} - ${itemName}`
- } as MaterialSearchItem;
- }
- return null;
- })()}
- onMaterialSelect={(material) => {
- form.setValue(`items.${index}.itemCode`, material?.materialGroupCode || '');
- form.setValue(`items.${index}.itemName`, material?.materialGroupDescription || '');
- }}
- placeholder="자재그룹을 검색하세요..."
- title="자재그룹 선택"
- description="원하는 자재그룹을 검색하고 선택해주세요."
- triggerVariant="outline"
- />
- </div>
- </div>
-
- {/* 자재코드 선택 */}
- <div className="mb-3">
- <FormLabel className="text-xs">
- 자재코드(자재명)
- </FormLabel>
- <div className="mt-1">
- <MaterialSelectorDialogSingle
- triggerLabel="자재코드 선택"
- selectedMaterial={(() => {
- const materialCode = form.watch(`items.${index}.materialCode`);
- const materialName = form.watch(`items.${index}.materialName`);
- if (materialCode && materialName) {
- return {
- materialCode: materialCode,
- materialName: materialName,
- displayText: `${materialCode} - ${materialName}`
- } as SAPMaterialSearchItem;
- }
- return null;
- })()}
- onMaterialSelect={(material) => {
- form.setValue(`items.${index}.materialCode`, material?.materialCode || '');
- form.setValue(`items.${index}.materialName`, material?.materialName || '');
- }}
- placeholder="자재코드를 검색하세요..."
- title="자재코드 선택"
- description="원하는 자재코드를 검색하고 선택해주세요."
- triggerVariant="outline"
- />
- </div>
- </div>
-
- <div className="grid grid-cols-2 gap-3">
- {/* 수량 */}
- <FormField
- control={form.control}
- name={`items.${index}.quantity`}
- render={({ field }) => (
- <FormItem>
- <FormLabel className="text-xs">
- 수량 <span className="text-red-500">*</span>
- </FormLabel>
- <FormControl>
- <Input
- type="number"
- min="1"
- placeholder="1"
- className="h-8 text-sm"
- {...field}
- onChange={(e) => field.onChange(Number(e.target.value))}
- />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- {/* 단위 */}
- <FormField
- control={form.control}
- name={`items.${index}.uom`}
- render={({ field }) => (
- <FormItem>
- <FormLabel className="text-xs">
- 단위 <span className="text-red-500">*</span>
- </FormLabel>
- <Select onValueChange={field.onChange} value={field.value}>
- <FormControl>
- <SelectTrigger className="h-8 text-sm">
- <SelectValue placeholder="단위 선택" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- <SelectItem value="EA">EA (Each)</SelectItem>
- <SelectItem value="KG">KG (Kilogram)</SelectItem>
- <SelectItem value="M">M (Meter)</SelectItem>
- <SelectItem value="L">L (Liter)</SelectItem>
- <SelectItem value="PC">PC (Piece)</SelectItem>
- <SelectItem value="BOX">BOX (Box)</SelectItem>
- <SelectItem value="SET">SET (Set)</SelectItem>
- <SelectItem value="LOT">LOT (Lot)</SelectItem>
- <SelectItem value="PCS">PCS (Pieces)</SelectItem>
- <SelectItem value="TON">TON (Ton)</SelectItem>
- <SelectItem value="G">G (Gram)</SelectItem>
- <SelectItem value="ML">ML (Milliliter)</SelectItem>
- <SelectItem value="CM">CM (Centimeter)</SelectItem>
- <SelectItem value="MM">MM (Millimeter)</SelectItem>
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
- </div>
-
- {/* 비고 */}
- <div className="mt-3">
- <FormField
- control={form.control}
- name={`items.${index}.remark`}
- render={({ field }) => (
- <FormItem>
- <FormLabel className="text-xs">비고</FormLabel>
- <FormControl>
- <Input
- placeholder="자재별 비고사항"
- className="h-8 text-sm"
- {...field}
- />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- </div>
- </div>
- ))}
- </div>
- </div>
+ {/* 아이템 정보 섹션 (자동 매핑되므로 UI 제거) */}
+ {/* <div className="space-y-4">
+ ...
+ </div> */}
</form>
</Form>
</ScrollArea>
|
