From ee77f36b1ceece1236d45fba102c3ea410acebc1 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 11 Sep 2025 11:20:42 +0000 Subject: (최겸) 구매 계약 메인 및 상세 기능 개발(템플릿 연동 및 계약 전달 개발 필요) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/general-contracts-table-columns.tsx | 547 +++++++++++++++++++++ 1 file changed, 547 insertions(+) create mode 100644 lib/general-contracts/main/general-contracts-table-columns.tsx (limited to 'lib/general-contracts/main/general-contracts-table-columns.tsx') diff --git a/lib/general-contracts/main/general-contracts-table-columns.tsx b/lib/general-contracts/main/general-contracts-table-columns.tsx new file mode 100644 index 00000000..394a2cf5 --- /dev/null +++ b/lib/general-contracts/main/general-contracts-table-columns.tsx @@ -0,0 +1,547 @@ +"use client" + +import * as React from "react" +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, MoreHorizontal +} from "lucide-react" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" +import { DataTableRowAction } from "@/types/table" +import { formatDate } from "@/lib/utils" + +// 일반계약 리스트 아이템 타입 정의 +export interface GeneralContractListItem { + id: number + contractNumber: string + revision: number + status: string + category: string + type: string + executionMethod: string + name: string + selectionMethod?: string + startDate: string + endDate: string + validityEndDate?: string + contractScope?: string + specificationType?: string + specificationManualText?: string + contractAmount?: number | string | null + totalAmount?: number | string | null + currency?: string + registeredAt: string + signedAt?: string + linkedPoNumber?: string + linkedRfqOrItb?: string + linkedBidNumber?: string + lastUpdatedAt: string + notes?: string + vendorName?: string + vendorCode?: string + managerName?: string + lastUpdatedByName?: string +} + +interface GetColumnsProps { + setRowAction: React.Dispatch | null>> +} + +// 상태별 배지 색상 +const getStatusBadgeVariant = (status: string) => { + switch (status) { + case 'Draft': + return 'outline' + case 'Request to Review': + case 'Confirm to Review': + return 'secondary' + case 'Contract Accept Request': + return 'default' + case 'Complete the Contract': + return 'default' + case 'Reject to Accept Contract': + case 'Contract Delete': + return 'destructive' + default: + return 'outline' + } +} + +// 상태 텍스트 변환 +const getStatusText = (status: string) => { + switch (status) { + case 'Draft': + return '임시저장' + case 'Request to Review': + return '조건검토요청' + case 'Confirm to Review': + return '조건검토완료' + case 'Contract Accept Request': + return '계약승인요청' + case 'Complete the Contract': + return '계약체결' + case 'Reject to Accept Contract': + return '계약승인거절' + case 'Contract Delete': + return '계약폐기' + case 'PCR Request': + return 'PCR요청' + case 'VO Request': + return 'VO요청' + case 'PCR Accept': + return 'PCR승인' + case 'PCR Reject': + return 'PCR거절' + default: + return status + } +} + +// 계약구분 텍스트 변환 +const getCategoryText = (category: string) => { + switch (category) { + case '단가계약': + return '단가계약' + case '일반계약': + return '일반계약' + case '매각계약': + return '매각계약' + default: + return category + } +} + +// 계약종류 텍스트 변환 +const getTypeText = (type: string) => { + switch (type) { + case 'UP': + return '자재단가계약' + case 'LE': + return '임대차계약' + case 'IL': + return '개별운송계약' + case 'AL': + return '연간운송계약' + case 'OS': + return '외주용역계약' + case 'OW': + return '도급계약' + case 'IS': + return '검사계약' + case 'LO': + return 'LOI' + case 'FA': + return 'FA' + case 'SC': + return '납품합의계약' + case 'OF': + return '클레임상계계약' + case 'AW': + return '사전작업합의' + case 'AD': + return '사전납품합의' + case 'AM': + return '설계계약' + case 'SC_SELL': + return '폐기물매각계약' + default: + return type + } +} + +// 체결방식 텍스트 변환 +const getExecutionMethodText = (method: string) => { + switch (method) { + case '전자계약': + return '전자계약' + case '오프라인계약': + return '오프라인계약' + default: + return method + } +} + +// 업체선정방법 텍스트 변환 +const getSelectionMethodText = (method?: string) => { + if (!method) return '-' + switch (method) { + case '견적': + return '견적' + case '입찰': + return '입찰' + case '기타': + return '기타' + default: + return method + } +} + +// 금액 포맷팅 +const formatCurrency = (amount: string | number | null | undefined, currency = 'KRW') => { + if (!amount && amount !== 0) return '-' + + const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount + if (isNaN(numAmount)) return '-' + + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: currency, + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(numAmount) +} + +export function getGeneralContractsColumns({ setRowAction }: GetColumnsProps): ColumnDef[] { + return [ + // ═══════════════════════════════════════════════════════════════ + // 선택 및 기본 정보 + // ═══════════════════════════════════════════════════════════════ + { + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!v)} + aria-label="select all" + className="translate-y-0.5" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!v)} + aria-label="select row" + className="translate-y-0.5" + /> + ), + size: 40, + enableSorting: false, + enableHiding: false, + }, + + // ░░░ 계약번호 ░░░ + { + accessorKey: "contractNumber", + header: ({ column }) => , + cell: ({ row }) => ( +
+ {row.original.contractNumber} + {row.original.revision > 0 && ( + + Rev.{row.original.revision} + + )} +
+ ), + size: 150, + meta: { excelHeader: "계약번호 (Rev.)" }, + }, + + // ░░░ 계약상태 ░░░ + { + accessorKey: "status", + header: ({ column }) => , + cell: ({ row }) => ( + + {getStatusText(row.original.status)} + + ), + size: 120, + meta: { excelHeader: "계약상태" }, + }, + + // ░░░ 계약명 ░░░ + { + accessorKey: "name", + header: ({ column }) => , + cell: ({ row }) => ( +
+ +
+ ), + size: 200, + meta: { excelHeader: "계약명" }, + }, + + // ═══════════════════════════════════════════════════════════════ + // 계약 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "계약 정보", + columns: [ + { + accessorKey: "category", + header: ({ column }) => , + cell: ({ row }) => ( + + {getCategoryText(row.original.category)} + + ), + size: 100, + meta: { excelHeader: "계약구분" }, + }, + + { + accessorKey: "type", + header: ({ column }) => , + cell: ({ row }) => ( + + {getTypeText(row.original.type)} + + ), + size: 120, + meta: { excelHeader: "계약종류" }, + }, + + { + accessorKey: "executionMethod", + header: ({ column }) => , + cell: ({ row }) => ( + + {getExecutionMethodText(row.original.executionMethod)} + + ), + size: 100, + meta: { excelHeader: "체결방식" }, + }, + + { + accessorKey: "selectionMethod", + header: ({ column }) => , + cell: ({ row }) => ( + + {getSelectionMethodText(row.original.selectionMethod)} + + ), + size: 200, + meta: { excelHeader: "업체선정방법" }, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 협력업체 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "협력업체", + columns: [ + { + accessorKey: "vendorName", + header: ({ column }) => , + cell: ({ row }) => ( +
+ {row.original.vendorName || '-'} + + {row.original.vendorCode ? row.original.vendorCode : "-"} + +
+ ), + size: 150, + meta: { excelHeader: "협력업체명" }, + }, + + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 기간 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "계약기간", + columns: [ + { + id: "contractPeriod", + header: ({ column }) => , + cell: ({ row }) => { + const startDate = row.original.startDate + const endDate = row.original.endDate + + if (!startDate || !endDate) return - + + const now = new Date() + const isActive = now >= new Date(startDate) && now <= new Date(endDate) + const isExpired = now > new Date(endDate) + + return ( +
+
+ {formatDate(startDate, "KR")} ~ {formatDate(endDate, "KR")} +
+ {isActive && ( + 진행중 + )} + {isExpired && ( + 만료 + )} +
+ ) + }, + size: 200, + meta: { excelHeader: "계약기간" }, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 금액 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "금액 정보", + columns: [ + { + accessorKey: "currency", + header: ({ column }) => , + cell: ({ row }) => ( + {row.original.currency || 'KRW'} + ), + size: 60, + meta: { excelHeader: "통화" }, + }, + + { + accessorKey: "contractAmount", + header: ({ column }) => , + cell: ({ row }) => ( + + {formatCurrency(row.original.contractAmount, row.original.currency)} + + ), + size: 200, + meta: { excelHeader: "계약금액" }, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 담당자 및 관리 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "관리 정보", + columns: [ + { + accessorKey: "managerName", + header: ({ column }) => , + cell: ({ row }) => ( +
+ {row.original.managerName || '-'} +
+ ), + size: 100, + meta: { excelHeader: "계약담당자" }, + }, + + { + accessorKey: "registeredAt", + header: ({ column }) => , + cell: ({ row }) => ( + {formatDate(row.original.registeredAt, "KR")} + ), + size: 100, + meta: { excelHeader: "계약등록일" }, + }, + + { + accessorKey: "signedAt", + header: ({ column }) => , + cell: ({ row }) => ( + + {row.original.signedAt ? formatDate(row.original.signedAt, "KR") : '-'} + + ), + size: 100, + meta: { excelHeader: "계약체결일" }, + }, + + { + accessorKey: "linkedPoNumber", + header: ({ column }) => , + cell: ({ row }) => ( + {row.original.linkedPoNumber || '-'} + ), + size: 140, + meta: { excelHeader: "연계 PO번호" }, + }, + + { + accessorKey: "lastUpdatedAt", + header: ({ column }) => , + cell: ({ row }) => ( + {formatDate(row.original.lastUpdatedAt, "KR")} + ), + size: 100, + meta: { excelHeader: "최종수정일" }, + }, + + { + accessorKey: "lastUpdatedByName", + header: ({ column }) => , + cell: ({ row }) => ( + {row.original.lastUpdatedByName || '-'} + ), + size: 100, + meta: { excelHeader: "최종수정자" }, + }, + ] + }, + + // ░░░ 비고 ░░░ + { + accessorKey: "notes", + header: ({ column }) => , + cell: ({ row }) => ( +
+ {row.original.notes || '-'} +
+ ), + size: 150, + meta: { excelHeader: "비고" }, + }, + + // ═══════════════════════════════════════════════════════════════ + // 액션 + // ═══════════════════════════════════════════════════════════════ + { + id: "actions", + header: "액션", + cell: ({ row }) => ( + + + + + + setRowAction({ row, type: "view" })}> + + 상세보기 + + setRowAction({ row, type: "update" })}> + + 수정 + + + + + ), + size: 50, + enableSorting: false, + enableHiding: false, + }, + ] +} -- cgit v1.2.3