summaryrefslogtreecommitdiff
path: root/lib/avl/table/avl-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/avl/table/avl-table.tsx')
-rw-r--r--lib/avl/table/avl-table.tsx219
1 files changed, 33 insertions, 186 deletions
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,29 +327,19 @@ 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"
onClick={() => handleAction('standard-registration')}
>
표준AVL등록
@@ -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>