/** * DynamicTableSection * config 기반 범용 테이블 섹션 (공정, 품질검사, 구매처, 공정표, 배차 등) * * API 연동 시: GET/PUT /v1/items/{itemId}/section-data/{sectionId} * API 연동 전: rows를 폼 상태(formData)에 로컬 관리 */ 'use client'; import { useCallback } from 'react'; import { Plus, Trash2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle, } from '@/components/ui/card'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { TableCellRenderer } from './TableCellRenderer'; import type { ItemSectionResponse } from '@/types/item-master-api'; import type { TableConfig } from '../types'; export interface DynamicTableSectionProps { section: { section: ItemSectionResponse; orderNo: number; }; tableConfig: TableConfig; rows: Record[]; onRowsChange: (rows: Record[]) => void; disabled?: boolean; } export default function DynamicTableSection({ section, tableConfig, rows, onRowsChange, disabled, }: DynamicTableSectionProps) { const { columns, minRows, maxRows, summaryRow } = tableConfig; // 행 추가 const handleAddRow = useCallback(() => { if (maxRows && rows.length >= maxRows) return; const newRow: Record = {}; columns.forEach(col => { newRow[col.key] = null; }); onRowsChange([...rows, newRow]); }, [rows, columns, maxRows, onRowsChange]); // 행 삭제 const handleRemoveRow = useCallback((index: number) => { if (minRows && rows.length <= minRows) return; onRowsChange(rows.filter((_, i) => i !== index)); }, [rows, minRows, onRowsChange]); // 셀 값 변경 const handleCellChange = useCallback((rowIndex: number, columnKey: string, value: unknown) => { const updated = rows.map((row, i) => i === rowIndex ? { ...row, [columnKey]: value } : row ); onRowsChange(updated); }, [rows, onRowsChange]); // 요약행 계산 const computeSummary = (columnKey: string, type: string): string | number => { const values = rows.map(r => Number(r[columnKey]) || 0); switch (type) { case 'sum': return values.reduce((a, b) => a + b, 0); case 'avg': return values.length > 0 ? Math.round(values.reduce((a, b) => a + b, 0) / values.length * 100) / 100 : 0; case 'count': return rows.length; default: return ''; } }; const canAdd = !maxRows || rows.length < maxRows; const canRemove = !minRows || rows.length > minRows; return (
{section.section.title} {section.section.description && (

{section.section.description}

)}
{!disabled && canAdd && ( )}
{rows.length === 0 ? (
데이터가 없습니다. "행 추가" 버튼으로 항목을 추가하세요.
) : (
# {columns.map(col => ( {col.label} {col.isRequired && *} ))} {!disabled && } {rows.map((row, rowIndex) => ( {rowIndex + 1} {columns.map(col => ( handleCellChange(rowIndex, col.key, val)} disabled={disabled} rowIndex={rowIndex} /> ))} {!disabled && ( )} ))} {/* 요약행 */} {summaryRow && summaryRow.length > 0 && ( 합계 {columns.map(col => { const summary = summaryRow.find(s => s.columnKey === col.key); return ( {summary ? summary.type === 'label' ? summary.label || '' : computeSummary(col.key, summary.type) : ''} ); })} {!disabled && } )}
)}
); }