summaryrefslogtreecommitdiff
path: root/lib/b-rfq/initial/initial-rfq-detail-columns.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-13 07:11:18 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-13 07:11:18 +0000
commit0fddf148402fd6b99a1b3800d73679899bcb2ed3 (patch)
treeeb51c02e6fa6037ddcc38a3b57d10d8c739125cf /lib/b-rfq/initial/initial-rfq-detail-columns.tsx
parentc72d0897f7b37843109c86f61d97eba05ba3ca0d (diff)
(대표님) 20250613 16시 10분 global css, b-rfq, document 등
Diffstat (limited to 'lib/b-rfq/initial/initial-rfq-detail-columns.tsx')
-rw-r--r--lib/b-rfq/initial/initial-rfq-detail-columns.tsx386
1 files changed, 386 insertions, 0 deletions
diff --git a/lib/b-rfq/initial/initial-rfq-detail-columns.tsx b/lib/b-rfq/initial/initial-rfq-detail-columns.tsx
new file mode 100644
index 00000000..f7ac0960
--- /dev/null
+++ b/lib/b-rfq/initial/initial-rfq-detail-columns.tsx
@@ -0,0 +1,386 @@
+// initial-rfq-detail-columns.tsx
+"use client"
+
+import * as React from "react"
+import { type ColumnDef } from "@tanstack/react-table"
+import {
+ Ellipsis, Building, Calendar, Eye,
+ MessageSquare, Settings, CheckCircle2, XCircle
+} 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
+} from "@/components/ui/dropdown-menu"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+
+interface GetInitialRfqDetailColumnsProps {
+ onSelectDetail?: (detail: any) => void
+}
+
+export function getInitialRfqDetailColumns({
+ onSelectDetail
+}: GetInitialRfqDetailColumnsProps = {}): ColumnDef<any>[] {
+
+ 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,
+ },
+
+ /** ───────────── RFQ 정보 ───────────── */
+ {
+ accessorKey: "rfqCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="RFQ 코드" />
+ ),
+ cell: ({ row }) => (
+ <Button
+ variant="link"
+ className="p-0 h-auto font-medium text-blue-600 hover:text-blue-800"
+ onClick={() => onSelectDetail?.(row.original)}
+ >
+ {row.getValue("rfqCode") as string}
+ </Button>
+ ),
+ size: 120,
+ },
+ {
+ accessorKey: "rfqStatus",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="RFQ 상태" />
+ ),
+ cell: ({ row }) => {
+ const status = row.getValue("rfqStatus") as string
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "DRAFT": return "secondary"
+ case "Doc. Received": return "outline"
+ case "PIC Assigned": return "default"
+ case "Doc. Confirmed": return "default"
+ case "Init. RFQ Sent": return "default"
+ case "Init. RFQ Answered": return "success"
+ case "TBE started": return "warning"
+ case "TBE finished": return "warning"
+ case "Final RFQ Sent": return "default"
+ case "Quotation Received": return "success"
+ case "Vendor Selected": return "success"
+ default: return "secondary"
+ }
+ }
+ return (
+ <Badge variant={getStatusColor(status) as any}>
+ {status}
+ </Badge>
+ )
+ },
+ size: 140
+ },
+ {
+ accessorKey: "initialRfqStatus",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="초기 RFQ 상태" />
+ ),
+ cell: ({ row }) => {
+ const status = row.getValue("initialRfqStatus") as string
+ const getInitialStatusColor = (status: string) => {
+ switch (status) {
+ case "PENDING": return "outline"
+ case "SENT": return "default"
+ case "RESPONDED": return "success"
+ case "EXPIRED": return "destructive"
+ case "CANCELLED": return "secondary"
+ default: return "secondary"
+ }
+ }
+ return (
+ <Badge variant={getInitialStatusColor(status) as any}>
+ {status}
+ </Badge>
+ )
+ },
+ size: 120
+ },
+
+ /** ───────────── 벤더 정보 ───────────── */
+ {
+ id: "vendorInfo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="벤더 정보" />
+ ),
+ cell: ({ row }) => {
+ const vendorName = row.original.vendorName as string
+ const vendorCode = row.original.vendorCode as string
+ const vendorCountry = row.original.vendorCountry as string
+ const businessSize = row.original.vendorBusinessSize as string
+
+ return (
+ <div className="space-y-1">
+ <div className="flex items-center gap-2">
+ <Building className="h-4 w-4 text-muted-foreground" />
+ <div className="font-medium">{vendorName}</div>
+ </div>
+ <div className="text-sm text-muted-foreground">
+ {vendorCode} • {vendorCountry}
+ </div>
+ {businessSize && (
+ <Badge variant="outline" className="text-xs">
+ {businessSize}
+ </Badge>
+ )}
+ </div>
+ )
+ },
+ size: 200,
+ },
+
+ /** ───────────── 날짜 정보 ───────────── */
+ {
+ accessorKey: "dueDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="마감일" />
+ ),
+ cell: ({ row }) => {
+ const dueDate = row.getValue("dueDate") as Date
+ const isOverdue = dueDate && new Date(dueDate) < new Date()
+
+ return dueDate ? (
+ <div className={`flex items-center gap-2 ${isOverdue ? 'text-red-600' : ''}`}>
+ <Calendar className="h-4 w-4" />
+ <div>
+ <div className="font-medium">{formatDate(dueDate)}</div>
+ {isOverdue && (
+ <div className="text-xs text-red-600">지연</div>
+ )}
+ </div>
+ </div>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ )
+ },
+ size: 120,
+ },
+ {
+ accessorKey: "validDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="유효일" />
+ ),
+ cell: ({ row }) => {
+ const validDate = row.getValue("validDate") as Date
+ return validDate ? (
+ <div className="text-sm">
+ {formatDate(validDate)}
+ </div>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ )
+ },
+ size: 100,
+ },
+
+ /** ───────────── Incoterms ───────────── */
+ {
+ id: "incoterms",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Incoterms" />
+ ),
+ cell: ({ row }) => {
+ const code = row.original.incotermsCode as string
+ const description = row.original.incotermsDescription as string
+
+ return code ? (
+ <div className="space-y-1">
+ <Badge variant="outline">{code}</Badge>
+ {description && (
+ <div className="text-xs text-muted-foreground max-w-[150px] truncate" title={description}>
+ {description}
+ </div>
+ )}
+ </div>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ )
+ },
+ size: 120,
+ },
+
+ /** ───────────── 플래그 정보 ───────────── */
+ {
+ id: "flags",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="플래그" />
+ ),
+ cell: ({ row }) => {
+ const shortList = row.original.shortList as boolean
+ const returnYn = row.original.returnYn as boolean
+ const cpRequestYn = row.original.cpRequestYn as boolean
+ const prjectGtcYn = row.original.prjectGtcYn as boolean
+
+ return (
+ <div className="flex flex-wrap gap-1">
+ {shortList && (
+ <Badge variant="secondary" className="text-xs">
+ <CheckCircle2 className="h-3 w-3 mr-1" />
+ Short List
+ </Badge>
+ )}
+ {returnYn && (
+ <Badge variant="outline" className="text-xs">
+ Return
+ </Badge>
+ )}
+ {cpRequestYn && (
+ <Badge variant="outline" className="text-xs">
+ CP Request
+ </Badge>
+ )}
+ {prjectGtcYn && (
+ <Badge variant="outline" className="text-xs">
+ GTC
+ </Badge>
+ )}
+ </div>
+ )
+ },
+ size: 150,
+ },
+
+ /** ───────────── 분류 정보 ───────────── */
+ {
+ id: "classification",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="분류" />
+ ),
+ cell: ({ row }) => {
+ const classification = row.original.classification as string
+ const sparepart = row.original.sparepart as string
+
+ return (
+ <div className="space-y-1">
+ {classification && (
+ <div className="text-sm font-medium max-w-[120px] truncate" title={classification}>
+ {classification}
+ </div>
+ )}
+ {sparepart && (
+ <Badge variant="outline" className="text-xs">
+ {sparepart}
+ </Badge>
+ )}
+ </div>
+ )
+ },
+ size: 120,
+ },
+
+ /** ───────────── 리비전 정보 ───────────── */
+ {
+ accessorKey: "returnRevision",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="리비전" />
+ ),
+ cell: ({ row }) => {
+ const revision = row.getValue("returnRevision") as number
+ return revision ? (
+ <Badge variant="outline">
+ Rev. {revision}
+ </Badge>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ )
+ },
+ size: 80,
+ },
+
+ /** ───────────── 등록/수정 정보 ───────────── */
+ {
+ accessorKey: "createdAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="등록일" />
+ ),
+ cell: ({ row }) => {
+ const created = row.getValue("createdAt") as Date
+ const updated = row.original.updatedAt as Date
+
+ return (
+ <div className="space-y-1">
+ <div className="text-sm">{formatDate(created)}</div>
+ {updated && new Date(updated) > new Date(created) && (
+ <div className="text-xs text-blue-600">
+ 수정: {formatDate(updated)}
+ </div>
+ )}
+ </div>
+ )
+ },
+ size: 120,
+ },
+
+ /** ───────────── 액션 ───────────── */
+ {
+ id: "actions",
+ enableHiding: false,
+ 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 onClick={() => onSelectDetail?.(row.original)}>
+ <Eye className="mr-2 h-4 w-4" />
+ 상세 보기
+ </DropdownMenuItem>
+ <DropdownMenuItem>
+ <MessageSquare className="mr-2 h-4 w-4" />
+ 벤더 응답 보기
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem>
+ <Settings className="mr-2 h-4 w-4" />
+ 설정 수정
+ </DropdownMenuItem>
+ <DropdownMenuItem className="text-red-600">
+ <XCircle className="mr-2 h-4 w-4" />
+ 삭제
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ )
+ },
+ size: 40,
+ },
+ ]
+} \ No newline at end of file