/** * V3 DataView Renderer * - 자체적으로 데이터를 fetching하고 테이블로 표시 * - entityJoinApi 사용 * - v2EventBus TABLE_REFRESH 이벤트 구독 * - 페이징, 정렬, 행 선택 지원 */ "use client"; import React, { useState, useEffect } from "react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { DataViewComponentConfig } from "@/lib/api/metaComponent"; import { entityJoinApi } from "@/lib/api/entityJoin"; import { v2EventBus } from "@/lib/v2-core/events/EventBus"; import { V2_EVENTS } from "@/lib/v2-core/events/types"; import { cn } from "@/lib/utils"; import { ChevronLeft, ChevronRight, ArrowUp, ArrowDown, Loader2 } from "lucide-react"; interface DataViewRendererProps { id: string; config: DataViewComponentConfig; selectedRowsData?: any[]; onSelectedRowsChange?: (rows: any[], data: any[]) => void; formData?: Record; onFormDataChange?: (fieldName: string, value: any) => void; tableName?: string; companyCode?: string; screenId?: number; isDesignMode?: boolean; className?: string; onRefresh?: () => void; } interface SortState { column: string | null; order: "asc" | "desc"; } export function DataViewRenderer({ id, config, selectedRowsData = [], onSelectedRowsChange, formData, onFormDataChange, tableName, companyCode, screenId, isDesignMode = false, className, onRefresh, }: DataViewRendererProps) { // 상태 관리 const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [totalCount, setTotalCount] = useState(0); const [sortState, setSortState] = useState({ column: null, order: "asc" }); const [selectedRowIds, setSelectedRowIds] = useState>(new Set()); const [filters, setFilters] = useState>({}); // 🔍 검색 필터 const pageSize = config.pageSize || 10; const totalPages = Math.ceil(totalCount / pageSize); const targetTableName = config.tableName || tableName; // 데이터 로드 함수 const loadData = async () => { if (isDesignMode || !targetTableName) { return; } setLoading(true); setError(null); try { // 🔍 filters를 search 파라미터로 전달 const response = await entityJoinApi.getTableDataWithJoins(targetTableName, { page: currentPage, size: pageSize, sortBy: sortState.column || undefined, sortOrder: sortState.order, enableEntityJoin: true, companyCodeOverride: companyCode, search: Object.keys(filters).length > 0 ? filters : undefined, // 필터가 있으면 전달 }); setData(response.data || []); setTotalCount(response.total || 0); } catch (err: any) { console.error("DataViewRenderer: 데이터 로드 실패", err); setError(err.message || "데이터를 불러오는 중 오류가 발생했습니다."); setData([]); setTotalCount(0); } finally { setLoading(false); } }; // 컬럼 목록 추출 (config.columns 또는 데이터에서 자동 추출) const columns = React.useMemo(() => { if (config.columns && config.columns.length > 0) { return config.columns; } // 첫 번째 데이터 행에서 컬럼 자동 추출 if (data.length > 0) { const firstRow = data[0]; return Object.keys(firstRow).map((key) => ({ columnName: key, columnLabel: key, visible: true, })); } return []; }, [config.columns, data]); // 마운트 시 + tableName/page/sort/filters 변경 시 데이터 로드 useEffect(() => { loadData(); }, [targetTableName, currentPage, sortState, filters]); // TABLE_REFRESH 이벤트 구독 useEffect(() => { const unsubscribe = v2EventBus.subscribe( V2_EVENTS.TABLE_REFRESH, (payload) => { // 모든 테이블 새로고침 또는 특정 테이블만 if (!payload.tableName || payload.tableName === targetTableName) { console.log(`DataViewRenderer: TABLE_REFRESH 이벤트 수신 (${targetTableName})`, payload); // 🔍 필터가 전달되면 적용 if (payload.filters !== undefined) { setFilters(payload.filters); setCurrentPage(1); // 필터 변경 시 첫 페이지로 } else { // 필터 없으면 그냥 새로고침 loadData(); } if (onRefresh) { onRefresh(); } } }, { componentId: id } ); return () => { unsubscribe(); }; }, [id, targetTableName]); // 컬럼 헤더 클릭 (정렬 전환) const handleSort = (columnName: string) => { setSortState((prev) => { if (prev.column === columnName) { // 같은 컬럼: ASC → DESC → null if (prev.order === "asc") { return { column: columnName, order: "desc" }; } else { return { column: null, order: "asc" }; } } else { // 다른 컬럼: ASC로 시작 return { column: columnName, order: "asc" }; } }); setCurrentPage(1); // 정렬 변경 시 첫 페이지로 }; // 행 클릭 (선택) const handleRowClick = (row: any, rowIndex: number) => { const rowId = row.id || rowIndex; const newSelectedIds = new Set(selectedRowIds); if (newSelectedIds.has(rowId)) { newSelectedIds.delete(rowId); } else { newSelectedIds.add(rowId); } setSelectedRowIds(newSelectedIds); // 선택된 행 데이터 전달 const selectedRows = data.filter((r, idx) => newSelectedIds.has(r.id || idx)); if (onSelectedRowsChange) { onSelectedRowsChange(Array.from(newSelectedIds) as any[], selectedRows); } }; // 페이지 변경 const handlePageChange = (page: number) => { if (page >= 1 && page <= totalPages) { setCurrentPage(page); } }; // 디자인 모드 렌더링 if (isDesignMode) { return (
데이터 뷰: {targetTableName || "(테이블 미지정)"}
{columns.length > 0 ? ( columns.map((col) => ( {col.columnLabel || col.columnName} )) ) : ( <> 컬럼 1 컬럼 2 컬럼 3 )} {[1, 2, 3].map((i) => ( {columns.length > 0 ? ( columns.map((col) => (
)) ) : ( <>
)} ))}
); } // 로딩 중 if (loading && data.length === 0) { return (

데이터를 불러오는 중...

); } // 에러 표시 if (error) { return (

⚠️ {error}

); } // 데이터 없음 if (data.length === 0) { return (

표시할 데이터가 없습니다.

); } // 테이블 렌더링 return (
{/* 🔍 현재 적용된 필터 표시 */} {Object.keys(filters).length > 0 && (
적용된 필터: {Object.entries(filters).map(([key, value]) => (
{key}: {String(value)}
))}
)} {/* 테이블 */}
{columns.map((col) => ( handleSort(col.columnName)} >
{col.columnLabel || col.columnName} {sortState.column === col.columnName && ( <> {sortState.order === "asc" ? ( ) : ( )} )}
))}
{data.map((row, rowIndex) => { const rowId = row.id || rowIndex; const isSelected = selectedRowIds.has(rowId); return ( handleRowClick(row, rowIndex)} > {columns.map((col) => ( {row[col.columnName] !== undefined && row[col.columnName] !== null ? String(row[col.columnName]) : "-"} ))} ); })}
{/* 페이징 */} {totalPages > 1 && (
전체 {totalCount}건 중 {(currentPage - 1) * pageSize + 1}~ {Math.min(currentPage * pageSize, totalCount)}건
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { const page = i + 1; return ( ); })} {totalPages > 5 && ...} {totalPages > 5 && ( )}
)}
); }