diff options
Diffstat (limited to 'lib/vendors/contract-history/contract-history-table.tsx')
| -rw-r--r-- | lib/vendors/contract-history/contract-history-table.tsx | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/lib/vendors/contract-history/contract-history-table.tsx b/lib/vendors/contract-history/contract-history-table.tsx new file mode 100644 index 00000000..62831aaa --- /dev/null +++ b/lib/vendors/contract-history/contract-history-table.tsx @@ -0,0 +1,240 @@ +"use client" + +import * as React from "react" +import type { + DataTableAdvancedFilterField, + DataTableFilterField, + DataTableRowAction, +} from "@/types/table" + +import { useDataTable } from "@/hooks/use-data-table" +import { DataTable } from "@/components/data-table/data-table" +import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" + +import { getColumns } from "./contract-history-table-columns" +import { ContractDetailParsed } from "@/db/schema/contract" +import type { ColumnDef } from "@tanstack/react-table" +import { getVendorContractHistoryExtended } from "../contract-history-service" + +interface ContractHistoryTableProps { + vendorId: number + onRowAction?: (action: DataTableRowAction<ContractDetailParsed>) => void +} + +// 컬럼 정보를 기반으로 필터 필드 생성하는 유틸리티 함수 +function generateFilterFieldsFromColumns(columns: ColumnDef<ContractDetailParsed>[]): { + basic: DataTableFilterField<ContractDetailParsed>[] + advanced: DataTableAdvancedFilterField<ContractDetailParsed>[] +} { + const basicFields: DataTableFilterField<ContractDetailParsed>[] = [] + const advancedFields: DataTableAdvancedFilterField<ContractDetailParsed>[] = [] + + // 필터링에서 제외할 컬럼 ID들 + const excludeIds = new Set(['select', 'contractDetail']) + + columns.forEach((column) => { + // 타입 안전하게 accessorKey 추출 + const accessorKey = (column as { accessorKey?: string }).accessorKey + const header = (column as { header?: unknown }).header + + // 제외할 컬럼이나 accessorKey가 없는 경우 스킵 + if (!accessorKey || excludeIds.has(accessorKey)) return + + // 헤더에서 타이틀 추출 + let title = '' + + // accessorKey를 기반으로 한글 타이틀 매핑 (contract.ts 스키마 기준) + const titleMap: Record<string, string> = { + contractNo: 'PO/계약번호', + contractVersion: 'Rev. / 품번', + status: '계약상태', + projectName: '프로젝트', + projectCode: 'PKG No.', + vendorName: '협력업체', + contractName: '계약명', + materialGroupCode: '자재그룹코드', + materialGroupName: '자재그룹명', + paymentTerms: '지불조건', + deliveryTerms: 'Incoterms', + shippmentPlace: '선적지', + deliveryDate: '계약납기일', + deliveryLocation: '납품장소', + priceIndexYn: '연동제대상', + currency: '통화', + totalAmount: '계약금액', + advancePaymentYn: '선급금', + startDate: 'PO/계약발송일', + endDate: '계약종료일', + electronicApprovalDate: 'PO/계약체결일', + hasSignature: '전자서명상태', + partialShippingAllowed: '분할선적허용', + partialPaymentAllowed: '분할결제허용', + createdAt: '생성일', + updatedAt: '수정일', + netTotal: '순총액', + discount: '할인', + tax: '세금', + shippingFee: '배송비', + remarks: '비고', + version: '버전' + } + + // 매핑된 타이틀이 있으면 사용 + if (titleMap[accessorKey]) { + title = titleMap[accessorKey] + } else { + // 함수형 헤더에서 추출 시도 + if (typeof header === 'function') { + try { + const headerProps = header({ column: { id: accessorKey } }) + if (React.isValidElement(headerProps) && headerProps.props && typeof headerProps.props === 'object' && 'title' in headerProps.props) { + const props = headerProps.props as { title?: string } + title = props.title || '' + } + } catch { + // 헤더 함수 실행 실패 시 스킵 + } + } else if (typeof header === 'string') { + title = header + } + } + + if (!title) return + + // 필터 타입 결정 (간단한 휴리스틱) + const getFilterType = (key: string): "text" | "number" | "date" => { + if (key.includes('Date') || key.includes('date') || key.includes('At')) return 'date' + if (key.includes('Amount') || key.includes('amount') || key.includes('total') || key.includes('price')) return 'number' + return 'text' + } + + const filterType = getFilterType(accessorKey) + + // 기본 필터 (주요 필드만) + const importantFields = ['contractNo', 'contractName', 'status', 'projectName', 'vendorName', 'currency'] + if (importantFields.includes(accessorKey)) { + basicFields.push({ + id: accessorKey as keyof ContractDetailParsed, + label: title, + }) + } + + // 고급 필터 (모든 필터링 가능한 필드) + advancedFields.push({ + id: accessorKey as keyof ContractDetailParsed, + label: title, + type: filterType, + }) + }) + + return { basic: basicFields, advanced: advancedFields } +} + +export function ContractHistoryTable({ vendorId, onRowAction }: ContractHistoryTableProps) { + // Row action state 관리 + const [rowAction, setRowAction] = React.useState<DataTableRowAction<ContractDetailParsed> | null>(null) + + // 데이터 상태 관리 + const [serviceData, setServiceData] = React.useState<{ + data: ContractDetailParsed[] + pageCount: number + totalCount: number + }>({ data: [], pageCount: 0, totalCount: 0 }) + const [isLoading, setIsLoading] = React.useState(false) + + console.log('ContractHistoryTable data:', serviceData.data.length, 'contracts') + + // Row action이 발생하면 외부 핸들러 호출 + React.useEffect(() => { + if (rowAction && onRowAction) { + onRowAction(rowAction) + setRowAction(null) // Reset action after handling + } + }, [rowAction, onRowAction]) + + // 초기 데이터 로드 + React.useEffect(() => { + const loadInitialData = async () => { + setIsLoading(true) + try { + const result = await getVendorContractHistoryExtended(vendorId, { + page: 1, + pageSize: 10 + }) + setServiceData(result) + } catch (error) { + console.error('Failed to load contract history:', error) + setServiceData({ data: [], pageCount: 0, totalCount: 0 }) + } finally { + setIsLoading(false) + } + } + + loadInitialData() + }, [vendorId]) + + const columns = React.useMemo( + () => getColumns({ setRowAction }), + [] + ) + + // 컬럼 정보를 기반으로 동적으로 필터 필드 생성 + const { basic: filterFields, advanced: advancedFilterFields } = React.useMemo(() => { + return generateFilterFieldsFromColumns(columns) + }, [columns]) + + // useDataTable 훅 사용 + const { + table, + } = useDataTable({ + data: serviceData.data, + columns, + pageCount: serviceData.pageCount, + filterFields, + enablePinning: true, + enableAdvancedFilter: true, + initialState: { + sorting: [{ id: "contractNo", desc: false }], + }, + getRowId: (originalRow) => String(originalRow.id || 'unknown'), + shallow: false, + clearOnDefault: true, + }) + + return ( + <div className="w-full space-y-2.5 overflow-x-auto" style={{maxWidth:'100vw'}}> + + + + {/* 로딩 상태가 아닐 때만 테이블 렌더링 */} + {!isLoading ? ( + <> + {/* 도구 모음 */} + <DataTableAdvancedToolbar + table={table} + filterFields={advancedFilterFields} + shallow={false} + /> + + {/* 테이블 렌더링 */} + <DataTable + table={table} + compact={false} + autoSizeColumns={true} + /> + </> + ) : ( + /* 로딩 스켈레톤 */ + <div className="space-y-3"> + <div className="text-sm text-muted-foreground mb-4"> + 계약 히스토리를 불러오는 중입니다... + </div> + {Array.from({ length: 10 }).map((_, i) => ( + <div key={i} className="h-12 w-full bg-muted animate-pulse rounded" /> + ))} + </div> + )} + + </div> + ) +} |
