summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/plant/upload/columns.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-19 07:51:27 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-19 07:51:27 +0000
commit9ecdfb23fe3df6a5df86782385002c562dfc1198 (patch)
tree4188cb7e6bf2c862d9c86a59d79946bd41217227 /lib/vendor-document-list/plant/upload/columns.tsx
parentb67861fbb424c7ad47ad1538f75e2945bd8890c5 (diff)
(대표님) rfq 히스토리, swp 등
Diffstat (limited to 'lib/vendor-document-list/plant/upload/columns.tsx')
-rw-r--r--lib/vendor-document-list/plant/upload/columns.tsx379
1 files changed, 379 insertions, 0 deletions
diff --git a/lib/vendor-document-list/plant/upload/columns.tsx b/lib/vendor-document-list/plant/upload/columns.tsx
new file mode 100644
index 00000000..c0f17afc
--- /dev/null
+++ b/lib/vendor-document-list/plant/upload/columns.tsx
@@ -0,0 +1,379 @@
+"use client"
+
+import * as React from "react"
+import { ColumnDef } from "@tanstack/react-table"
+import { formatDate, formatDateTime } from "@/lib/utils"
+import { Checkbox } from "@/components/ui/checkbox"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { DataTableRowAction } from "@/types/table"
+import { StageSubmissionView } from "@/db/schema"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Progress } from "@/components/ui/progress"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import {
+ Ellipsis,
+ Upload,
+ Eye,
+ RefreshCw,
+ CheckCircle2,
+ XCircle,
+ AlertCircle,
+ Clock
+} from "lucide-react"
+
+interface GetColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<StageSubmissionView> | null>>
+}
+
+export function getColumns({
+ setRowAction,
+}: GetColumnsProps): ColumnDef<StageSubmissionView>[] {
+ return [
+ {
+ id: "select",
+ header: ({ table }) => (
+ <Checkbox
+ checked={
+ table.getIsAllPageRowsSelected() ||
+ (table.getIsSomePageRowsSelected() && "indeterminate")
+ }
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ />
+ ),
+ cell: ({ row }) => (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
+ aria-label="Select row"
+ />
+ ),
+ size: 40,
+ enableSorting: false,
+ enableHiding: false,
+ },
+ {
+ accessorKey: "docNumber",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Doc Number" />
+ ),
+ cell: ({ row }) => {
+ const vendorDocNumber = row.original.vendorDocNumber
+ return (
+ <div className="space-y-1">
+ <div className="font-medium">{row.getValue("docNumber")}</div>
+ {vendorDocNumber && (
+ <div className="text-xs text-muted-foreground">{vendorDocNumber}</div>
+ )}
+ </div>
+ )
+ },
+ size: 150,
+ },
+ {
+ accessorKey: "documentTitle",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Document Title" />
+ ),
+ cell: ({ row }) => (
+ <div className="max-w-[300px] truncate" title={row.getValue("documentTitle")}>
+ {row.getValue("documentTitle")}
+ </div>
+ ),
+ size: 250,
+ },
+ {
+ accessorKey: "projectCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Project" />
+ ),
+ cell: ({ row }) => (
+ <Badge variant="outline">{row.getValue("projectCode")}</Badge>
+ ),
+ size: 100,
+ },
+ {
+ accessorKey: "stageName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Stage" />
+ ),
+ cell: ({ row }) => {
+ const stageName = row.getValue("stageName") as string
+ const stageStatus = row.original.stageStatus
+ const stageOrder = row.original.stageOrder
+
+ return (
+ <div className="space-y-1">
+ <div className="flex items-center gap-2">
+ <Badge variant="secondary" className="text-xs">
+ {stageOrder ? `#${stageOrder}` : ""}
+ </Badge>
+ <span className="text-sm">{stageName}</span>
+ </div>
+ {stageStatus && (
+ <Badge
+ variant={
+ stageStatus === "COMPLETED" ? "success" :
+ stageStatus === "IN_PROGRESS" ? "default" :
+ stageStatus === "REJECTED" ? "destructive" :
+ "secondary"
+ }
+ className="text-xs"
+ >
+ {stageStatus}
+ </Badge>
+ )}
+ </div>
+ )
+ },
+ size: 200,
+ },
+ {
+ accessorKey: "stagePlanDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Due Date" />
+ ),
+ cell: ({ row }) => {
+ const planDate = row.getValue("stagePlanDate") as Date | null
+ const isOverdue = row.original.isOverdue
+ const daysUntilDue = row.original.daysUntilDue
+
+ if (!planDate) return <span className="text-muted-foreground">-</span>
+
+ return (
+ <div className="space-y-1">
+ <div className={isOverdue ? "text-destructive font-medium" : ""}>
+ {formatDate(planDate)}
+ </div>
+ {daysUntilDue !== null && (
+ <div className="text-xs">
+ {isOverdue ? (
+ <Badge variant="destructive" className="gap-1">
+ <AlertCircle className="h-3 w-3" />
+ {Math.abs(daysUntilDue)} days overdue
+ </Badge>
+ ) : daysUntilDue === 0 ? (
+ <Badge variant="warning" className="gap-1">
+ <Clock className="h-3 w-3" />
+ Due today
+ </Badge>
+ ) : (
+ <span className="text-muted-foreground">
+ {daysUntilDue} days remaining
+ </span>
+ )}
+ </div>
+ )}
+ </div>
+ )
+ },
+ size: 150,
+ },
+ {
+ accessorKey: "latestSubmissionStatus",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Submission Status" />
+ ),
+ cell: ({ row }) => {
+ const status = row.getValue("latestSubmissionStatus") as string | null
+ const reviewStatus = row.original.latestReviewStatus
+ const revisionNumber = row.original.latestRevisionNumber
+ const revisionCode = row.original.latestRevisionCode
+
+ if (!status) {
+ return (
+ <Badge variant="outline" className="gap-1">
+ <AlertCircle className="h-3 w-3" />
+ Not submitted
+ </Badge>
+ )
+ }
+
+ return (
+ <div className="space-y-1">
+ <Badge
+ variant={
+ reviewStatus === "APPROVED" ? "success" :
+ reviewStatus === "REJECTED" ? "destructive" :
+ status === "SUBMITTED" ? "default" :
+ "secondary"
+ }
+ >
+ {reviewStatus || status}
+ </Badge>
+ {revisionCode !== null &&(
+ <div className="text-xs text-muted-foreground">
+ {revisionCode}
+ </div>
+ )}
+ </div>
+ )
+ },
+ size: 150,
+ },
+ {
+ id: "syncStatus",
+ accessorKey: "latestSyncStatus",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Sync Status" />
+ ),
+ cell: ({ row }) => {
+ const syncStatus = row.getValue("latestSyncStatus") as string | null
+ const syncProgress = row.original.syncProgress
+ const requiresSync = row.original.requiresSync
+
+ if (!syncStatus || syncStatus === "pending") {
+ if (requiresSync) {
+ return (
+ <Badge variant="outline" className="gap-1">
+ <Clock className="h-3 w-3" />
+ Pending
+ </Badge>
+ )
+ }
+ return <span className="text-muted-foreground">-</span>
+ }
+
+ return (
+ <div className="space-y-2">
+ <Badge
+ variant={
+ syncStatus === "synced" ? "success" :
+ syncStatus === "failed" ? "destructive" :
+ syncStatus === "syncing" ? "default" :
+ "secondary"
+ }
+ className="gap-1"
+ >
+ {syncStatus === "syncing" && <RefreshCw className="h-3 w-3 animate-spin" />}
+ {syncStatus === "synced" && <CheckCircle2 className="h-3 w-3" />}
+ {syncStatus === "failed" && <XCircle className="h-3 w-3" />}
+ {syncStatus}
+ </Badge>
+ {syncProgress !== null && syncProgress !== undefined && syncStatus === "syncing" && (
+ <Progress value={syncProgress} className="h-1.5 w-20" />
+ )}
+ </div>
+ )
+ },
+ size: 120,
+ },
+ {
+ accessorKey: "totalFiles",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Files" />
+ ),
+ cell: ({ row }) => {
+ const totalFiles = row.getValue("totalFiles") as number
+ const syncedFiles = row.original.syncedFilesCount
+
+ if (!totalFiles) return <span className="text-muted-foreground">0</span>
+
+ return (
+ <div className="text-sm">
+ {syncedFiles !== null && syncedFiles !== undefined ? (
+ <span>{syncedFiles}/{totalFiles}</span>
+ ) : (
+ <span>{totalFiles}</span>
+ )}
+ </div>
+ )
+ },
+ size: 80,
+ },
+ // {
+ // accessorKey: "vendorName",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="Vendor" />
+ // ),
+ // cell: ({ row }) => {
+ // const vendorName = row.getValue("vendorName") as string
+ // const vendorCode = row.original.vendorCode
+
+ // return (
+ // <div className="space-y-1">
+ // <div className="text-sm">{vendorName}</div>
+ // {vendorCode && (
+ // <div className="text-xs text-muted-foreground">{vendorCode}</div>
+ // )}
+ // </div>
+ // )
+ // },
+ // size: 150,
+ // },
+ {
+ id: "actions",
+ enableHiding: false,
+ cell: function Cell({ row }) {
+ const requiresSubmission = row.original.requiresSubmission
+ const requiresSync = row.original.requiresSync
+ const latestSubmissionId = row.original.latestSubmissionId
+
+ return (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ aria-label="Open menu"
+ variant="ghost"
+ className="flex size-7 p-0"
+ >
+ <Ellipsis className="size-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end" className="w-48">
+ {requiresSubmission && (
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "upload" })}
+ className="gap-2"
+ >
+ <Upload className="h-4 w-4" />
+ Upload Documents
+ </DropdownMenuItem>
+ )}
+
+ {latestSubmissionId && (
+ <>
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "view" })}
+ className="gap-2"
+ >
+ <Eye className="h-4 w-4" />
+ View Submission
+ </DropdownMenuItem>
+
+ {requiresSync && (
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "sync" })}
+ className="gap-2"
+ >
+ <RefreshCw className="h-4 w-4" />
+ Retry Sync
+ </DropdownMenuItem>
+ )}
+ </>
+ )}
+
+ <DropdownMenuSeparator />
+
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "history" })}
+ className="gap-2"
+ >
+ <Clock className="h-4 w-4" />
+ View History
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ )
+ },
+ size: 40,
+ }
+ ]
+} \ No newline at end of file