summaryrefslogtreecommitdiff
path: root/lib/vendors
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendors')
-rw-r--r--lib/vendors/contract-history-service.ts182
-rw-r--r--lib/vendors/contract-history/contract-history-table-columns.tsx412
-rw-r--r--lib/vendors/contract-history/contract-history-table.tsx240
3 files changed, 834 insertions, 0 deletions
diff --git a/lib/vendors/contract-history-service.ts b/lib/vendors/contract-history-service.ts
new file mode 100644
index 00000000..2aebcd8b
--- /dev/null
+++ b/lib/vendors/contract-history-service.ts
@@ -0,0 +1,182 @@
+'use server'
+
+import db from "@/db/db"
+import { eq, desc, and, sql } from "drizzle-orm"
+import { contractsDetailView, ContractDetailParsed } from "@/db/schema/contract"
+
+export interface ContractHistoryFilters {
+ vendorId?: number
+ projectId?: number
+ status?: string
+ startDate?: Date
+ endDate?: Date
+ contractNo?: string
+ search?: string
+}
+
+export interface ContractHistoryResult {
+ data: ContractDetailParsed[]
+ totalCount: number
+ pageCount: number
+}
+
+export interface ContractHistoryQueryOptions {
+ page?: number
+ pageSize?: number
+ sortBy?: string
+ sortOrder?: 'asc' | 'desc'
+ filters?: ContractHistoryFilters
+}
+
+/**
+ * 벤더별 계약 히스토리 조회 (contractsDetailView 사용)
+ */
+export async function getVendorContractHistory(
+ vendorId: number,
+ options: ContractHistoryQueryOptions = {}
+): Promise<ContractHistoryResult> {
+ const {
+ page = 1,
+ pageSize = 10,
+ sortBy = 'createdAt',
+ sortOrder = 'desc',
+ filters = {}
+ } = options
+
+ const offset = (page - 1) * pageSize
+
+ // where 조건들을 한 번에 구성
+ const conditions = [eq(contractsDetailView.vendorId, vendorId)]
+
+ if (filters.projectId) {
+ conditions.push(eq(contractsDetailView.projectId, filters.projectId))
+ }
+
+ if (filters.status) {
+ conditions.push(eq(contractsDetailView.status, filters.status))
+ }
+
+ if (filters.startDate) {
+ conditions.push(sql`${contractsDetailView.startDate} >= ${filters.startDate}`)
+ }
+
+ if (filters.endDate) {
+ conditions.push(sql`${contractsDetailView.endDate} <= ${filters.endDate}`)
+ }
+
+ if (filters.contractNo) {
+ conditions.push(sql`${contractsDetailView.contractNo} ILIKE ${`%${filters.contractNo}%`}`)
+ }
+
+ if (filters.search) {
+ conditions.push(sql`(
+ ${contractsDetailView.contractNo} ILIKE ${`%${filters.search}%`} OR
+ ${contractsDetailView.contractName} ILIKE ${`%${filters.search}%`} OR
+ ${contractsDetailView.projectName} ILIKE ${`%${filters.search}%`}
+ )`)
+ }
+
+ const whereCondition = conditions.length === 1 ? conditions[0] : and(...conditions)
+
+ // 정렬 설정
+ const orderByField = sortBy === 'createdAt' ? contractsDetailView.createdAt : contractsDetailView.contractNo
+ const orderBy = sortOrder === 'desc' ? desc(orderByField) : orderByField
+
+ // 데이터 조회
+ const data = await db
+ .select()
+ .from(contractsDetailView)
+ .where(whereCondition)
+ .orderBy(orderBy)
+ .limit(pageSize)
+ .offset(offset)
+
+ // 전체 개수 조회
+ const [{ count }] = await db
+ .select({ count: sql<number>`count(*)` })
+ .from(contractsDetailView)
+ .where(whereCondition)
+
+ const totalCount = count
+ const pageCount = Math.ceil(totalCount / pageSize)
+
+ // ContractDetailParsed 타입으로 변환
+ const parsedData: ContractDetailParsed[] = data.map(item => ({
+ ...item,
+ envelopes: JSON.parse(item.envelopes || '[]'),
+ items: JSON.parse(item.items || '[]')
+ }))
+
+ return {
+ data: parsedData,
+ totalCount,
+ pageCount
+ }
+}
+
+/**
+ * 벤더별 계약 히스토리 조회 (contractsDetailView 사용)
+ * contract.ts 스키마를 기준으로 데이터를 가져옴
+ */
+export async function getVendorContractHistoryExtended(
+ vendorId: number,
+ options: ContractHistoryQueryOptions = {}
+): Promise<ContractHistoryResult> {
+ // 기본 함수와 동일한 로직 사용
+ return getVendorContractHistory(vendorId, options)
+}
+
+/**
+ * 무한 스크롤용 계약 히스토리 조회
+ */
+export async function getVendorContractHistoryInfinite(
+ vendorId: number,
+ cursor?: number,
+ pageSize: number = 50,
+ filters?: ContractHistoryFilters
+): Promise<{
+ data: ContractDetailParsed[]
+ hasNextPage: boolean
+ nextCursor?: number
+}> {
+ // where 조건들을 한 번에 구성
+ const conditions = [eq(contractsDetailView.vendorId, vendorId)]
+
+ if (cursor) {
+ conditions.push(sql`${contractsDetailView.id} < ${cursor}`)
+ }
+
+ if (filters?.status) {
+ conditions.push(eq(contractsDetailView.status, filters.status))
+ }
+
+ if (filters?.contractNo) {
+ conditions.push(sql`${contractsDetailView.contractNo} ILIKE ${`%${filters.contractNo}%`}`)
+ }
+
+ const whereCondition = conditions.length === 1 ? conditions[0] : and(...conditions)
+
+ const data = await db
+ .select()
+ .from(contractsDetailView)
+ .where(whereCondition)
+ .orderBy(desc(contractsDetailView.createdAt))
+ .limit(pageSize + 1) // 다음 페이지 확인용 +1
+
+ const hasNextPage = data.length > pageSize
+ const items = hasNextPage ? data.slice(0, -1) : data
+ const nextCursor = hasNextPage ? items[items.length - 1]?.id : undefined
+
+ // ContractDetailParsed 타입으로 변환
+ const parsedData: ContractDetailParsed[] = items.map(item => ({
+ ...item,
+ envelopes: JSON.parse(item.envelopes || '[]'),
+ items: JSON.parse(item.items || '[]')
+ }))
+
+ return {
+ data: parsedData,
+ hasNextPage,
+ nextCursor
+ }
+} \ No newline at end of file
diff --git a/lib/vendors/contract-history/contract-history-table-columns.tsx b/lib/vendors/contract-history/contract-history-table-columns.tsx
new file mode 100644
index 00000000..a25f8f33
--- /dev/null
+++ b/lib/vendors/contract-history/contract-history-table-columns.tsx
@@ -0,0 +1,412 @@
+/**
+ * 계약히스토리 테이블 컬럼 설정
+ *
+ *
+ * 컬럼목록:
+ * - 선택
+ * - PO/계약번호
+ * - Rev. / 품번
+ * - 계약상태
+ * - 프로젝트
+ * - PKG No.
+ * - PKG 명
+ * - 자재그룹코드
+ * - 자재그룹명
+ * - 지불조건
+ * - Incoterms
+ * - 선적지
+ * - 계약납기일
+ * - L/C No.
+ * - 연동제대상
+ * - 통화
+ * - 계약금액
+ * - 선급금
+ * - 납품대금
+ * - 유보금
+ * - PO/계약발송일
+ * - PO/계약체결일
+ * - 계약서
+ * - 계약담당자
+ * - 계약상세
+ */
+
+"use client"
+
+import * as React from "react"
+import { type DataTableRowAction } from "@/types/table"
+import { type ColumnDef } from "@tanstack/react-table"
+import { Ellipsis, FileText, User, Eye } 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,
+ DropdownMenuShortcut,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+
+import { ContractDetailParsed } from "@/db/schema/contract"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+
+
+
+
+// 간단한 숫자 포맷팅 함수
+const formatNumber = (value: number): string => {
+ return new Intl.NumberFormat('ko-KR').format(value)
+}
+
+interface GetColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<ContractDetailParsed> | null>>;
+}
+
+/**
+ * 계약 히스토리 테이블 컬럼 정의 (간단 버전)
+ */
+export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<ContractDetailParsed>[] {
+ 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,
+ },
+
+ // PO/계약번호
+ {
+ accessorKey: "contractNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약번호" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // Rev. / 품번 (contract.contractVersion 사용)
+ {
+ accessorKey: "contractVersion",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Rev. / 품번" />
+ ),
+ cell: ({ cell }) => {
+ const value = cell.getValue()
+ return value ? `Rev.${value}` : <span className="text-muted-foreground">-</span>
+ },
+ },
+
+ // 계약상태
+ {
+ accessorKey: "status",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약상태" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // 프로젝트
+ {
+ accessorKey: "projectName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="프로젝트" />
+ ),
+ cell: ({ row }) => (
+ <div className="flex flex-col">
+ <span className="font-medium">{row.original.projectName}</span>
+ <span className="text-xs text-muted-foreground">{row.original.projectCode}</span>
+ </div>
+ ),
+ },
+
+ // PKG No.
+ {
+ accessorKey: "projectCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PKG No." />
+ ),
+ cell: ({ row }) => row.original.projectCode ? <Badge variant="outline">{row.original.projectCode}</Badge> : <span className="text-muted-foreground">-</span>,
+ },
+
+ // 벤더명
+ {
+ accessorKey: "vendorName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="협력업체" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // 계약명
+ {
+ accessorKey: "contractName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약명" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // 자재그룹코드 (지원되지 않음)
+ {
+ accessorKey: "materialGroupCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="자재그룹코드" />
+ ),
+ cell: () => <span className="text-muted-foreground">-</span>,
+ },
+
+ // 자재그룹명 (지원되지 않음)
+ {
+ accessorKey: "materialGroupName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="자재그룹명" />
+ ),
+ cell: () => <span className="text-muted-foreground">-</span>,
+ },
+
+ // 지불조건
+ {
+ accessorKey: "paymentTerms",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="지불조건" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // Incoterms
+ {
+ accessorKey: "deliveryTerms",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Incoterms" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // 선적지
+ {
+ accessorKey: "shippmentPlace",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="선적지" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // 계약납기일
+ {
+ accessorKey: "deliveryDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약납기일" />
+ ),
+ cell: ({ cell }) => {
+ const value = cell.getValue()
+ if (value instanceof Date) return formatDate(value, "KR")
+ if (typeof value === "string") return formatDate(new Date(value), "KR")
+ return ""
+ },
+ },
+
+ // L/C No.
+ {
+ accessorKey: "lcNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="L/C No." />
+ ),
+ cell: () => <span className="text-muted-foreground">-</span>,
+ },
+
+ // 연동제대상
+ {
+ accessorKey: "priceIndexYn",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="연동제대상" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // 통화
+ {
+ accessorKey: "currency",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="통화" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // 계약금액
+ {
+ accessorKey: "totalAmount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약금액" />
+ ),
+ cell: ({ cell }) => {
+ const value = cell.getValue()
+ return typeof value === "number" ? formatNumber(value) : value ?? ""
+ },
+ },
+
+ // 선급금
+ {
+ accessorKey: "advancePaymentYn",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="선급금" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // 납품장소
+ {
+ accessorKey: "deliveryLocation",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="납품장소" />
+ ),
+ cell: ({ cell }) => cell.getValue() ?? "",
+ },
+
+ // 유보금
+ {
+ accessorKey: "retentionAmount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="유보금" />
+ ),
+ cell: () => <span className="text-muted-foreground">-</span>,
+ },
+
+ // PO/계약발송일
+ {
+ accessorKey: "startDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약발송일" />
+ ),
+ cell: ({ cell }) => {
+ const value = cell.getValue()
+ if (value instanceof Date) return formatDate(value, "KR")
+ if (typeof value === "string") return formatDate(new Date(value), "KR")
+ return ""
+ },
+ },
+
+ // PO/계약체결일
+ {
+ accessorKey: "electronicApprovalDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약체결일" />
+ ),
+ cell: ({ cell }) => {
+ const value = cell.getValue()
+ if (value instanceof Date) return formatDate(value, "KR")
+ if (typeof value === "string") return formatDate(new Date(value), "KR")
+ return ""
+ },
+ },
+
+ // 전자서명상태
+ {
+ accessorKey: "hasSignature",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="전자서명상태" />
+ ),
+ cell: ({ cell }) => {
+ const hasSignature = cell.getValue()
+ return hasSignature ?
+ <Badge variant="default">서명완료</Badge> :
+ <Badge variant="secondary">미서명</Badge>
+ },
+ },
+
+ // 계약서 (지원되지 않음)
+ {
+ accessorKey: "contractDocument",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약서" />
+ ),
+ cell: () => (
+ <Button variant="ghost" size="sm" className="h-8 px-2" disabled>
+ <FileText className="size-4 mr-1" />
+ 보기
+ </Button>
+ ),
+ },
+
+ // 계약담당자 (지원되지 않음)
+ {
+ accessorKey: "contractManager",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약담당자" />
+ ),
+ cell: () => (
+ <div className="flex items-center text-muted-foreground">
+ <User className="size-4 mr-2" />
+ <span>-</span>
+ </div>
+ ),
+ },
+
+ // 계약상세 (Actions)
+ {
+ accessorKey: "contractDetail",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약상세" />
+ ),
+ cell: ({ row }) => (
+ <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-40">
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "view" })}
+ >
+ <Eye className="size-4 mr-2" />
+ 상세보기
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "update" })}
+ >
+ 수정
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "delete" })}
+ >
+ 삭제
+ <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ ),
+ size: 40,
+ enableSorting: false,
+ enableHiding: false,
+ },
+ ]
+} \ No newline at end of file
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>
+ )
+}