diff options
| author | joonhoekim <26rote@gmail.com> | 2025-12-01 19:54:27 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-12-01 19:54:27 +0900 |
| commit | 4b5880064e2362baf85c91f33b2b44baecea3a7f (patch) | |
| tree | 24b48163ecbf205023fcd565b0476a30e3079a9f /lib/rfq-last/table | |
| parent | 44b74ff4170090673b6eeacd8c528e0abf47b7aa (diff) | |
| parent | cd0ce0cbe8af8719a6f542098ec78f2a5c1222ce (diff) | |
Merge branch 'dujinkim' of https://github.com/DTS-Development/SHI_EVCP into dujinkim
Diffstat (limited to 'lib/rfq-last/table')
| -rw-r--r-- | lib/rfq-last/table/rfq-table-columns.tsx | 338 | ||||
| -rw-r--r-- | lib/rfq-last/table/rfq-table.tsx | 9 |
2 files changed, 344 insertions, 3 deletions
diff --git a/lib/rfq-last/table/rfq-table-columns.tsx b/lib/rfq-last/table/rfq-table-columns.tsx index 62f14579..58c45aa0 100644 --- a/lib/rfq-last/table/rfq-table-columns.tsx +++ b/lib/rfq-last/table/rfq-table-columns.tsx @@ -24,7 +24,7 @@ type NextRouter = ReturnType<typeof useRouter>; interface GetColumnsProps { setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<RfqsLastView> | null>>; - rfqCategory?: "general" | "itb" | "rfq"; + rfqCategory?: "general" | "itb" | "rfq" | "pre_bidding"; router: NextRouter; } @@ -756,6 +756,342 @@ export function getRfqColumns({ } // ═══════════════════════════════════════════════════════════════ + // 사전견적(입찰) 컬럼 정의 + // ═══════════════════════════════════════════════════════════════ + if (rfqCategory === "pre_bidding") { + return [ + // Checkbox + { + id: "select", + header: ({ table }) => ( + <Checkbox + checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")} + onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)} + aria-label="select all" + className="translate-y-0.5" + /> + ), + cell: ({ row }) => ( + <Checkbox + checked={row.getIsSelected()} + onCheckedChange={(v) => row.toggleSelected(!!v)} + aria-label="select row" + className="translate-y-0.5" + /> + ), + size: 40, + enableSorting: false, + enableHiding: false, + }, + + // 견적 No. + { + accessorKey: "rfqCode", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적 No." />, + cell: ({ row }) => ( + <span className="font-mono font-medium">{row.original.rfqCode}</span> + ), + size: 120, + }, + + // 입찰 No. (추가) + { + accessorKey: "biddingNumber", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰 No." />, + cell: ({ row }) => ( + <span className="font-mono font-medium">{row.original.biddingNumber || "-"}</span> + ), + size: 120, + }, + + // 상세 - 수정됨 + { + id: "detail", + header: "상세", + cell: ({ row }) => ( + <Button + variant="ghost" + size="icon" + className="h-8 w-8" + onClick={() => router.push(`/evcp/rfq-last/${row.original.id}`)} + > + <Eye className="h-4 w-4" /> + </Button> + ), + size: 60, + }, + + // 견적상태 + { + accessorKey: "status", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적상태" />, + cell: ({ row }) => ( + <Badge variant={getStatusBadgeVariant(row.original.status)}> + {row.original.status} + </Badge> + ), + size: 120, + }, + + // 프로젝트 (프로젝트명) + { + accessorKey: "projectName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트 (프로젝트명)" />, + cell: ({ row }) => ( + <div className="flex flex-col"> + <span className="font-mono text-xs text-muted-foreground"> + {row.original.projectCode} + </span> + <span className="max-w-[200px] truncate" title={row.original.projectName || ""}> + {row.original.projectName || "-"} + </span> + </div> + ), + size: 220, + }, + + // // 시리즈 + // { + // accessorKey: "series", + // header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="시리즈" />, + // cell: ({ row }) => { + // const series = row.original.series; + // if (!series) return "-"; + // const label = series === "SS" ? "시리즈 통합" : series === "II" ? "품목 통합" : series; + // return <Badge variant="outline">{label}</Badge>; + // }, + // size: 100, + // }, + + // // 선급 + // { + // accessorKey: "classNo", + // header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="선급" />, + // cell: ({ row }) => row.original.classNo || "-", + // size: 80, + // }, + + // 견적명 + { + accessorKey: "rfqTitle", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적명" />, + cell: ({ row }) => ( + <div className="max-w-[200px] truncate" title={row.original.rfqTitle || ""}> + {row.original.rfqTitle || "-"} + </div> + ), + size: 200, + }, + + // 자재그룹 (자재그룹명) + { + accessorKey: "majorItemMaterialDescription", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="자재그룹 (자재그룹명)" />, + cell: ({ row }) => ( + <div className="flex flex-col"> + <span className="font-mono text-xs text-muted-foreground"> + {row.original.majorItemMaterialCategory} + </span> + <span className="max-w-[150px] truncate" title={row.original.majorItemMaterialDescription || ""}> + {row.original.majorItemMaterialDescription || "-"} + </span> + </div> + ), + size: 180, + }, + + // 자재코드 + { + accessorKey: "itemCode", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="자재코드" />, + cell: ({ row }) => ( + <span className="font-mono text-sm">{row.original.itemCode || "-"}</span> + ), + size: 100, + }, + // 자재명 + { + accessorKey: "itemName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="자재명" />, + cell: ({ row }) => ( + <span className="font-mono text-sm">{row.original.itemName || "-"}</span> + ), + size: 100, + }, + + // 견적 자료 + { + id: "rfqDocument", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적 자료" />, + cell: ({ row }) => ( + <Button + variant="ghost" + size="sm" + onClick={() => setRowAction({ row, type: "attachment" })} + > + <FileText className="h-4 w-4" /> + </Button> + ), + size: 80, + }, + + // 견적품목수 + { + accessorKey: "prItemsCount", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적품목수" />, + cell: ({ row }) => ( + <Button + variant="ghost" + size="sm" + className="font-mono text-sm p-1 h-auto" + onClick={() => setRowAction({ row, type: "items" })} + > + {row.original.prItemsCount || 0} + </Button> + ), + size: 90, + }, + + // 견적생성일 + { + accessorKey: "createdAt", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적생성일" />, + cell: ({ row }) => { + const date = row.original.createdAt; + return date ? format(new Date(date), "yyyy-MM-dd") : "-"; + }, + size: 100, + }, + + // 견적발송일 + { + accessorKey: "rfqSendDate", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적발송일" />, + cell: ({ row }) => { + const date = row.original.rfqSendDate; + return date ? format(new Date(date), "yyyy-MM-dd") : "-"; + }, + size: 100, + }, + + // 견적마감일 + { + accessorKey: "dueDate", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적마감일" />, + cell: ({ row }) => { + const date = row.original.dueDate; + if (!date) return "-"; + + const now = new Date(); + const dueDate = new Date(date); + const daysLeft = differenceInDays(dueDate, now); + + // 상태별 스타일과 아이콘 설정 + let statusIcon; + let statusText; + let statusClass; + + if (daysLeft < 0) { + const daysOverdue = Math.abs(daysLeft); + statusIcon = <XCircle className="h-4 w-4" />; + statusText = `${daysOverdue}일 지남`; + statusClass = "text-red-600"; + } else if (daysLeft === 0) { + statusIcon = <AlertTriangle className="h-4 w-4" />; + statusText = "오늘 마감"; + statusClass = "text-orange-600"; + } else if (daysLeft <= 3) { + statusIcon = <AlertCircle className="h-4 w-4" />; + statusText = `${daysLeft}일 남음`; + statusClass = "text-amber-600"; + } else if (daysLeft <= 7) { + statusIcon = <Clock className="h-4 w-4" />; + statusText = `${daysLeft}일 남음`; + statusClass = "text-blue-600"; + } else { + statusIcon = <CheckCircle className="h-4 w-4" />; + statusText = `${daysLeft}일 남음`; + statusClass = "text-green-600"; + } + + return ( + <div className="flex flex-col gap-1"> + <span className="text-sm text-muted-foreground"> + {format(dueDate, "yyyy-MM-dd")} + </span> + <div className={`flex items-center gap-1 ${statusClass}`}> + {statusIcon} + <span className="text-xs font-medium">{statusText}</span> + </div> + </div> + ); + }, + size: 120, + }, + + // 계약기간 (추가) + { + id: "contractPeriod", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약기간" />, + cell: ({ row }) => { + const start = row.original.contractStartDate; + const end = row.original.contractEndDate; + + if (!start && !end) return "-"; + + return ( + <div className="flex flex-col text-xs"> + <span>{start ? format(new Date(start), "yyyy-MM-dd") : "미정"}</span> + <span className="text-muted-foreground">~</span> + <span>{end ? format(new Date(end), "yyyy-MM-dd") : "미정"}</span> + </div> + ); + }, + size: 120, + }, + + // 구매담당자 + { + accessorKey: "picUserName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="구매담당자" />, + cell: ({ row }) => { + const name = row.original.picUserName || row.original.picName || "-"; + const picCode = row.original.picCode || ""; + return name === "-" ? "-" : `${name}(${picCode})`; + }, + size: 100, + }, + + // 최종수정일 + { + accessorKey: "updatedAt", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="최종수정일" />, + cell: ({ row }) => { + const date = row.original.updatedAt; + return date ? format(new Date(date), "yyyy-MM-dd HH:mm") : "-"; + }, + size: 120, + }, + + // 최종수정자 + { + accessorKey: "updatedByUserName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="최종수정자" />, + cell: ({ row }) => row.original.updatedByUserName || "-", + size: 100, + }, + + // 비고 + { + accessorKey: "remark", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="비고" />, + cell: ({ row }) => row.original.remark || "-", + size: 150, + }, + ]; + } + + // ═══════════════════════════════════════════════════════════════ // 일반견적 컬럼 정의 // ═══════════════════════════════════════════════════════════════ if (rfqCategory === "general") { diff --git a/lib/rfq-last/table/rfq-table.tsx b/lib/rfq-last/table/rfq-table.tsx index 80f1422e..e8dd299d 100644 --- a/lib/rfq-last/table/rfq-table.tsx +++ b/lib/rfq-last/table/rfq-table.tsx @@ -29,7 +29,7 @@ import { RfqItemsDialog } from "../shared/rfq-items-dialog"; interface RfqTableProps { data: Awaited<ReturnType<typeof getRfqs>>; - rfqCategory?: "general" | "itb" | "rfq"; + rfqCategory?: "general" | "itb" | "rfq" | "pre_bidding"; className?: string; } @@ -274,7 +274,7 @@ export function RfqTable({ { id: "vendorCount", label: "업체수", type: "number" }, { id: "dueDate", label: "마감일", type: "date" }, { id: "rfqSendDate", label: "발송일", type: "date" }, - ...(rfqCategory === "general" ? [ + ...(rfqCategory === "general" || rfqCategory === "pre_bidding" ? [ { id: "rfqType", label: "견적 유형", @@ -283,10 +283,14 @@ export function RfqTable({ { label: "단가계약", value: "단가계약" }, { label: "매각계약", value: "매각계약" }, { label: "일반계약", value: "일반계약" }, + ...(rfqCategory === "pre_bidding" ? [{ label: "사전견적(입찰)", value: "사전견적(입찰)" }] : []) ] }, { id: "rfqTitle", label: "견적 제목", type: "text" }, ] as DataTableAdvancedFilterField<RfqsLastView>[] : []), + ...(rfqCategory === "pre_bidding" ? [ + { id: "biddingNumber", label: "입찰 No.", type: "text" }, + ] as DataTableAdvancedFilterField<RfqsLastView>[] : []), ...(rfqCategory === "itb" ? [ { id: "smCode", label: "SM 코드", type: "text" }, ] as DataTableAdvancedFilterField<RfqsLastView>[] : []), @@ -414,6 +418,7 @@ export function RfqTable({ <Badge variant="outline" className="text-sm"> {rfqCategory === "general" ? "일반견적" : + rfqCategory === "pre_bidding" ? "사전견적(입찰)" : rfqCategory === "itb" ? "ITB" : "RFQ"} </Badge> |
