summaryrefslogtreecommitdiff
path: root/lib/pcr/table/pcr-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pcr/table/pcr-table.tsx')
-rw-r--r--lib/pcr/table/pcr-table.tsx396
1 files changed, 396 insertions, 0 deletions
diff --git a/lib/pcr/table/pcr-table.tsx b/lib/pcr/table/pcr-table.tsx
new file mode 100644
index 00000000..6538e820
--- /dev/null
+++ b/lib/pcr/table/pcr-table.tsx
@@ -0,0 +1,396 @@
+"use client"
+
+import * as React from "react"
+import { useSearchParams } from "next/navigation"
+import type {
+ DataTableRowAction,
+} from "@/types/table"
+import {
+ ResizablePanelGroup,
+ ResizablePanel,
+ ResizableHandle,
+} from "@/components/ui/resizable"
+
+import { useDataTable } from "@/hooks/use-data-table"
+import { DataTable } from "@/components/data-table/data-table"
+import { getColumns } from "./pcr-table-column"
+import { useEffect, useMemo } from "react"
+import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
+import { PcrTableToolbarActions } from "./pcr-table-toolbar-actions"
+import { getPcrPoList } from "@/lib/pcr/service"
+import { useTablePresets } from "@/components/data-table/use-table-presets"
+import { PcrDetailTables } from "./detail-table/pcr-detail-table"
+import { EditPcrSheet } from "./edit-pcr-sheet"
+import { cn } from "@/lib/utils"
+import { PcrPoData } from "@/lib/pcr/types"
+import type {
+ DataTableAdvancedFilterField,
+ DataTableFilterField,
+} from "@/types/table"
+
+interface PcrTableProps {
+ tableData: Awaited<ReturnType<typeof getPcrPoList>>
+ className?: string;
+ calculatedHeight?: string;
+ isEvcpPage?: boolean; // EvcP 페이지인지 여부
+ isPartnersPage?: boolean; // Partners 페이지인지 여부
+ currentVendorId?: number; // Partners 페이지에서 현재 사용자의 vendorId
+}
+
+export function PcrTable({
+ tableData,
+ className,
+ calculatedHeight,
+ isEvcpPage = false,
+ isPartnersPage = false,
+ currentVendorId,
+}: PcrTableProps) {
+ const searchParams = useSearchParams()
+
+
+ // 선택된 PCR_PO 상태
+ const [selectedPcrPo, setSelectedPcrPo] = React.useState<PcrPoData | null>(null)
+
+ // Edit sheet 상태
+ const [editSheetOpen, setEditSheetOpen] = React.useState(false)
+ const [editingPcr, setEditingPcr] = React.useState<PcrPoData | null>(null)
+
+
+ // 패널 collapse 상태
+ const [panelHeight, setPanelHeight] = React.useState<number>(55)
+
+ // RFQListTable 컴포넌트 내부의 rowAction 처리 부분 수정
+ const [rowAction, setRowAction] = React.useState<DataTableRowAction<PcrPoData> | null>(null)
+
+ // 고정 높이 설정을 위한 상수 (실제 측정값으로 조정 필요)
+ const LAYOUT_HEADER_HEIGHT = 64 // Layout Header 높이
+ const LAYOUT_FOOTER_HEIGHT = 60 // Layout Footer 높이 (있다면 실제 값)
+ const LOCAL_HEADER_HEIGHT = 72 // 로컬 헤더 바 높이 (p-4 + border)
+
+ console.log(calculatedHeight)
+
+ // 테이블 컨텐츠 높이 - 전달받은 높이에서 로컬 헤더 제외
+ const FIXED_TABLE_HEIGHT = calculatedHeight
+ ? `calc(${calculatedHeight} - ${LOCAL_HEADER_HEIGHT}px)`
+ : `calc(100vh - ${LAYOUT_HEADER_HEIGHT + LAYOUT_FOOTER_HEIGHT + LOCAL_HEADER_HEIGHT+76}px)` // fallback
+
+ // 데이터는 props로 직접 전달받음
+
+ // 초기 설정 정의
+ const initialSettings = React.useMemo(() => ({
+ page: parseInt(searchParams?.get('page') || '1'),
+ perPage: parseInt(searchParams?.get('perPage') || '10'),
+ sort: searchParams?.get('sort') ? JSON.parse(searchParams.get('sort')!) : [{ id: "createdAt", desc: true }],
+ columnVisibility: {},
+ columnOrder: [],
+ pinnedColumns: { left: [], right: [] },
+ filters: [],
+ joinOperator: "and" as const,
+ basicFilters: [],
+ basicJoinOperator: "and" as const,
+ search: "",
+ }), [searchParams])
+
+ // DB 기반 프리셋 훅 사용
+ const {
+ getCurrentSettings,
+ } = useTablePresets<PcrPoData>('pcr-po-table', initialSettings)
+
+
+ // 행 액션 처리
+ useEffect(() => {
+ if (rowAction) {
+ switch (rowAction.type) {
+ case "select":
+ // 객체 참조 안정화를 위해 필요한 필드만 추출
+ const pcrPoData = rowAction.row.original;
+ console.log("Row action select - PCR_PO 데이터:", pcrPoData)
+ setSelectedPcrPo({
+ id: pcrPoData.id,
+ no: pcrPoData.no,
+ pcrApprovalStatus: pcrPoData.pcrApprovalStatus,
+ changeType: pcrPoData.changeType,
+ details: pcrPoData.details || undefined,
+ project: pcrPoData.project || undefined,
+ pcrRequestDate: pcrPoData.pcrRequestDate,
+ poContractNumber: pcrPoData.poContractNumber,
+ revItemNumber: pcrPoData.revItemNumber || undefined,
+ purchaseContractManager: pcrPoData.purchaseContractManager || undefined,
+ pcrCreator: pcrPoData.pcrCreator || undefined,
+ poContractAmountBefore: pcrPoData.poContractAmountBefore ? Number(pcrPoData.poContractAmountBefore) : undefined as number | undefined,
+ poContractAmountAfter: pcrPoData.poContractAmountAfter ? Number(pcrPoData.poContractAmountAfter) : undefined as number | undefined,
+ contractCurrency: pcrPoData.contractCurrency || "KRW",
+ pcrReason: pcrPoData.pcrReason || undefined,
+ detailsReason: pcrPoData.detailsReason || undefined,
+ rejectionReason: pcrPoData.rejectionReason || undefined,
+ pcrResponseDate: pcrPoData.pcrResponseDate || undefined,
+ vendorId: pcrPoData.vendorId || undefined,
+ vendorName: pcrPoData.vendorName || undefined,
+ createdBy: pcrPoData.createdBy,
+ updatedBy: pcrPoData.updatedBy,
+ createdAt: pcrPoData.createdAt,
+ updatedAt: pcrPoData.updatedAt,
+ });
+ break;
+ case "update":
+ // PCR_PO 수정 시트 열기
+ setEditingPcr(rowAction.row.original)
+ setEditSheetOpen(true)
+ break;
+ case "delete":
+ console.log("Delete PCR_PO:", rowAction.row.original)
+ break;
+ }
+ setRowAction(null)
+ }
+ }, [rowAction])
+
+ const columns = React.useMemo(
+ () => getColumns({
+ setRowAction,
+ isEvcpPage,
+ }),
+ [setRowAction, isEvcpPage]
+ )
+
+ // 필터 필드 정의
+ const filterFields: DataTableFilterField<PcrPoData>[] = []
+
+ const advancedFilterFields: DataTableAdvancedFilterField<PcrPoData>[] = [
+ {
+ id: "pcrApprovalStatus",
+ label: "PCR 승인상태",
+ type: "multi-select",
+ options: [
+ { label: "승인대기", value: "승인대기" },
+ { label: "승인완료", value: "승인완료" },
+ { label: "거절", value: "거절" },
+ { label: "취소", value: "취소" },
+ ],
+ },
+ {
+ id: "changeType",
+ label: "변경구분",
+ type: "multi-select",
+ options: [
+ { label: "수량변경", value: "QUANTITY" },
+ { label: "금액변경", value: "AMOUNT" },
+ { label: "기간변경", value: "PERIOD" },
+ { label: "품목변경", value: "ITEM" },
+ { label: "기타", value: "OTHER" },
+ ],
+ },
+ {
+ id: "poContractNumber",
+ label: "PO/계약번호",
+ type: "text",
+ },
+ {
+ id: "project",
+ label: "프로젝트",
+ type: "text",
+ },
+ ...(isEvcpPage ? [{
+ id: "vendorName" as const,
+ label: "협력업체",
+ type: "text" as const,
+ }] : []),
+ {
+ id: "pcrRequestDate",
+ label: "PCR 요청일자",
+ type: "date",
+ },
+ {
+ id: "createdAt",
+ label: "생성일",
+ type: "date",
+ },
+ ]
+
+
+ // 현재 설정 가져오기
+ const currentSettings = useMemo(() => {
+ return getCurrentSettings()
+ }, [getCurrentSettings])
+
+ // useDataTable 초기 상태 설정
+ const initialState = useMemo(() => {
+ return {
+ sorting: initialSettings.sort.filter((sortItem: any) => {
+ const columnExists = columns.some((col: any) => col.accessorKey === sortItem.id)
+ return columnExists
+ }) as any,
+ columnVisibility: currentSettings.columnVisibility,
+ columnPinning: currentSettings.pinnedColumns,
+ }
+ }, [currentSettings, initialSettings.sort, columns])
+
+ // useDataTable 훅 설정
+ const { table } = useDataTable({
+ data: tableData?.data || [],
+ columns: columns as any,
+ pageCount: tableData?.pageCount || 0,
+ filterFields,
+ enablePinning: true,
+ enableAdvancedFilter: true,
+ initialState,
+ getRowId: (originalRow) => String(originalRow.id),
+ shallow: false,
+ clearOnDefault: true,
+ columnResizeMode: "onEnd",
+ })
+
+ // 선택된 행들 감시하여 selectedPcrPo 설정 (checkbox selection)
+ React.useEffect(() => {
+ if (table) {
+ const selectedRows = table.getSelectedRowModel().rows
+ if (selectedRows.length === 1) {
+ const pcrPoData = selectedRows[0].original
+ const selectedData = {
+ id: pcrPoData.id,
+ no: (pcrPoData as any).no,
+ pcrApprovalStatus: pcrPoData.pcrApprovalStatus || "",
+ changeType: pcrPoData.changeType || "",
+ details: pcrPoData.details || undefined,
+ project: pcrPoData.project || undefined,
+ pcrRequestDate: pcrPoData.pcrRequestDate,
+ poContractNumber: pcrPoData.poContractNumber,
+ revItemNumber: pcrPoData.revItemNumber || undefined,
+ purchaseContractManager: pcrPoData.purchaseContractManager || undefined,
+ pcrCreator: pcrPoData.pcrCreator || undefined,
+ poContractAmountBefore: pcrPoData.poContractAmountBefore ? Number(pcrPoData.poContractAmountBefore) : undefined as number | undefined,
+ poContractAmountAfter: pcrPoData.poContractAmountAfter ? Number(pcrPoData.poContractAmountAfter) : undefined as number | undefined,
+ contractCurrency: pcrPoData.contractCurrency || "KRW",
+ pcrReason: pcrPoData.pcrReason || undefined,
+ detailsReason: pcrPoData.detailsReason || undefined,
+ rejectionReason: pcrPoData.rejectionReason || undefined,
+ pcrResponseDate: pcrPoData.pcrResponseDate || undefined,
+ vendorId: pcrPoData.vendorId || undefined,
+ vendorName: pcrPoData.vendorName || undefined,
+ createdBy: pcrPoData.createdBy,
+ updatedBy: pcrPoData.updatedBy,
+ createdAt: pcrPoData.createdAt,
+ updatedAt: pcrPoData.updatedAt,
+ }
+ setSelectedPcrPo(selectedData)
+ } else if (selectedRows.length === 0) {
+ // 선택이 해제되었을 때는 selectedPcrPo를 null로 설정하지 않음
+ // row action select가 우선권을 가짐
+ }
+ }
+ }, [table?.getSelectedRowModel().rows])
+
+
+ return (
+ <div
+ className={cn("flex flex-col relative", className)}
+ style={{ height: calculatedHeight }}
+ >
+
+ {/* Main Content */}
+ <div
+ className="flex flex-col"
+ style={{
+ height: '100%'
+ }}
+ >
+ {/* Header Bar - 고정 높이 */}
+ <div
+ className="flex items-center justify-between p-4 bg-background border-b"
+ style={{
+ height: `${LOCAL_HEADER_HEIGHT}px`,
+ flexShrink: 0
+ }}
+ >
+
+ {/* Right side info */}
+ <div className="text-sm text-muted-foreground">
+ {tableData && (
+ <span>총 {tableData.totalCount || 0}건</span>
+ )}
+ </div>
+ </div>
+
+ {/* Table Content Area - 계산된 높이 사용 */}
+ <div
+ className="relative bg-background"
+ style={{
+ height: FIXED_TABLE_HEIGHT,
+ display: 'grid',
+ gridTemplateRows: '1fr',
+ gridTemplateColumns: '1fr'
+ }}
+ >
+ <ResizablePanelGroup
+ direction="vertical"
+ className="w-full h-full"
+ >
+ <ResizablePanel
+ defaultSize={60}
+ minSize={25}
+ maxSize={75}
+ collapsible={false}
+ onResize={(size) => {
+ setPanelHeight(size)
+ }}
+ className="flex flex-col overflow-hidden"
+ >
+ {/* 상단 테이블 영역 */}
+ <div className="flex-1 min-h-0 overflow-hidden">
+ <DataTable
+ table={table}
+ maxHeight={`${panelHeight*0.5}vh`}
+ >
+ <DataTableAdvancedToolbar
+ table={table as any}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ >
+ <div className="flex items-center gap-2">
+ <PcrTableToolbarActions
+ selection={table}
+ onRefresh={() => {}}
+ isEvcpPage={isEvcpPage}
+ isPartnersPage={isPartnersPage}
+ currentVendorId={currentVendorId}
+ />
+ </div>
+ </DataTableAdvancedToolbar>
+ </DataTable>
+ </div>
+ </ResizablePanel>
+
+ <ResizableHandle withHandle />
+
+ <ResizablePanel
+ minSize={25}
+ defaultSize={40}
+ collapsible={false}
+ className="flex flex-col overflow-hidden"
+ >
+ {/* 하단 상세 테이블 영역 */}
+ <div className="flex-1 min-h-0 overflow-hidden bg-background">
+ <PcrDetailTables
+ selectedPcrPo={selectedPcrPo}
+ maxHeight={`${(100-panelHeight)*0.4}vh`}
+ isPartnersPage={isPartnersPage}
+ />
+ </div>
+ </ResizablePanel>
+ </ResizablePanelGroup>
+ </div>
+ </div>
+
+ {/* PCR 수정 시트 */}
+ <EditPcrSheet
+ open={editSheetOpen}
+ onOpenChange={setEditSheetOpen}
+ pcrData={editingPcr}
+ onSuccess={() => {
+ // 데이터 새로고침
+ window.location.reload()
+ }}
+ />
+ </div>
+ )
+}