diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-02 09:52:21 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-02 09:52:21 +0000 |
| commit | fccb00d15466cd0b2d861163663a5070c768ff77 (patch) | |
| tree | 4b14b27417ebeb873a9d4b4d7b5c64f6e1d78135 /lib/rfq-last/table/rfq-table-columns.tsx | |
| parent | 72f212f717f136e875e7623404a5ddd4c5268901 (diff) | |
(대표님) OCR 박진석프로 요청 대응, rfq 변경된 요구사항 구현
Diffstat (limited to 'lib/rfq-last/table/rfq-table-columns.tsx')
| -rw-r--r-- | lib/rfq-last/table/rfq-table-columns.tsx | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/lib/rfq-last/table/rfq-table-columns.tsx b/lib/rfq-last/table/rfq-table-columns.tsx new file mode 100644 index 00000000..3fac8881 --- /dev/null +++ b/lib/rfq-last/table/rfq-table-columns.tsx @@ -0,0 +1,445 @@ +"use client"; + +import * as React from "react"; +import { type ColumnDef } from "@tanstack/react-table"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Eye, FileText, Send, Package, Users, ChevronRight } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"; +import { RfqsLastView } from "@/db/schema"; +import { DataTableRowAction } from "@/types/table"; +import { format } from "date-fns"; +import { ko } from "date-fns/locale"; + +interface GetColumnsProps { + setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<RfqsLastView> | null>>; + rfqCategory?: "all" | "general" | "itb" | "rfq"; +} + +// RFQ 상태별 색상 +const getStatusBadgeVariant = (status: string) => { + switch (status) { + case "RFQ 생성": return "outline"; + case "구매담당지정": return "secondary"; + case "견적요청문서 확정": return "default"; + case "Short List 확정": return "default"; + case "TBE 완료": return "default"; + case "RFQ 발송": return "default"; + case "견적접수": return "default"; + case "최종업체선정": return "default"; + default: return "outline"; + } +}; + +// 시리즈 배지 +const getSeriesBadge = (series: string | null) => { + if (!series) return null; + + const label = series === "SS" ? "시리즈 통합" : series === "II" ? "품목 통합" : series; + const variant = series === "SS" ? "default" : series === "II" ? "secondary" : "outline"; + + return <Badge variant={variant} className="text-xs">{label}</Badge>; +}; + +export function getRfqColumns({ + setRowAction, + rfqCategory = "all" +}: GetColumnsProps): ColumnDef<RfqsLastView>[] { + + const baseColumns: ColumnDef<RfqsLastView>[] = [ + // ═══════════════════════════════════════════════════════════════ + // 선택 및 기본 정보 + // ═══════════════════════════════════════════════════════════════ + + // 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, + }, + + // RFQ 코드 + { + accessorKey: "rfqCode", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="RFQ 코드" />, + cell: ({ row }) => { + const rfqSealed = row.original.rfqSealedYn; + return ( + <div className="flex items-center gap-1"> + <span className="font-mono font-medium">{row.original.rfqCode}</span> + {rfqSealed && ( + <Badge variant="destructive" className="text-xs">봉인</Badge> + )} + </div> + ); + }, + size: 140, + }, + + // 상태 + { + accessorKey: "status", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="상태" />, + cell: ({ row }) => ( + <Badge variant={getStatusBadgeVariant(row.original.status)}> + {row.original.status} + </Badge> + ), + size: 120, + }, + + // ═══════════════════════════════════════════════════════════════ + // 일반견적 필드 (rfqCategory가 'general' 또는 'all'일 때만) + // ═══════════════════════════════════════════════════════════════ + ...(rfqCategory === "general" || rfqCategory === "all" ? [ + { + id: "rfqType", + accessorKey: "rfqType", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적 유형" />, + cell: ({ row }) => row.original.rfqType || "-", + size: 100, + }, + { + id: "rfqTitle", + 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, + }, + ] as ColumnDef<RfqsLastView>[] : []), + + // ═══════════════════════════════════════════════════════════════ + // ITB 필드 (rfqCategory가 'itb' 또는 'all'일 때만) + // ═══════════════════════════════════════════════════════════════ + ...(rfqCategory === "itb" || rfqCategory === "all" ? [ + { + id: "projectCompany", + accessorKey: "projectCompany", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트 회사" />, + cell: ({ row }) => row.original.projectCompany || "-", + size: 120, + }, + { + id: "projectFlag", + accessorKey: "projectFlag", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트 플래그" />, + cell: ({ row }) => row.original.projectFlag || "-", + size: 100, + }, + { + id: "projectSite", + accessorKey: "projectSite", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트 사이트" />, + cell: ({ row }) => row.original.projectSite || "-", + size: 120, + }, + { + id: "smCode", + accessorKey: "smCode", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="SM 코드" />, + cell: ({ row }) => row.original.smCode || "-", + size: 80, + }, + ] as ColumnDef<RfqsLastView>[] : []), + + // ═══════════════════════════════════════════════════════════════ + // RFQ(PR) 필드 (rfqCategory가 'rfq' 또는 'all'일 때만) + // ═══════════════════════════════════════════════════════════════ + ...(rfqCategory === "rfq" || rfqCategory === "all" ? [ + { + id: "prNumber", + accessorKey: "prNumber", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="PR 번호" />, + cell: ({ row }) => ( + <span className="font-mono text-sm">{row.original.prNumber || "-"}</span> + ), + size: 120, + }, + { + id: "prIssueDate", + accessorKey: "prIssueDate", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="PR 발행일" />, + cell: ({ row }) => { + const date = row.original.prIssueDate; + return date ? format(new Date(date), "yyyy-MM-dd") : "-"; + }, + size: 100, + }, + { + id: "series", + accessorKey: "series", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="시리즈" />, + cell: ({ row }) => getSeriesBadge(row.original.series), + size: 100, + }, + ] as ColumnDef<RfqsLastView>[] : []), + + // ═══════════════════════════════════════════════════════════════ + // 공통 프로젝트 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "프로젝트 정보", + columns: [ + { + accessorKey: "projectCode", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" />, + cell: ({ row }) => ( + <span className="font-mono text-sm">{row.original.projectCode || "-"}</span> + ), + size: 120, + }, + { + accessorKey: "projectName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트명" />, + cell: ({ row }) => ( + <div className="max-w-[200px] truncate" title={row.original.projectName || ""}> + {row.original.projectName || "-"} + </div> + ), + size: 200, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 품목 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "품목 정보", + columns: [ + { + 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 }) => ( + <div className="max-w-[200px] truncate" title={row.original.itemName || ""}> + {row.original.itemName || "-"} + </div> + ), + size: 200, + }, + { + accessorKey: "packageNo", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="패키지 번호" />, + cell: ({ row }) => row.original.packageNo || "-", + size: 100, + }, + { + accessorKey: "packageName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="패키지명" />, + cell: ({ row }) => ( + <div className="max-w-[200px] truncate" title={row.original.packageName || ""}> + {row.original.packageName || "-"} + </div> + ), + size: 200, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 담당자 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "담당자", + columns: [ + { + accessorKey: "picUserName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="구매담당자" />, + cell: ({ row }) => row.original.picUserName || row.original.picName || "-", + size: 100, + }, + { + accessorKey: "engPicName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="엔지니어링 담당" />, + cell: ({ row }) => row.original.engPicName || "-", + size: 120, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 일정 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "일정", + columns: [ + { + accessorKey: "rfqSendDate", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="발송일" />, + cell: ({ row }) => { + const date = row.original.rfqSendDate; + return date ? ( + <div className="flex items-center gap-1"> + <Send className="h-3 w-3 text-muted-foreground" /> + <span className="text-sm"> + {format(new Date(date), "MM-dd", { locale: ko })} + </span> + </div> + ) : "-"; + }, + size: 90, + }, + { + 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 isOverdue = now > dueDate; + + return ( + <span className={`text-sm ${isOverdue ? "text-red-600 font-medium" : ""}`}> + {format(dueDate, "MM-dd", { locale: ko })} + </span> + ); + }, + size: 90, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 벤더 및 견적 현황 + // ═══════════════════════════════════════════════════════════════ + { + header: "견적 현황", + columns: [ + { + accessorKey: "vendorCount", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="업체수" />, + cell: ({ row }) => ( + <div className="flex items-center gap-1"> + <Users className="h-3 w-3 text-muted-foreground" /> + <span className="font-medium">{row.original.vendorCount || 0}</span> + </div> + ), + size: 80, + }, + { + accessorKey: "shortListedVendorCount", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="Short List" />, + cell: ({ row }) => { + const count = row.original.shortListedVendorCount || 0; + return count > 0 ? ( + <Badge variant="default" className="font-mono"> + {count} + </Badge> + ) : "-"; + }, + size: 90, + }, + { + accessorKey: "quotationReceivedCount", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적접수" />, + cell: ({ row }) => { + const received = row.original.quotationReceivedCount || 0; + const total = row.original.vendorCount || 0; + + return ( + <div className="flex items-center gap-1"> + <FileText className="h-3 w-3 text-muted-foreground" /> + <span className={`text-sm ${received === total && total > 0 ? "text-green-600 font-medium" : ""}`}> + {received}/{total} + </span> + </div> + ); + }, + size: 90, + }, + ] + }, + + // PR Items 정보 + { + accessorKey: "prItemsCount", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="PR Items" />, + cell: ({ row }) => { + const prItems = row.original.prItemsCount || 0; + const majorItems = row.original.majorItemsCount || 0; + + return ( + <div className="flex flex-col gap-0.5"> + <span className="text-sm font-medium">{prItems}개</span> + {majorItems > 0 && ( + <Badge variant="secondary" className="text-xs"> + 주요 {majorItems} + </Badge> + )} + </div> + ); + }, + size: 90, + }, + + // 액션 + { + id: "actions", + header: "액션", + enableHiding: false, + size: 80, + minSize: 80, + cell: ({ row }) => { + return ( + <div className="flex items-center gap-1"> + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="ghost" + size="icon" + className="h-8 w-8" + onClick={() => setRowAction({ row, type: "view" })} + > + <Eye className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent>상세보기</TooltipContent> + </Tooltip> + </TooltipProvider> + </div> + ); + }, + }, + ]; + + return baseColumns; +}
\ No newline at end of file |
