테이블에 카테고리 값 보이기

This commit is contained in:
kjs 2025-11-05 18:28:43 +09:00
parent 4c98839df8
commit cf9e81a216
3 changed files with 213 additions and 15 deletions

View File

@ -144,6 +144,9 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
const [filteredData, setFilteredData] = useState<any[]>([]); // 필터링된 데이터
const [columnLabels, setColumnLabels] = useState<Record<string, string>>({}); // 컬럼명 -> 라벨 매핑
// 카테고리 값 매핑 캐시 (컬럼명 -> {코드 -> 라벨})
const [categoryMappings, setCategoryMappings] = useState<Record<string, Record<string, string>>>({});
// 공통코드 옵션 가져오기
const loadCodeOptions = useCallback(
async (categoryCode: string) => {
@ -191,6 +194,66 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
};
}, [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<string, Record<string, string>> = {};
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<string, string> = {};
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<string, any>) => {
@ -374,7 +437,6 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
// 먼저 컴포넌트에 설정된 컬럼에서 찾기 (화면 관리에서 설정한 값 우선)
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<InteractiveDataTableProps> = ({
// 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<InteractiveDataTableProps> = ({
// 가상 파일 컬럼의 경우 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<InteractiveDataTableProps> = ({
);
}
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 {

View File

@ -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<string[]>([]); // 전체 컬럼 목록
const [filteredData, setFilteredData] = useState<any[]>([]); // 필터링된 데이터
// 카테고리 값 매핑 캐시 (컬럼명 -> {코드 -> 라벨})
const [categoryMappings, setCategoryMappings] = useState<Record<string, Record<string, string>>>({});
// 🆕 그룹 설정 관련 상태
const [isGroupSettingOpen, setIsGroupSettingOpen] = useState(false); // 그룹 설정 다이얼로그
const [groupByColumns, setGroupByColumns] = useState<string[]>([]); // 그룹화할 컬럼 목록
@ -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<string, Record<string, string>> = {};
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<string, string> = {};
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) => (
<div key={col} className="flex justify-between gap-2 text-xs">
<span className="text-muted-foreground font-medium">{columnLabels[col] || col}:</span>
<span className="text-foreground truncate">{formatValue(row[col])}</span>
<span className="text-foreground truncate">{formatValue(row[col], col)}</span>
</div>
))}
</div>
@ -1095,7 +1162,7 @@ export function FlowWidget({
)}
{stepDataColumns.map((col) => (
<TableCell key={col} className="h-16 border-b px-6 py-3 text-sm whitespace-nowrap">
{formatValue(row[col])}
{formatValue(row[col], col)}
</TableCell>
))}
</TableRow>
@ -1125,7 +1192,7 @@ export function FlowWidget({
)}
{stepDataColumns.map((col) => (
<TableCell key={col} className="h-16 border-b px-6 py-3 text-sm whitespace-nowrap">
{formatValue(row[col])}
{formatValue(row[col], col)}
</TableCell>
))}
</TableRow>

View File

@ -244,7 +244,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
const [localPageSize, setLocalPageSize] = useState<number>(tableConfig.pagination?.pageSize || 20);
const [displayColumns, setDisplayColumns] = useState<ColumnConfig[]>([]);
const [joinColumnMapping, setJoinColumnMapping] = useState<Record<string, string>>({});
const [columnMeta, setColumnMeta] = useState<Record<string, { webType?: string; codeCategory?: string }>>({});
const [columnMeta, setColumnMeta] = useState<Record<string, { webType?: string; codeCategory?: string; inputType?: string }>>({});
const [categoryMappings, setCategoryMappings] = useState<Record<string, Record<string, string>>>({});
const [searchValues, setSearchValues] = useState<Record<string, any>>({});
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
@ -428,6 +429,51 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}
}, [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<string, Record<string, string>> = {};
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<string, string> = {};
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<TableListComponentProps> = ({
// 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<TableListComponentProps> = ({
return String(value);
}
},
[columnMeta, optimizedConvertCode],
[columnMeta, categoryMappings, optimizedConvertCode],
);
// ========================================