feat: 분할 패널 테이블에서 카테고리 값을 라벨로 표시하는 기능 추가

- 좌측/우측 패널 테이블의 카테고리 타입 컬럼 매핑 로드
- formatCellValue 함수 추가하여 카테고리 코드를 라벨+배지로 변환
- 좌측 패널 테이블 렌더링 부분(그룹화/일반)에 formatCellValue 적용
- 우측 패널 테이블 렌더링 부분에 formatCellValue 적용
- Badge 컴포넌트 import 및 카테고리별 색상 표시

변경 사항:
- 카테고리 매핑 상태 추가 (leftCategoryMappings, rightCategoryMappings)
- API 클라이언트 import 추가
- 카테고리 값 조회 API 호출 useEffect 2개 추가
- 셀 값 포맷팅 함수 formatCellValue 추가
- 모든 테이블 td 렌더링에서 String(value) → formatCellValue() 적용
This commit is contained in:
kjs 2025-11-13 17:52:33 +09:00
parent 2a52f25c10
commit 702b506665
1 changed files with 146 additions and 9 deletions

View File

@ -6,11 +6,13 @@ import { SplitPanelLayoutConfig } from "./types";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp, Save, ChevronRight, Pencil, Trash2 } from "lucide-react"; import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp, Save, ChevronRight, Pencil, Trash2 } from "lucide-react";
import { dataApi } from "@/lib/api/data"; import { dataApi } from "@/lib/api/data";
import { entityJoinApi } from "@/lib/api/entityJoin"; import { entityJoinApi } from "@/lib/api/entityJoin";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { tableTypeApi } from "@/lib/api/screen"; import { tableTypeApi } from "@/lib/api/screen";
import { apiClient } from "@/lib/api/client";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { useTableOptions } from "@/contexts/TableOptionsContext"; import { useTableOptions } from "@/contexts/TableOptionsContext";
@ -64,6 +66,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const [expandedItems, setExpandedItems] = useState<Set<any>>(new Set()); // 펼쳐진 항목들 const [expandedItems, setExpandedItems] = useState<Set<any>>(new Set()); // 펼쳐진 항목들
const [leftColumnLabels, setLeftColumnLabels] = useState<Record<string, string>>({}); // 좌측 컬럼 라벨 const [leftColumnLabels, setLeftColumnLabels] = useState<Record<string, string>>({}); // 좌측 컬럼 라벨
const [rightColumnLabels, setRightColumnLabels] = useState<Record<string, string>>({}); // 우측 컬럼 라벨 const [rightColumnLabels, setRightColumnLabels] = useState<Record<string, string>>({}); // 우측 컬럼 라벨
const [leftCategoryMappings, setLeftCategoryMappings] = useState<Record<string, Record<string, { label: string; color?: string }>>>({}); // 좌측 카테고리 매핑
const [rightCategoryMappings, setRightCategoryMappings] = useState<Record<string, Record<string, { label: string; color?: string }>>>({}); // 우측 카테고리 매핑
const { toast } = useToast(); const { toast } = useToast();
// 추가 모달 상태 // 추가 모달 상태
@ -241,6 +245,39 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
})); }));
}, [leftData, leftGrouping]); }, [leftData, leftGrouping]);
// 셀 값 포맷팅 함수 (카테고리 타입 처리)
const formatCellValue = useCallback((
columnName: string,
value: any,
categoryMappings: Record<string, Record<string, { label: string; color?: string }>>
) => {
if (value === null || value === undefined) return "-";
// 카테고리 매핑이 있는지 확인
const mapping = categoryMappings[columnName];
if (mapping && mapping[String(value)]) {
const categoryData = mapping[String(value)];
const displayLabel = categoryData.label || String(value);
const displayColor = categoryData.color || "#64748b";
// 배지로 표시
return (
<Badge
style={{
backgroundColor: displayColor,
borderColor: displayColor,
}}
className="text-white"
>
{displayLabel}
</Badge>
);
}
// 일반 값
return String(value);
}, []);
// 좌측 데이터 로드 // 좌측 데이터 로드
const loadLeftData = useCallback(async () => { const loadLeftData = useCallback(async () => {
const leftTableName = componentConfig.leftPanel?.tableName; const leftTableName = componentConfig.leftPanel?.tableName;
@ -523,6 +560,112 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
loadRightTableColumns(); loadRightTableColumns();
}, [componentConfig.rightPanel?.tableName, isDesignMode]); }, [componentConfig.rightPanel?.tableName, isDesignMode]);
// 좌측 테이블 카테고리 매핑 로드
useEffect(() => {
const loadLeftCategoryMappings = async () => {
const leftTableName = componentConfig.leftPanel?.tableName;
if (!leftTableName || isDesignMode) return;
try {
// 1. 컬럼 메타 정보 조회
const columnsResponse = await tableTypeApi.getColumns(leftTableName);
const categoryColumns = columnsResponse.filter(
(col: any) => col.inputType === "category"
);
if (categoryColumns.length === 0) {
setLeftCategoryMappings({});
return;
}
// 2. 각 카테고리 컬럼에 대한 값 조회
const mappings: Record<string, Record<string, { label: string; color?: string }>> = {};
for (const col of categoryColumns) {
const columnName = col.columnName || col.column_name;
try {
const response = await apiClient.get(
`/table-type/category-values/${leftTableName}/${columnName}`
);
if (response.data.success && response.data.data) {
const valueMap: Record<string, { label: string; color?: string }> = {};
response.data.data.forEach((item: any) => {
valueMap[item.value_code || item.valueCode] = {
label: item.value_label || item.valueLabel,
color: item.color,
};
});
mappings[columnName] = valueMap;
console.log(`✅ 좌측 카테고리 매핑 로드 [${columnName}]:`, valueMap);
}
} catch (error) {
console.error(`좌측 카테고리 값 조회 실패 [${columnName}]:`, error);
}
}
setLeftCategoryMappings(mappings);
} catch (error) {
console.error("좌측 카테고리 매핑 로드 실패:", error);
}
};
loadLeftCategoryMappings();
}, [componentConfig.leftPanel?.tableName, isDesignMode]);
// 우측 테이블 카테고리 매핑 로드
useEffect(() => {
const loadRightCategoryMappings = async () => {
const rightTableName = componentConfig.rightPanel?.tableName;
if (!rightTableName || isDesignMode) return;
try {
// 1. 컬럼 메타 정보 조회
const columnsResponse = await tableTypeApi.getColumns(rightTableName);
const categoryColumns = columnsResponse.filter(
(col: any) => col.inputType === "category"
);
if (categoryColumns.length === 0) {
setRightCategoryMappings({});
return;
}
// 2. 각 카테고리 컬럼에 대한 값 조회
const mappings: Record<string, Record<string, { label: string; color?: string }>> = {};
for (const col of categoryColumns) {
const columnName = col.columnName || col.column_name;
try {
const response = await apiClient.get(
`/table-type/category-values/${rightTableName}/${columnName}`
);
if (response.data.success && response.data.data) {
const valueMap: Record<string, { label: string; color?: string }> = {};
response.data.data.forEach((item: any) => {
valueMap[item.value_code || item.valueCode] = {
label: item.value_label || item.valueLabel,
color: item.color,
};
});
mappings[columnName] = valueMap;
console.log(`✅ 우측 카테고리 매핑 로드 [${columnName}]:`, valueMap);
}
} catch (error) {
console.error(`우측 카테고리 값 조회 실패 [${columnName}]:`, error);
}
}
setRightCategoryMappings(mappings);
} catch (error) {
console.error("우측 카테고리 매핑 로드 실패:", error);
}
};
loadRightCategoryMappings();
}, [componentConfig.rightPanel?.tableName, isDesignMode]);
// 항목 펼치기/접기 토글 // 항목 펼치기/접기 토글
const toggleExpand = useCallback((itemId: any) => { const toggleExpand = useCallback((itemId: any) => {
setExpandedItems(prev => { setExpandedItems(prev => {
@ -1193,9 +1336,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
className="whitespace-nowrap px-3 py-2 text-sm text-gray-900" className="whitespace-nowrap px-3 py-2 text-sm text-gray-900"
style={{ textAlign: col.align || "left" }} style={{ textAlign: col.align || "left" }}
> >
{item[col.name] !== null && item[col.name] !== undefined {formatCellValue(col.name, item[col.name], leftCategoryMappings)}
? String(item[col.name])
: "-"}
</td> </td>
))} ))}
</tr> </tr>
@ -1246,9 +1387,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
className="whitespace-nowrap px-3 py-2 text-sm text-gray-900" className="whitespace-nowrap px-3 py-2 text-sm text-gray-900"
style={{ textAlign: col.align || "left" }} style={{ textAlign: col.align || "left" }}
> >
{item[col.name] !== null && item[col.name] !== undefined {formatCellValue(col.name, item[col.name], leftCategoryMappings)}
? String(item[col.name])
: "-"}
</td> </td>
))} ))}
</tr> </tr>
@ -1619,9 +1758,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
className="whitespace-nowrap px-3 py-2 text-sm text-gray-900" className="whitespace-nowrap px-3 py-2 text-sm text-gray-900"
style={{ textAlign: col.align || "left" }} style={{ textAlign: col.align || "left" }}
> >
{item[col.name] !== null && item[col.name] !== undefined {formatCellValue(col.name, item[col.name], rightCategoryMappings)}
? String(item[col.name])
: "-"}
</td> </td>
))} ))}
{!isDesignMode && ( {!isDesignMode && (