summaryrefslogtreecommitdiff
path: root/lib/b-rfq/final/final-rfq-detail-columns.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/b-rfq/final/final-rfq-detail-columns.tsx')
-rw-r--r--lib/b-rfq/final/final-rfq-detail-columns.tsx589
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