From 7b0c7c8e56fb027c729c953b0b87dab72156f661 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Mon, 15 Sep 2025 23:42:46 +0900
Subject: (김준회) 임시 견적요청 및 AVL detail 관련 수정사항 처리
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/avl/table/avl-table.tsx | 219 +++++++-------------------------------------
 1 file changed, 33 insertions(+), 186 deletions(-)

(limited to 'lib/avl/table/avl-table.tsx')

diff --git a/lib/avl/table/avl-table.tsx b/lib/avl/table/avl-table.tsx
index eb9b2079..45da6268 100644
--- a/lib/avl/table/avl-table.tsx
+++ b/lib/avl/table/avl-table.tsx
@@ -2,7 +2,7 @@
 
 import * as React from "react"
 import type {
-  DataTableFilterField,
+  DataTableAdvancedFilterField
 } from "@/types/table"
 
 import { useDataTable } from "@/hooks/use-data-table"
@@ -12,16 +12,17 @@ import { Button } from "@/components/ui/button"
 import { toast } from "sonner"
 
 import { getColumns } from "./avl-table-columns"
-import { createAvlListAction, updateAvlListAction, deleteAvlListAction, handleAvlActionAction } from "../service"
+import { updateAvlListAction, deleteAvlListAction, handleAvlActionAction } from "../service"
 import type { AvlListItem } from "../types"
 import { AvlHistoryModal, type AvlHistoryRecord } from "@/lib/avl/components/avl-history-modal"
+import { getAvlHistory } from "@/lib/avl/history-service"
 
 // 테이블 메타 타입 확장
 declare module "@tanstack/react-table" {
   interface TableMeta<TData> {
-    onCellUpdate?: (id: string, field: keyof TData, newValue: any) => Promise<void>
+    onCellUpdate?: (id: string, field: keyof TData, newValue: string | boolean) => Promise<void>
     onCellCancel?: (id: string, field: keyof TData) => void
-    onAction?: (action: string, data?: any) => void
+    onAction?: (action: string, data?: Partial<AvlListItem>) => void
     onSaveEmptyRow?: (tempId: string) => Promise<void>
     onCancelEmptyRow?: (tempId: string) => void
     isEmptyRow?: (id: string) => boolean
@@ -47,10 +48,6 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
   const [pendingChanges, setPendingChanges] = React.useState<Record<string, Partial<AvlListItem>>>({})
   const [isSaving, setIsSaving] = React.useState(false)
 
-  // 빈 행 관리 (신규 등록용)
-  const [emptyRows, setEmptyRows] = React.useState<Record<string, AvlListItem>>({})
-  const [isCreating, setIsCreating] = React.useState(false)
-
   // 히스토리 모달 관리
   const [historyModalOpen, setHistoryModalOpen] = React.useState(false)
   const [selectedAvlItem, setSelectedAvlItem] = React.useState<AvlListItem | null>(null)
@@ -58,36 +55,11 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
   // 히스토리 데이터 로드 함수
   const loadHistoryData = React.useCallback(async (avlItem: AvlListItem): Promise<AvlHistoryRecord[]> => {
     try {
-      // 현재 리비전의 스냅샷 데이터 (실제 저장된 데이터 사용)
-      const currentSnapshot = avlItem.vendorInfoSnapshot || []
-      
-      const historyData: AvlHistoryRecord[] = [
-        {
-          id: avlItem.id,
-          rev: avlItem.rev || 1,
-          createdAt: avlItem.createdAt || new Date().toISOString(),
-          createdBy: avlItem.createdBy || "system",
-          vendorInfoSnapshot: currentSnapshot,
-          changeDescription: "최신 리비전 (확정완료)"
-        }
-      ]
-      
-      // TODO: 실제 구현에서는 DB에서 이전 리비전들의 스냅샷 데이터를 조회해야 함
-      // 현재는 더미 데이터로 이전 리비전들을 시뮬레이션
-      if ((avlItem.rev || 1) > 1) {
-        for (let rev = (avlItem.rev || 1) - 1; rev >= 1; rev--) {
-          historyData.push({
-            id: avlItem.id + rev * 1000, // 임시 ID
-            rev,
-            createdAt: new Date(Date.now() - rev * 24 * 60 * 60 * 1000).toISOString(),
-            createdBy: "system",
-            vendorInfoSnapshot: [], // 이전 리비전의 스냅샷 데이터 (실제로는 DB에서 조회)
-            changeDescription: `리비전 ${rev} 변경사항`
-          })
-        }
-      }
-      
-      return historyData
+      const historyRecords = await getAvlHistory(avlItem)
+      return historyRecords.map(record => ({
+        ...record,
+        vendorInfoSnapshot: record.vendorInfoSnapshot || []
+      }))
     } catch (error) {
       console.error('히스토리 로드 실패:', error)
       toast.error("히스토리를 불러오는데 실패했습니다.")
@@ -96,7 +68,7 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
   }, [])
 
   // 필터 필드 정의
-  const filterFields: DataTableFilterField<AvlListItem>[] = [
+  const filterFields: DataTableAdvancedFilterField<AvlListItem>[] = [
     {
       id: "isTemplate",
       label: "AVL 분류",
@@ -105,6 +77,7 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
         { label: "프로젝트 AVL", value: "false" },
         { label: "표준 AVL", value: "true" },
       ],
+      type: "select"
     },
     {
       id: "constructionSector",
@@ -114,6 +87,7 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
         { label: "조선", value: "조선" },
         { label: "해양", value: "해양" },
       ],
+      type: "select"
     },
     {
       id: "htDivision",
@@ -123,17 +97,18 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
         { label: "H", value: "H" },
         { label: "T", value: "T" },
       ],
+      type: "select"
     },
   ]
 
 
   // 인라인 편집 핸들러 (일괄 저장용)
-  const handleCellUpdate = React.useCallback(async (id: string, field: keyof AvlListItem, newValue: any) => {
+  const handleCellUpdate = React.useCallback(async (id: string, field: keyof AvlListItem, newValue: string | boolean) => {
     const isEmptyRow = String(id).startsWith('temp-')
 
     if (isEmptyRow) {
-      // 빈 행의 경우 emptyRows 상태도 업데이트
-      setEmptyRows(prev => ({
+      // 빈 행의 경우 pendingChanges 상태도 업데이트
+      setPendingChanges(prev => ({
         ...prev,
         [id]: {
           ...prev[id],
@@ -157,15 +132,7 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
     const isEmptyRow = String(id).startsWith('temp-')
 
     if (isEmptyRow) {
-      // 빈 행의 경우 emptyRows와 pendingChanges 모두 취소
-      setEmptyRows(prev => ({
-        ...prev,
-        [id]: {
-          ...prev[id],
-          [field]: prev[id][field] // 원래 값으로 복원 (pendingChanges의 초기값 사용)
-        }
-      }))
-
+      // 빈 행의 경우 pendingChanges 취소
       setPendingChanges(prev => {
         const itemChanges = { ...prev[id] }
         delete itemChanges[field]
@@ -185,39 +152,9 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
   }, [])
 
   // 액션 핸들러
-  const handleAction = React.useCallback(async (action: string, data?: any) => {
+  const handleAction = React.useCallback(async (action: string, data?: Partial<AvlListItem>) => {
     try {
       switch (action) {
-        case 'new-registration':
-          // 신규 등록 - 빈 행 추가
-          const tempId = `temp-${Date.now()}`
-          const newEmptyRow: AvlListItem = {
-            id: tempId as any,
-            no: 0,
-            selected: false,
-            isTemplate: false,
-            constructionSector: "",
-            projectCode: "",
-            shipType: "",
-            avlKind: "",
-            htDivision: "",
-            rev: 1,
-            vendorInfoSnapshot: null,
-            createdAt: new Date().toISOString().split('T')[0],
-            updatedAt: new Date().toISOString().split('T')[0],
-            createdBy: "system",
-            updatedBy: "system",
-            registrant: "system",
-            lastModifier: "system",
-          }
-
-          setEmptyRows(prev => ({
-            ...prev,
-            [tempId]: newEmptyRow
-          }))
-          toast.success("신규 등록 행이 추가되었습니다.")
-          break
-
         case 'standard-registration':
           // 표준 AVL 등록
           const result = await handleAvlActionAction('standard-registration')
@@ -263,7 +200,13 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
             for (const [id, changes] of Object.entries(pendingChanges)) {
               if (String(id).startsWith('temp-')) continue // 빈 행은 제외
 
-              const result = await updateAvlListAction(Number(id), changes as any)
+              // id 속성을 명시적으로 추가
+              const updateData = {
+                ...changes,
+                id: Number(id)
+              }
+              
+              const result = await updateAvlListAction(Number(id), updateData)
               if (!result) {
                 throw new Error(`항목 ${id} 저장 실패`)
               }
@@ -330,95 +273,20 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
     }
   }, [pendingChanges, onRefresh, onRegistrationModeChange])
 
-  // 빈 행 저장 핸들러
-  const handleSaveEmptyRow = React.useCallback(async (tempId: string) => {
-    const emptyRow = emptyRows[tempId]
-    if (!emptyRow) return
-
-    try {
-      setIsCreating(true)
-
-      // 필수 필드 검증
-      if (!emptyRow.constructionSector || !emptyRow.avlKind) {
-        toast.error("공사부문과 AVL 종류는 필수 입력 항목입니다.")
-        return
-      }
-
-      // 빈 행 데이터를 생성 데이터로 변환
-      const createData = {
-        isTemplate: emptyRow.isTemplate,
-        constructionSector: emptyRow.constructionSector,
-        projectCode: emptyRow.projectCode || undefined,
-        shipType: emptyRow.shipType || undefined,
-        avlKind: emptyRow.avlKind,
-        htDivision: emptyRow.htDivision || undefined,
-        rev: emptyRow.rev,
-        createdBy: "system",
-        updatedBy: "system",
-      }
-
-      const result = await createAvlListAction(createData as any)
-      if (result) {
-        // 빈 행 제거 및 성공 메시지
-        setEmptyRows(prev => {
-          const newRows = { ...prev }
-          delete newRows[tempId]
-          return newRows
-        })
-
-        // pendingChanges에서도 제거
-        setPendingChanges(prev => {
-          const newChanges = { ...prev }
-          delete newChanges[tempId]
-          return newChanges
-        })
-
-        toast.success("새 항목이 등록되었습니다.")
-        onRefresh?.()
-      } else {
-        toast.error("등록에 실패했습니다.")
-      }
-    } catch (error) {
-      console.error('빈 행 저장 실패:', error)
-      toast.error("등록 중 오류가 발생했습니다.")
-    } finally {
-      setIsCreating(false)
-    }
-  }, [emptyRows, onRefresh])
-
-  // 빈 행 취소 핸들러
-  const handleCancelEmptyRow = React.useCallback((tempId: string) => {
-    setEmptyRows(prev => {
-      const newRows = { ...prev }
-      delete newRows[tempId]
-      return newRows
-    })
-
-    setPendingChanges(prev => {
-      const newChanges = { ...prev }
-      delete newChanges[tempId]
-      return newChanges
-    })
-
-    toast.info("등록이 취소되었습니다.")
-  }, [])
-
   // 빈 행 포함한 전체 데이터
   const allData = React.useMemo(() => {
     // 로딩 중에는 빈 데이터를 표시
     if (isLoading) {
       return []
     }
-    const emptyRowArray = Object.values(emptyRows)
-    return [...data, ...emptyRowArray]
-  }, [data, emptyRows, isLoading])
+    return [...data]
+  }, [data, isLoading])
 
   // 행 선택 처리 (1개만 선택 가능 - shi-vendor-po 방식)
   const handleRowSelect = React.useCallback((id: number, selected: boolean) => {
     if (selected) {
       setSelectedRows([id]) // 1개만 선택
       // 선택된 레코드 찾아서 부모 콜백 호출
-      const allData = isLoading ? [] : [...data, ...Object.values(emptyRows)]
       const selectedRow = allData.find(row => row.id === id)
       if (selectedRow) {
         onRowSelect?.(selectedRow)
@@ -427,18 +295,16 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
       setSelectedRows([])
       onRowSelect?.(null)
     }
-  }, [data, emptyRows, isLoading, onRowSelect])
+  }, [allData, onRowSelect])
 
   // 테이블 메타 설정
   const tableMeta = React.useMemo(() => ({
     onCellUpdate: handleCellUpdate,
     onCellCancel: handleCellCancel,
     onAction: handleAction,
-    onSaveEmptyRow: handleSaveEmptyRow,
-    onCancelEmptyRow: handleCancelEmptyRow,
     isEmptyRow: (id: string) => String(id).startsWith('temp-'),
     getPendingChanges: () => pendingChanges,
-  }), [handleCellUpdate, handleCellCancel, handleAction, handleSaveEmptyRow, handleCancelEmptyRow, pendingChanges])
+  }), [handleCellUpdate, handleCellCancel, handleAction, pendingChanges])
 
 
   // 데이터 테이블 설정
@@ -461,26 +327,16 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
 
   // 변경사항이 있는지 확인
   const hasPendingChanges = Object.keys(pendingChanges).length > 0
-  const hasEmptyRows = Object.keys(emptyRows).length > 0
 
   return (
     <div className="space-y-4">
       {/* 툴바 */}
       <DataTableAdvancedToolbar
         table={table}
-        filterFields={filterFields as any}
+        filterFields={filterFields}
       >
         <div className="flex items-center gap-2">
           {/* 액션 버튼들 */}
-          <Button
-            variant="outline"
-            size="sm"
-            onClick={() => handleAction('new-registration')}
-            disabled={isCreating}
-          >
-            신규등록
-          </Button>
-
           <Button
             variant="outline"
             size="sm"
@@ -497,16 +353,8 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
             프로젝트AVL등록
           </Button>
 
-          <Button
-            variant="outline"
-            size="sm"
-            onClick={() => handleAction('bulk-import')}
-          >
-            파일 업로드
-          </Button>
-
           {/* 저장 버튼 - 변경사항이 있을 때만 활성화 */}
-          {(hasPendingChanges || hasEmptyRows) && (
+          {(hasPendingChanges) && (
             <Button
               variant="default"
               size="sm"
@@ -543,10 +391,9 @@ export function AvlTable({ data, pageCount, onRefresh, isLoading, onRegistration
       />
 
       {/* 디버그 정보 (개발 환경에서만 표시) */}
-      {process.env.NODE_ENV === 'development' && (hasPendingChanges || hasEmptyRows) && (
+      {process.env.NODE_ENV === 'development' && (hasPendingChanges) && (
         <div className="text-xs text-muted-foreground p-2 bg-muted rounded">
           <div>Pending Changes: {Object.keys(pendingChanges).length}</div>
-          <div>Empty Rows: {Object.keys(emptyRows).length}</div>
         </div>
       )}
     </div>
-- 
cgit v1.2.3

