diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-08 11:23:40 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-08 11:23:40 +0000 |
| commit | b84621f9b2b7161a5ad4f0b194264e9df3e65dbf (patch) | |
| tree | ce5ec30b3d1e5104a3a2d942c71973779436783b /lib/b-rfq/final/final-rfq-detail-columns.tsx | |
| parent | 97936ddf280c56a4f122dedcb8dc389d0d2e63a2 (diff) | |
(대표님) 20250708 미반영분 커밋
Diffstat (limited to 'lib/b-rfq/final/final-rfq-detail-columns.tsx')
| -rw-r--r-- | lib/b-rfq/final/final-rfq-detail-columns.tsx | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/lib/b-rfq/final/final-rfq-detail-columns.tsx b/lib/b-rfq/final/final-rfq-detail-columns.tsx new file mode 100644 index 00000000..832923eb --- /dev/null +++ b/lib/b-rfq/final/final-rfq-detail-columns.tsx @@ -0,0 +1,589 @@ +// final-rfq-detail-columns.tsx +"use client" + +import * as React from "react" +import { type ColumnDef } from "@tanstack/react-table" +import { type Row } from "@tanstack/react-table" +import { + Ellipsis, Building, Eye, Edit, + MessageSquare, Settings, CheckCircle2, XCircle, DollarSign, Calendar +} from "lucide-react" + +import { formatDate } from "@/lib/utils" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, DropdownMenuShortcut +} from "@/components/ui/dropdown-menu" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" +import { FinalRfqDetailView } from "@/db/schema" + +// RowAction 타입 정의 +export interface DataTableRowAction<TData> { + row: Row<TData> + type: "update" +} + +interface GetFinalRfqDetailColumnsProps { + onSelectDetail?: (detail: any) => void + setRowAction?: React.Dispatch<React.SetStateAction<DataTableRowAction<FinalRfqDetailView> | null>> +} + +export function getFinalRfqDetailColumns({ + onSelectDetail, + setRowAction +}: GetFinalRfqDetailColumnsProps = {}): ColumnDef<FinalRfqDetailView>[] { + + return [ + /** ───────────── 체크박스 ───────────── */ + { + 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, + }, + + /** 1. RFQ Status */ + { + accessorKey: "finalRfqStatus", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최종 RFQ Status" /> + ), + cell: ({ row }) => { + const status = row.getValue("finalRfqStatus") as string + const getFinalStatusColor = (status: string) => { + switch (status) { + case "DRAFT": return "outline" + case "Final RFQ Sent": return "default" + case "Quotation Received": return "success" + case "Vendor Selected": return "default" + default: return "secondary" + } + } + return ( + <Badge variant={getFinalStatusColor(status) as any}> + {status} + </Badge> + ) + }, + size: 120 + }, + + /** 2. RFQ No. */ + { + accessorKey: "rfqCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="RFQ No." /> + ), + cell: ({ row }) => ( + <div className="text-sm font-medium"> + {row.getValue("rfqCode") as string} + </div> + ), + size: 120, + }, + + /** 3. Rev. */ + { + accessorKey: "returnRevision", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Rev." /> + ), + cell: ({ row }) => { + const revision = row.getValue("returnRevision") as number + return revision > 0 ? ( + <Badge variant="outline"> + Rev. {revision} + </Badge> + ) : ( + <Badge variant="outline"> + Rev. 0 + </Badge> + ) + }, + size: 80, + }, + + /** 4. Vendor Code */ + { + accessorKey: "vendorCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Vendor Code" /> + ), + cell: ({ row }) => ( + <div className="text-sm font-medium"> + {row.original.vendorCode} + </div> + ), + size: 100, + }, + + /** 5. Vendor Name */ + { + accessorKey: "vendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Vendor Name" /> + ), + cell: ({ row }) => ( + <div className="text-sm font-medium"> + {row.original.vendorName} + </div> + ), + size: 150, + }, + + /** 6. 업체분류 */ + { + id: "vendorClassification", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="업체분류" /> + ), + cell: ({ row }) => { + const vendorCode = row.original.vendorCode as string + return vendorCode ? ( + <Badge variant="success" className="text-xs"> + 정규업체 + </Badge> + ) : ( + <Badge variant="secondary" className="text-xs"> + 잠재업체 + </Badge> + ) + }, + size: 100, + }, + + /** 7. CP 현황 */ + { + accessorKey: "cpRequestYn", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="CP 현황" /> + ), + cell: ({ row }) => { + const cpRequest = row.getValue("cpRequestYn") as boolean + return cpRequest ? ( + <Badge variant="success" className="text-xs"> + 신청 + </Badge> + ) : ( + <Badge variant="outline" className="text-xs"> + 미신청 + </Badge> + ) + }, + size: 80, + }, + + /** 8. GTC현황 */ + { + id: "gtcStatus", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="GTC현황" /> + ), + cell: ({ row }) => { + const gtc = row.original.gtc as string + const gtcValidDate = row.original.gtcValidDate as string + const prjectGtcYn = row.original.prjectGtcYn as boolean + + if (prjectGtcYn || gtc) { + return ( + <div className="space-y-1"> + <Badge variant="success" className="text-xs"> + 보유 + </Badge> + {gtcValidDate && ( + <div className="text-xs text-muted-foreground"> + {gtcValidDate} + </div> + )} + </div> + ) + } + return ( + <Badge variant="outline" className="text-xs"> + 미보유 + </Badge> + ) + }, + size: 100, + }, + + /** 9. TBE 결과 (스키마에 없어서 placeholder) */ + { + id: "tbeResult", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="TBE 결과" /> + ), + cell: ({ row }) => { + // TODO: TBE 결과 로직 구현 필요 + return ( + <span className="text-muted-foreground text-xs">-</span> + ) + }, + size: 80, + }, + + /** 10. 최종 선정 */ + { + id: "finalSelection", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최종 선정" /> + ), + cell: ({ row }) => { + const status = row.original.finalRfqStatus as string + return status === "Vendor Selected" ? ( + <Badge variant="success" className="text-xs"> + <CheckCircle2 className="h-3 w-3 mr-1" /> + 선정 + </Badge> + ) : ( + <span className="text-muted-foreground text-xs">-</span> + ) + }, + size: 80, + }, + + /** 11. Currency */ + { + accessorKey: "currency", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Currency" /> + ), + cell: ({ row }) => { + const currency = row.getValue("currency") as string + return currency ? ( + <Badge variant="outline" className="text-xs"> + {/* <DollarSign className="h-3 w-3 mr-1" /> */} + {currency} + </Badge> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 80, + }, + + /** 12. Terms of Payment */ + { + accessorKey: "paymentTermsCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Terms of Payment" /> + ), + cell: ({ row }) => { + const paymentTermsCode = row.getValue("paymentTermsCode") as string + return paymentTermsCode ? ( + <Badge variant="secondary" className="text-xs"> + {paymentTermsCode} + </Badge> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 120, + }, + + /** 13. Payment Desc. */ + { + accessorKey: "paymentTermsDescription", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Payment Desc." /> + ), + cell: ({ row }) => { + const description = row.getValue("paymentTermsDescription") as string + return description ? ( + <div className="text-xs max-w-[150px] truncate" title={description}> + {description} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 150, + }, + + /** 14. TAX */ + { + accessorKey: "taxCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="TAX" /> + ), + cell: ({ row }) => { + const taxCode = row.getValue("taxCode") as string + return taxCode ? ( + <Badge variant="outline" className="text-xs"> + {taxCode} + </Badge> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 80, + }, + + /** 15. Delivery Date* */ + { + accessorKey: "deliveryDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Delivery Date*" /> + ), + cell: ({ row }) => { + const deliveryDate = row.getValue("deliveryDate") as Date + return deliveryDate ? ( + <div className="text-sm"> + {formatDate(deliveryDate)} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 120, + }, + + /** 16. Country */ + { + accessorKey: "vendorCountry", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Country" /> + ), + cell: ({ row }) => { + const country = row.getValue("vendorCountry") as string + const countryDisplay = country === "KR" ? "D" : "F" + return ( + <Badge variant="outline" className="text-xs"> + {countryDisplay} + </Badge> + ) + }, + size: 80, + }, + + /** 17. Place of Shipping */ + { + accessorKey: "placeOfShipping", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Place of Shipping" /> + ), + cell: ({ row }) => { + const placeOfShipping = row.getValue("placeOfShipping") as string + return placeOfShipping ? ( + <div className="text-xs max-w-[120px] truncate" title={placeOfShipping}> + {placeOfShipping} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 120, + }, + + /** 18. Place of Destination */ + { + accessorKey: "placeOfDestination", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Place of Destination" /> + ), + cell: ({ row }) => { + const placeOfDestination = row.getValue("placeOfDestination") as string + return placeOfDestination ? ( + <div className="text-xs max-w-[120px] truncate" title={placeOfDestination}> + {placeOfDestination} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 120, + }, + + /** 19. 초도 여부* */ + { + accessorKey: "firsttimeYn", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="초도 여부*" /> + ), + cell: ({ row }) => { + const firsttime = row.getValue("firsttimeYn") as boolean + return firsttime ? ( + <Badge variant="success" className="text-xs"> + 초도 + </Badge> + ) : ( + <Badge variant="outline" className="text-xs"> + 재구매 + </Badge> + ) + }, + size: 80, + }, + + /** 20. 연동제 적용* */ + { + accessorKey: "materialPriceRelatedYn", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="연동제 적용*" /> + ), + cell: ({ row }) => { + const materialPrice = row.getValue("materialPriceRelatedYn") as boolean + return materialPrice ? ( + <Badge variant="success" className="text-xs"> + 적용 + </Badge> + ) : ( + <Badge variant="outline" className="text-xs"> + 미적용 + </Badge> + ) + }, + size: 100, + }, + + /** 21. Business Size */ + { + id: "businessSizeDisplay", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Business Size" /> + ), + cell: ({ row }) => { + const businessSize = row.original.vendorBusinessSize as string + return businessSize ? ( + <Badge variant="outline" className="text-xs"> + {businessSize} + </Badge> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 100, + }, + + /** 22. 최종 Update일 */ + { + accessorKey: "updatedAt", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최종 Update일" /> + ), + cell: ({ row }) => { + const updated = row.getValue("updatedAt") as Date + return updated ? ( + <div className="text-sm"> + {formatDate(updated)} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 120, + }, + + /** 23. 최종 Update담당자 (스키마에 없어서 placeholder) */ + { + id: "updatedByUser", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최종 Update담당자" /> + ), + cell: ({ row }) => { + // TODO: updatedBy 사용자 정보 조인 필요 + return ( + <span className="text-muted-foreground text-xs">-</span> + ) + }, + size: 120, + }, + + /** 24. Vendor 설명 */ + { + accessorKey: "vendorRemark", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Vendor 설명" /> + ), + cell: ({ row }) => { + const vendorRemark = row.getValue("vendorRemark") as string + return vendorRemark ? ( + <div className="text-xs max-w-[150px] truncate" title={vendorRemark}> + {vendorRemark} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 150, + }, + + /** 25. 비고 */ + { + accessorKey: "remark", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="비고" /> + ), + cell: ({ row }) => { + const remark = row.getValue("remark") as string + return remark ? ( + <div className="text-xs max-w-[150px] truncate" title={remark}> + {remark} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 150, + }, + + /** ───────────── 액션 ───────────── */ + { + id: "actions", + enableHiding: false, + cell: function Cell({ row }) { + return ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button + aria-label="Open menu" + variant="ghost" + className="flex size-8 p-0 data-[state=open]:bg-muted" + > + <Ellipsis className="size-4" aria-hidden="true" /> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" className="w-48"> + <DropdownMenuItem> + <MessageSquare className="mr-2 h-4 w-4" /> + 벤더 견적 보기 + </DropdownMenuItem> + <DropdownMenuSeparator /> + {setRowAction && ( + <DropdownMenuItem + onSelect={() => setRowAction({ row, type: "update" })} + > + <Edit className="mr-2 h-4 w-4" /> + 수정 + </DropdownMenuItem> + )} + </DropdownMenuContent> + </DropdownMenu> + ) + }, + size: 40, + }, + ] +}
\ No newline at end of file |
