diff --git a/frontend/components/screen/InteractiveDataTable.tsx b/frontend/components/screen/InteractiveDataTable.tsx index 14d1f5a9..3523518a 100644 --- a/frontend/components/screen/InteractiveDataTable.tsx +++ b/frontend/components/screen/InteractiveDataTable.tsx @@ -144,6 +144,9 @@ export const InteractiveDataTable: React.FC = ({ const [filteredData, setFilteredData] = useState([]); // 필터링된 데이터 const [columnLabels, setColumnLabels] = useState>({}); // 컬럼명 -> 라벨 매핑 + // 카테고리 값 매핑 캐시 (컬럼명 -> {코드 -> 라벨}) + const [categoryMappings, setCategoryMappings] = useState>>({}); + // 공통코드 옵션 가져오기 const loadCodeOptions = useCallback( async (categoryCode: string) => { @@ -191,6 +194,66 @@ export const InteractiveDataTable: React.FC = ({ }; }, [currentPage, searchValues, loadData, component.tableName]); + // 카테고리 타입 컬럼의 값 매핑 로드 + useEffect(() => { + const loadCategoryMappings = async () => { + if (!component.tableName) return; + + try { + // 카테고리 타입 컬럼 찾기 + const categoryColumns = component.columns?.filter((col) => { + const webType = getColumnWebType(col.columnName); + return webType === "category"; + }); + + console.log(`🔍 InteractiveDataTable 카테고리 컬럼 확인:`, { + tableName: component.tableName, + totalColumns: component.columns?.length, + categoryColumns: categoryColumns?.map(c => ({ + name: c.columnName, + webType: getColumnWebType(c.columnName) + })) + }); + + if (!categoryColumns || categoryColumns.length === 0) return; + + // 각 카테고리 컬럼의 값 목록 조회 + const mappings: Record> = {}; + + for (const col of categoryColumns) { + try { + const response = await apiClient.get( + `/table-categories/${component.tableName}/${col.columnName}/values` + ); + + if (response.data.success && response.data.data) { + // valueCode -> valueLabel 매핑 생성 + const mapping: Record = {}; + response.data.data.forEach((item: any) => { + mapping[item.valueCode] = item.valueLabel; + }); + mappings[col.columnName] = mapping; + } + } catch (error) { + // 카테고리 값 로드 실패 시 무시 + } + } + + console.log(`✅ InteractiveDataTable 카테고리 매핑 완료:`, { + tableName: component.tableName, + mappedColumns: Object.keys(mappings), + mappings + }); + + setCategoryMappings(mappings); + } catch (error) { + console.error("카테고리 매핑 로드 실패:", error); + } + }; + + loadCategoryMappings(); + }, [component.tableName, component.columns, getColumnWebType]); + // 파일 상태 확인 함수 const checkFileStatus = useCallback( async (rowData: Record) => { @@ -374,7 +437,6 @@ export const InteractiveDataTable: React.FC = ({ // 먼저 컴포넌트에 설정된 컬럼에서 찾기 (화면 관리에서 설정한 값 우선) const componentColumn = component.columns?.find((col) => col.columnName === columnName); if (componentColumn?.widgetType && componentColumn.widgetType !== "text") { - console.log(`🔍 [${columnName}] componentColumn.widgetType 사용:`, componentColumn.widgetType); return componentColumn.widgetType; } @@ -384,14 +446,11 @@ export const InteractiveDataTable: React.FC = ({ // input_type 우선 사용 (category 등) const inputType = (tableColumn as any)?.input_type || (tableColumn as any)?.inputType; if (inputType) { - console.log(`✅ [${columnName}] input_type 사용:`, inputType); return inputType; } // 없으면 webType 사용 - const result = tableColumn?.webType || "text"; - console.log(`✅ [${columnName}] webType 사용:`, result); - return result; + return tableColumn?.webType || "text"; }, [component.columns, tableColumns], ); @@ -1862,9 +1921,12 @@ export const InteractiveDataTable: React.FC = ({ // 가상 파일 컬럼의 경우 value가 없어도 파일 아이콘을 표시해야 함 if (!column.isVirtualFileColumn && (value === null || value === undefined)) return ""; + // 실제 웹 타입 가져오기 (input_type 포함) + const actualWebType = getColumnWebType(column.columnName); + // 파일 타입 컬럼 처리 (가상 파일 컬럼 포함) const isFileColumn = - column.widgetType === "file" || column.columnName === "file_path" || column.isVirtualFileColumn; + actualWebType === "file" || column.columnName === "file_path" || column.isVirtualFileColumn; // 파일 타입 컬럼은 파일 아이콘으로 표시 (컬럼별 파일 관리) if (isFileColumn && rowData) { @@ -1904,7 +1966,18 @@ export const InteractiveDataTable: React.FC = ({ ); } - switch (column.widgetType) { + // 실제 웹 타입으로 스위치 (input_type="category"도 포함됨) + switch (actualWebType) { + case "category": { + // 카테고리 타입: 코드값 -> 라벨로 변환 + const mapping = categoryMappings[column.columnName]; + if (mapping && value) { + const label = mapping[String(value)]; + return label || String(value); + } + return String(value || ""); + } + case "date": if (value) { try { diff --git a/frontend/components/screen/widgets/FlowWidget.tsx b/frontend/components/screen/widgets/FlowWidget.tsx index fd24ea19..2e174554 100644 --- a/frontend/components/screen/widgets/FlowWidget.tsx +++ b/frontend/components/screen/widgets/FlowWidget.tsx @@ -66,12 +66,21 @@ export function FlowWidget({ const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인 const { user } = useAuth(); // 사용자 정보 가져오기 - // 숫자 포맷팅 함수 - const formatValue = (value: any): string => { + // 값 포맷팅 함수 (숫자, 카테고리 등) + const formatValue = useCallback((value: any, columnName?: string): string => { if (value === null || value === undefined || value === "") { return "-"; } + // 카테고리 타입: 코드값 -> 라벨로 변환 + if (columnName && categoryMappings[columnName]) { + const mapping = categoryMappings[columnName]; + const label = mapping[String(value)]; + if (label) { + return label; + } + } + // 숫자 타입이거나 숫자로 변환 가능한 문자열인 경우 포맷팅 if (typeof value === "number") { return value.toLocaleString("ko-KR"); @@ -86,7 +95,7 @@ export function FlowWidget({ } return String(value); - }; + }, [categoryMappings]); // 🆕 전역 상태 관리 const setSelectedStep = useFlowStepStore((state) => state.setSelectedStep); @@ -114,6 +123,9 @@ export function FlowWidget({ const [allAvailableColumns, setAllAvailableColumns] = useState([]); // 전체 컬럼 목록 const [filteredData, setFilteredData] = useState([]); // 필터링된 데이터 + // 카테고리 값 매핑 캐시 (컬럼명 -> {코드 -> 라벨}) + const [categoryMappings, setCategoryMappings] = useState>>({}); + // 🆕 그룹 설정 관련 상태 const [isGroupSettingOpen, setIsGroupSettingOpen] = useState(false); // 그룹 설정 다이얼로그 const [groupByColumns, setGroupByColumns] = useState([]); // 그룹화할 컬럼 목록 @@ -677,6 +689,61 @@ export function FlowWidget({ } }; + // 카테고리 타입 컬럼의 값 매핑 로드 + useEffect(() => { + const loadCategoryMappings = async () => { + if (!selectedStepId || !steps.length) return; + + try { + const currentStep = steps.find((s) => s.id === selectedStepId); + const tableName = currentStep?.stepConfig?.tableName; + + if (!tableName) return; + + // 테이블 컬럼 정보 조회하여 카테고리 타입 찾기 + const apiClient = (await import("@/lib/api/client")).apiClient; + const columnsResponse = await apiClient.get(`/table-management/tables/${tableName}/columns`); + + if (!columnsResponse.data?.success) return; + + const columns = columnsResponse.data.data?.columns || []; + const categoryColumns = columns.filter((col: any) => + (col.inputType === "category" || col.input_type === "category") + ); + + if (categoryColumns.length === 0) return; + + // 각 카테고리 컬럼의 값 목록 조회 + const mappings: Record> = {}; + + for (const col of categoryColumns) { + const columnName = col.columnName || col.column_name; + try { + const response = await apiClient.get( + `/table-categories/${tableName}/${columnName}/values` + ); + + if (response.data.success && response.data.data) { + const mapping: Record = {}; + response.data.data.forEach((item: any) => { + mapping[item.valueCode] = item.valueLabel; + }); + mappings[columnName] = mapping; + } + } catch (error) { + // 카테고리 값 로드 실패 시 무시 + } + } + + setCategoryMappings(mappings); + } catch (error) { + console.error("FlowWidget 카테고리 매핑 로드 실패:", error); + } + }; + + loadCategoryMappings(); + }, [selectedStepId, steps]); + // 체크박스 토글 const toggleRowSelection = (rowIndex: number) => { // 프리뷰 모드에서는 행 선택 차단 @@ -1017,7 +1084,7 @@ export function FlowWidget({ {stepDataColumns.map((col) => (
{columnLabels[col] || col}: - {formatValue(row[col])} + {formatValue(row[col], col)}
))} @@ -1095,7 +1162,7 @@ export function FlowWidget({ )} {stepDataColumns.map((col) => ( - {formatValue(row[col])} + {formatValue(row[col], col)} ))} @@ -1125,7 +1192,7 @@ export function FlowWidget({ )} {stepDataColumns.map((col) => ( - {formatValue(row[col])} + {formatValue(row[col], col)} ))} diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 3a2e1d60..ba827020 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -244,7 +244,8 @@ export const TableListComponent: React.FC = ({ const [localPageSize, setLocalPageSize] = useState(tableConfig.pagination?.pageSize || 20); const [displayColumns, setDisplayColumns] = useState([]); const [joinColumnMapping, setJoinColumnMapping] = useState>({}); - const [columnMeta, setColumnMeta] = useState>({}); + const [columnMeta, setColumnMeta] = useState>({}); + const [categoryMappings, setCategoryMappings] = useState>>({}); const [searchValues, setSearchValues] = useState>({}); const [selectedRows, setSelectedRows] = useState>(new Set()); const [columnWidths, setColumnWidths] = useState>({}); @@ -428,6 +429,51 @@ export const TableListComponent: React.FC = ({ } }, [tableConfig.selectedTable]); + // ======================================== + // 카테고리 값 매핑 로드 + // ======================================== + + useEffect(() => { + const loadCategoryMappings = async () => { + if (!tableConfig.selectedTable || !columnMeta) return; + + try { + const categoryColumns = Object.entries(columnMeta) + .filter(([_, meta]) => meta.inputType === "category") + .map(([columnName, _]) => columnName); + + if (categoryColumns.length === 0) return; + + const mappings: Record> = {}; + + for (const columnName of categoryColumns) { + try { + const apiClient = (await import("@/lib/api/client")).apiClient; + const response = await apiClient.get( + `/table-categories/${tableConfig.selectedTable}/${columnName}/values` + ); + + if (response.data.success && response.data.data) { + const mapping: Record = {}; + response.data.data.forEach((item: any) => { + mapping[item.valueCode] = item.valueLabel; + }); + mappings[columnName] = mapping; + } + } catch (error) { + // 카테고리 값 로드 실패 시 무시 + } + } + + setCategoryMappings(mappings); + } catch (error) { + console.error("TableListComponent 카테고리 매핑 로드 실패:", error); + } + }; + + loadCategoryMappings(); + }, [tableConfig.selectedTable, columnMeta]); + // ======================================== // 데이터 가져오기 // ======================================== @@ -923,6 +969,18 @@ export const TableListComponent: React.FC = ({ // inputType 기반 포맷팅 (columnMeta에서 가져온 inputType 우선) const inputType = meta?.inputType || column.inputType; + // 카테고리 타입: 코드값 → 라벨로 변환 + if (inputType === "category") { + const mapping = categoryMappings[column.columnName]; + if (mapping && value) { + const label = mapping[String(value)]; + if (label) { + return label; + } + } + return String(value); + } + // 코드 타입: 코드 값 → 코드명 변환 if (inputType === "code" && meta?.codeCategory && value) { try { @@ -979,7 +1037,7 @@ export const TableListComponent: React.FC = ({ return String(value); } }, - [columnMeta, optimizedConvertCode], + [columnMeta, categoryMappings, optimizedConvertCode], ); // ========================================