summaryrefslogtreecommitdiff
path: root/lib/tbe-last/table/tbe-last-table-columns.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-14 05:28:01 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-14 05:28:01 +0000
commit675b4e3d8ffcb57a041db285417d81e61284d900 (patch)
tree254f3d6a6c0ce39ae8fba35618f3810e08945f19 /lib/tbe-last/table/tbe-last-table-columns.tsx
parent39f12cb19f29cbc5568057e154e6adf4789ae736 (diff)
(대표님) RFQ-last, tbe-last, 기본계약 템플릿 내 견적,입찰,계약 추가, env.dev NAS_PATH 수정
Diffstat (limited to 'lib/tbe-last/table/tbe-last-table-columns.tsx')
-rw-r--r--lib/tbe-last/table/tbe-last-table-columns.tsx376
1 files changed, 376 insertions, 0 deletions
diff --git a/lib/tbe-last/table/tbe-last-table-columns.tsx b/lib/tbe-last/table/tbe-last-table-columns.tsx
new file mode 100644
index 00000000..71b3acde
--- /dev/null
+++ b/lib/tbe-last/table/tbe-last-table-columns.tsx
@@ -0,0 +1,376 @@
+// lib/tbe-last/table/tbe-last-table-columns.tsx
+
+"use client"
+
+import * as React from "react"
+import { type ColumnDef } from "@tanstack/react-table"
+import { FileText, MessageSquare, Package, ListChecks } from "lucide-react"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { formatDate } from "@/lib/utils"
+import { TbeLastView } from "@/db/schema/tbeLastView"
+
+interface GetColumnsProps {
+ onOpenSessionDetail: (sessionId: number) => void;
+ onOpenDocuments: (sessionId: number) => void;
+ onOpenPrItems: (rfqId: number) => void;
+ onOpenEvaluation: (session: TbeLastView) => void;
+}
+
+export function getColumns({
+ onOpenSessionDetail,
+ onOpenDocuments,
+ onOpenPrItems,
+ onOpenEvaluation,
+}: GetColumnsProps): ColumnDef<TbeLastView>[] {
+
+ const columns: ColumnDef<TbeLastView>[] = [
+ // Select Column
+ {
+ id: "select",
+ header: ({ table }) => (
+ <Checkbox
+ checked={
+ table.getIsAllPageRowsSelected() ||
+ (table.getIsSomePageRowsSelected() && "indeterminate")
+ }
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ className="translate-y-0.5"
+ />
+ ),
+ cell: ({ row }) => (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
+ aria-label="Select row"
+ className="translate-y-0.5"
+ />
+ ),
+ size: 40,
+ enableSorting: false,
+ enableHiding: false,
+ },
+
+ // TBE Session Code
+ {
+ accessorKey: "sessionCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="TBE Code" />
+ ),
+ cell: ({ row }) => {
+ const sessionId = row.original.tbeSessionId;
+ const sessionCode = row.original.sessionCode;
+
+ return (
+ <Button
+ variant="link"
+ className="p-0 h-auto font-medium"
+ onClick={() => onOpenSessionDetail(sessionId)}
+ >
+ {sessionCode}
+ </Button>
+ );
+ },
+ size: 120,
+ },
+
+ // RFQ Info Group
+ {
+ id: "rfqInfo",
+ header: "RFQ Information",
+ columns: [
+ {
+ accessorKey: "rfqCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="RFQ Code" />
+ ),
+ cell: ({ row }) => row.original.rfqCode,
+ size: 120,
+ },
+ {
+ accessorKey: "rfqTitle",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="RFQ Title" />
+ ),
+ cell: ({ row }) => row.original.rfqTitle || "-",
+ size: 200,
+ },
+ {
+ accessorKey: "rfqDueDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Due Date" />
+ ),
+ cell: ({ row }) => {
+ const date = row.original.rfqDueDate;
+ return date ? formatDate(date, "KR") : "-";
+ },
+ size: 100,
+ },
+ ],
+ },
+
+ // Package Info
+ {
+ id: "packageInfo",
+ header: "Package",
+ columns: [
+ {
+ accessorKey: "packageNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Package No" />
+ ),
+ cell: ({ row }) => {
+ const packageNo = row.original.packageNo;
+ const packageName = row.original.packageName;
+
+ if (!packageNo) return "-";
+
+ return (
+ <div className="flex flex-col">
+ <span className="font-medium">{packageNo}</span>
+ {packageName && (
+ <span className="text-xs text-muted-foreground">{packageName}</span>
+ )}
+ </div>
+ );
+ },
+ size: 150,
+ },
+ ],
+ },
+
+ // Project Info
+ {
+ accessorKey: "projectCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Project" />
+ ),
+ cell: ({ row }) => {
+ const projectCode = row.original.projectCode;
+ const projectName = row.original.projectName;
+
+ if (!projectCode) return "-";
+
+ return (
+ <div className="flex flex-col">
+ <span className="font-medium">{projectCode}</span>
+ {projectName && (
+ <span className="text-xs text-muted-foreground">{projectName}</span>
+ )}
+ </div>
+ );
+ },
+ size: 150,
+ },
+
+ // Vendor Info
+ {
+ id: "vendorInfo",
+ header: "Vendor",
+ columns: [
+ {
+ accessorKey: "vendorCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Vendor Code" />
+ ),
+ cell: ({ row }) => row.original.vendorCode || "-",
+ size: 100,
+ },
+ {
+ accessorKey: "vendorName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Vendor Name" />
+ ),
+ cell: ({ row }) => row.original.vendorName,
+ size: 200,
+ },
+ ],
+ },
+
+ // TBE Status
+ {
+ accessorKey: "sessionStatus",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Status" />
+ ),
+ cell: ({ row }) => {
+ const status = row.original.sessionStatus;
+
+ let variant: "default" | "secondary" | "outline" | "destructive" = "outline";
+
+ switch (status) {
+ case "준비중":
+ variant = "outline";
+ break;
+ case "진행중":
+ variant = "default";
+ break;
+ case "검토중":
+ variant = "secondary";
+ break;
+ case "완료":
+ variant = "default";
+ break;
+ case "보류":
+ variant = "destructive";
+ break;
+ }
+
+ return <Badge variant={variant}>{status}</Badge>;
+ },
+ size: 100,
+ },
+
+ // Evaluation Result
+ {
+ accessorKey: "evaluationResult",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Result" />
+ ),
+ cell: ({ row }) => {
+ const result = row.original.evaluationResult;
+ const session = row.original;
+
+ if (!result) {
+ return (
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => onOpenEvaluation(session)}
+ >
+ 평가입력
+ </Button>
+ );
+ }
+
+ let variant: "default" | "secondary" | "destructive" = "default";
+ let displayText = result;
+
+ switch (result) {
+ case "pass":
+ variant = "default";
+ displayText = "Pass";
+ break;
+ case "conditional_pass":
+ variant = "secondary";
+ displayText = "Conditional";
+ break;
+ case "non_pass":
+ variant = "destructive";
+ displayText = "Non-Pass";
+ break;
+ }
+
+ return (
+ <Button
+ variant="link"
+ className="p-0 h-auto"
+ onClick={() => onOpenEvaluation(session)}
+ >
+ <Badge variant={variant}>{displayText}</Badge>
+ </Button>
+ );
+ },
+ size: 120,
+ },
+
+ // PR Items
+ {
+ id: "prItems",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PR Items" />
+ ),
+ cell: ({ row }) => {
+ const rfqId = row.original.rfqId;
+ const totalCount = row.original.prItemsCount;
+ const majorCount = row.original.majorItemsCount;
+
+ return (
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 px-2"
+ onClick={() => onOpenPrItems(rfqId)}
+ >
+ <ListChecks className="h-4 w-4 mr-1" />
+ <span className="text-xs">
+ {totalCount} ({majorCount})
+ </span>
+ </Button>
+ );
+ },
+ size: 100,
+ enableSorting: false,
+ },
+
+ // Documents
+ {
+ id: "documents",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Documents" />
+ ),
+ cell: ({ row }) => {
+ const sessionId = row.original.tbeSessionId;
+ const buyerDocs = row.original.buyerDocumentsCount;
+ const vendorDocs = row.original.vendorDocumentsCount;
+ const reviewedDocs = row.original.reviewedDocumentsCount;
+ const totalDocs = buyerDocs + vendorDocs;
+
+ return (
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 px-2"
+ onClick={() => onOpenDocuments(sessionId)}
+ >
+ <FileText className="h-4 w-4 mr-1" />
+ <span className="text-xs">
+ {reviewedDocs}/{totalDocs}
+ </span>
+ </Button>
+ );
+ },
+ size: 100,
+ enableSorting: false,
+ },
+
+ // Comments
+ {
+ id: "comments",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Comments" />
+ ),
+ cell: ({ row }) => {
+ const sessionId = row.original.tbeSessionId;
+ const totalComments = row.original.totalCommentsCount;
+ const unresolvedComments = row.original.unresolvedCommentsCount;
+
+ return (
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 px-2 relative"
+ onClick={() => onOpenDocuments(sessionId)}
+ >
+ <MessageSquare className="h-4 w-4" />
+ {totalComments > 0 && (
+ <Badge
+ variant={unresolvedComments > 0 ? "destructive" : "secondary"}
+ className="absolute -top-1 -right-1 h-4 min-w-[1rem] p-0 text-[0.625rem]"
+ >
+ {unresolvedComments > 0 ? unresolvedComments : totalComments}
+ </Badge>
+ )}
+ </Button>
+ );
+ },
+ size: 80,
+ enableSorting: false,
+ },
+ ];
+
+ return columns;
+} \ No newline at end of file