diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-18 00:23:40 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-18 00:23:40 +0000 |
| commit | cf8dac0c6490469dab88a560004b0c07dbd48612 (patch) | |
| tree | b9e76061e80d868331e6b4277deecb9086f845f3 /lib/itb/table/purchase-request-columns.tsx | |
| parent | e5745fc0268bbb5770bc14a55fd58a0ec30b466e (diff) | |
(대표님) rfq, 계약, 서명 등
Diffstat (limited to 'lib/itb/table/purchase-request-columns.tsx')
| -rw-r--r-- | lib/itb/table/purchase-request-columns.tsx | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/lib/itb/table/purchase-request-columns.tsx b/lib/itb/table/purchase-request-columns.tsx new file mode 100644 index 00000000..55321a21 --- /dev/null +++ b/lib/itb/table/purchase-request-columns.tsx @@ -0,0 +1,380 @@ +// components/purchase-requests/purchase-request-columns.tsx +"use client"; + +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, + Edit, + Trash2, + CheckCircle, + XCircle, + FileText, + Package, + Send, + Clock +} from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { MoreHorizontal } from "lucide-react"; +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"; +import { format } from "date-fns"; +import { ko } from "date-fns/locale"; +import type { DataTableRowAction } from "@/types/table"; +import type { PurchaseRequestView } from "@/db/schema"; + +interface GetColumnsProps { + setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<PurchaseRequestView> | null>>; +} + +const statusConfig = { + "작성중": { + label: "작성중", + variant: "secondary" as const, + icon: Edit, + color: "text-gray-500" + }, + "요청완료": { + label: "요청완료", + variant: "default" as const, + icon: Send, + color: "text-blue-500" + }, + "검토중": { + label: "검토중", + variant: "warning" as const, + icon: Clock, + color: "text-yellow-500" + }, + "승인": { + label: "승인", + variant: "success" as const, + icon: CheckCircle, + color: "text-green-500" + }, + "반려": { + label: "반려", + variant: "destructive" as const, + icon: XCircle, + color: "text-red-500" + }, + "RFQ생성완료": { + label: "RFQ생성완료", + variant: "outline" as const, + icon: FileText, + color: "text-purple-500" + }, +}; + +export function getPurchaseRequestColumns({ + setRowAction +}: GetColumnsProps): ColumnDef<PurchaseRequestView>[] { + 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" + /> + ), + enableSorting: false, + enableHiding: false, + size: 40, + }, + { + accessorKey: "requestCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="요청번호" /> + ), + cell: ({ row }) => ( + <Button + variant="ghost" + className="h-auto p-0 font-mono font-medium text-primary hover:underline" + onClick={() => setRowAction({ row, type: "view" })} + > + {row.getValue("requestCode")} + </Button> + ), + size: 130, + }, + { + accessorKey: "status", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="상태" /> + ), + cell: ({ row }) => { + const status = row.getValue("status") as keyof typeof statusConfig; + const config = statusConfig[status]; + const Icon = config?.icon; + + return ( + <Badge variant={config?.variant} className="font-medium"> + {Icon && <Icon className={`mr-1 h-3 w-3 ${config.color}`} />} + {config?.label || status} + </Badge> + ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + size: 120, + }, + { + accessorKey: "requestTitle", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="요청제목" /> + ), + cell: ({ row }) => ( + <div className="max-w-[300px]"> + <div className="truncate font-medium" title={row.getValue("requestTitle")}> + {row.getValue("requestTitle")} + </div> + {row.original.requestDescription && ( + <div className="truncate text-xs text-muted-foreground mt-0.5" + title={row.original.requestDescription}> + {row.original.requestDescription} + </div> + )} + </div> + ), + size: 300, + }, + { + accessorKey: "projectName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="프로젝트" /> + ), + cell: ({ row }) => ( + <div className="flex flex-col space-y-0.5"> + {row.original.projectCode && ( + <span className="font-mono text-xs text-muted-foreground"> + {row.original.projectCode} + </span> + )} + <span className="truncate text-sm" title={row.original.projectName}> + {row.original.projectName || "-"} + </span> + </div> + ), + size: 180, + }, + { + accessorKey: "packageName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="패키지" /> + ), + cell: ({ row }) => ( + <div className="flex flex-col space-y-0.5"> + {row.original.packageNo && ( + <span className="font-mono text-xs text-muted-foreground"> + {row.original.packageNo} + </span> + )} + <span className="truncate text-sm" title={row.original.packageName}> + {row.original.packageName || "-"} + </span> + </div> + ), + size: 150, + }, + { + id: "attachments", + header: "첨부", + cell: ({ row }) => { + const count = Number(row.original.attachmentCount) || 0; + return count > 0 ? ( + <Button + variant="ghost" + size="sm" + className="h-8 px-2" + onClick={() => setRowAction({ row, type: "attachments" })} + > + <FileText className="h-4 w-4 mr-1" /> + {count} + </Button> + ) : ( + <span className="text-muted-foreground text-sm">-</span> + ); + }, + size: 70, + }, + { + id: "items", + header: "품목", + cell: ({ row }) => { + const count = row.original.itemCount || 0; + const totalQuantity = row.original.totalQuantity; + return count > 0 ? ( + <Button + variant="ghost" + size="sm" + className="h-8 px-2" + onClick={() => setRowAction({ row, type: "items" })} + > + <Package className="h-4 w-4 mr-1" /> + <div className="flex flex-col items-start"> + <span>{count}종</span> + {totalQuantity && ( + <span className="text-xs text-muted-foreground"> + 총 {totalQuantity}개 + </span> + )} + </div> + </Button> + ) : ( + <span className="text-muted-foreground text-sm">-</span> + ); + }, + size: 90, + }, + { + accessorKey: "engPicName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="설계담당" /> + ), + cell: ({ row }) => ( + <div className="text-sm"> + {row.getValue("engPicName") || "-"} + </div> + ), + size: 100, + }, + { + accessorKey: "purchasePicName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="구매담당" /> + ), + cell: ({ row }) => ( + <div className="text-sm"> + {row.getValue("purchasePicName") || "-"} + </div> + ), + size: 100, + }, + { + accessorKey: "estimatedBudget", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="예상예산" /> + ), + cell: ({ row }) => ( + <div className="text-sm font-mono"> + {row.getValue("estimatedBudget") || "-"} + </div> + ), + size: 100, + }, + { + accessorKey: "requestedDeliveryDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="희망납기" /> + ), + cell: ({ row }) => { + const date = row.getValue("requestedDeliveryDate") as Date | null; + return ( + <div className="text-sm"> + {date ? format(new Date(date), "yyyy-MM-dd", { locale: ko }) : "-"} + </div> + ); + }, + size: 100, + }, + { + accessorKey: "createdAt", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="등록일" /> + ), + cell: ({ row }) => { + const date = row.getValue("createdAt") as Date; + return ( + <div className="text-sm text-muted-foreground"> + {date ? format(new Date(date), "yyyy-MM-dd", { locale: ko }) : "-"} + </div> + ); + }, + size: 100, + }, + { + id: "actions", + header: "작업", + cell: ({ row }) => { + const status = row.original.status; + + return ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button variant="ghost" className="h-8 w-8 p-0"> + <span className="sr-only">메뉴 열기</span> + <MoreHorizontal className="h-4 w-4" /> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <DropdownMenuLabel>작업</DropdownMenuLabel> + <DropdownMenuItem + onClick={() => setRowAction({ row, type: "view" })} + > + <Eye className="mr-2 h-4 w-4" /> + 상세보기 + </DropdownMenuItem> + <DropdownMenuSeparator /> + + + + <DropdownMenuItem + onClick={() => setRowAction({ row, type: "update" })} + > + <Edit className="mr-2 h-4 w-4" /> + 수정 + </DropdownMenuItem> + <DropdownMenuItem + onClick={() => setRowAction({ row, type: "delete" })} + className="text-destructive" + > + <Trash2 className="mr-2 h-4 w-4" /> + 삭제 + </DropdownMenuItem> + + + + +{status ==="작성중" && +<> + <DropdownMenuSeparator /> + <DropdownMenuItem + onClick={() => setRowAction({ row, type: "createRfq" })} + className="text-primary" + > + <FileText className="mr-2 h-4 w-4" /> + RFQ 생성 + </DropdownMenuItem> + </> + } + + </DropdownMenuContent> + </DropdownMenu> + + ); + }, + size: 80, + }, + ]; +}
\ No newline at end of file |
