feat: 분할 패널 테이블에서 카테고리 값을 라벨로 표시하는 기능 추가
- 좌측/우측 패널 테이블의 카테고리 타입 컬럼 매핑 로드 - formatCellValue 함수 추가하여 카테고리 코드를 라벨+배지로 변환 - 좌측 패널 테이블 렌더링 부분(그룹화/일반)에 formatCellValue 적용 - 우측 패널 테이블 렌더링 부분에 formatCellValue 적용 - Badge 컴포넌트 import 및 카테고리별 색상 표시 변경 사항: - 카테고리 매핑 상태 추가 (leftCategoryMappings, rightCategoryMappings) - API 클라이언트 import 추가 - 카테고리 값 조회 API 호출 useEffect 2개 추가 - 셀 값 포맷팅 함수 formatCellValue 추가 - 모든 테이블 td 렌더링에서 String(value) → formatCellValue() 적용
This commit is contained in:
parent
2a52f25c10
commit
702b506665
|
|
@ -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 && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue