ERP-node/frontend/components/admin/dashboard/MultiChartConfigPanel.tsx

331 lines
12 KiB
TypeScript
Raw Normal View History

"use client";
import React, { useState, useEffect } from "react";
import { ChartConfig, ChartDataSource } from "./types";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { Trash2 } from "lucide-react";
interface MultiChartConfigPanelProps {
config: ChartConfig;
dataSources: ChartDataSource[];
testResults: Map<string, { columns: string[]; rows: Record<string, unknown>[] }>; // 각 데이터 소스의 테스트 결과
onConfigChange: (config: ChartConfig) => void;
}
export function MultiChartConfigPanel({
config,
dataSources,
testResults,
onConfigChange,
}: MultiChartConfigPanelProps) {
const [chartType, setChartType] = useState<string>(config.chartType || "line");
const [mergeMode, setMergeMode] = useState<boolean>(config.mergeMode || false);
const [dataSourceConfigs, setDataSourceConfigs] = useState<
Array<{
dataSourceId: string;
xAxis: string;
yAxis: string[];
label?: string;
}>
>(config.dataSourceConfigs || []);
// 데이터 소스별 사용 가능한 컬럼
const getColumnsForDataSource = (dataSourceId: string): string[] => {
const result = testResults.get(dataSourceId);
return result?.columns || [];
};
// 데이터 소스별 숫자 컬럼
const getNumericColumnsForDataSource = (dataSourceId: string): string[] => {
const result = testResults.get(dataSourceId);
if (!result || !result.rows || result.rows.length === 0) return [];
const firstRow = result.rows[0];
return Object.keys(firstRow).filter((key) => {
const value = firstRow[key];
return typeof value === "number" || !isNaN(Number(value));
});
};
// 차트 타입 변경
const handleChartTypeChange = (type: string) => {
setChartType(type);
onConfigChange({
...config,
chartType: type,
mergeMode,
dataSourceConfigs,
});
};
// 병합 모드 변경
const handleMergeModeChange = (checked: boolean) => {
setMergeMode(checked);
onConfigChange({
...config,
chartType,
mergeMode: checked,
dataSourceConfigs,
});
};
// 데이터 소스 설정 추가
const handleAddDataSourceConfig = (dataSourceId: string) => {
const columns = getColumnsForDataSource(dataSourceId);
const numericColumns = getNumericColumnsForDataSource(dataSourceId);
const newConfig = {
dataSourceId,
xAxis: columns[0] || "",
yAxis: numericColumns.length > 0 ? [numericColumns[0]] : [],
label: dataSources.find((ds) => ds.id === dataSourceId)?.name || "",
};
const updated = [...dataSourceConfigs, newConfig];
setDataSourceConfigs(updated);
onConfigChange({
...config,
chartType,
mergeMode,
dataSourceConfigs: updated,
});
};
// 데이터 소스 설정 삭제
const handleRemoveDataSourceConfig = (dataSourceId: string) => {
const updated = dataSourceConfigs.filter((c) => c.dataSourceId !== dataSourceId);
setDataSourceConfigs(updated);
onConfigChange({
...config,
chartType,
mergeMode,
dataSourceConfigs: updated,
});
};
// X축 변경
const handleXAxisChange = (dataSourceId: string, xAxis: string) => {
const updated = dataSourceConfigs.map((c) => (c.dataSourceId === dataSourceId ? { ...c, xAxis } : c));
setDataSourceConfigs(updated);
onConfigChange({
...config,
chartType,
mergeMode,
dataSourceConfigs: updated,
});
};
// Y축 변경
const handleYAxisChange = (dataSourceId: string, yAxis: string) => {
const updated = dataSourceConfigs.map((c) => (c.dataSourceId === dataSourceId ? { ...c, yAxis: [yAxis] } : c));
setDataSourceConfigs(updated);
onConfigChange({
...config,
chartType,
mergeMode,
dataSourceConfigs: updated,
});
};
// 🆕 개별 차트 타입 변경
const handleIndividualChartTypeChange = (dataSourceId: string, chartType: "bar" | "line" | "area") => {
const updated = dataSourceConfigs.map((c) => (c.dataSourceId === dataSourceId ? { ...c, chartType } : c));
setDataSourceConfigs(updated);
onConfigChange({
...config,
chartType: "mixed", // 혼합 모드로 설정
mergeMode,
dataSourceConfigs: updated,
});
};
// 설정되지 않은 데이터 소스 (테스트 완료된 것만)
const availableDataSources = dataSources.filter(
(ds) => testResults.has(ds.id!) && !dataSourceConfigs.some((c) => c.dataSourceId === ds.id),
);
return (
<div className="space-y-4">
{/* 차트 타입 선택 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Select value={chartType} onValueChange={handleChartTypeChange}>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="차트 타입 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="line">📈 </SelectItem>
<SelectItem value="bar">📊 </SelectItem>
<SelectItem value="horizontal-bar">📊 </SelectItem>
<SelectItem value="stacked-bar">📊 </SelectItem>
<SelectItem value="area">📉 </SelectItem>
<SelectItem value="pie">🥧 </SelectItem>
<SelectItem value="donut">🍩 </SelectItem>
<SelectItem value="combo">🎨 </SelectItem>
</SelectContent>
</Select>
</div>
{/* 데이터 병합 모드 */}
{dataSourceConfigs.length > 1 && (
<div className="bg-muted/50 flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label className="text-xs font-medium"> </Label>
<p className="text-muted-foreground text-[10px]"> / </p>
</div>
<Switch checked={mergeMode} onCheckedChange={handleMergeModeChange} aria-label="데이터 병합 모드" />
</div>
)}
{/* 데이터 소스별 설정 */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-xs font-medium"> </Label>
{availableDataSources.length > 0 && (
<Select onValueChange={handleAddDataSourceConfig}>
<SelectTrigger className="h-7 w-32 text-xs">
<SelectValue placeholder="추가" />
</SelectTrigger>
<SelectContent>
{availableDataSources.map((ds) => (
<SelectItem key={ds.id} value={ds.id!} className="text-xs">
{ds.name || ds.id}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
{dataSourceConfigs.length === 0 ? (
<div className="rounded-lg border border-dashed p-4 text-center">
<p className="text-muted-foreground text-xs">
API <br />
</p>
</div>
) : (
dataSourceConfigs.map((dsConfig) => {
const dataSource = dataSources.find((ds) => ds.id === dsConfig.dataSourceId);
const columns = getColumnsForDataSource(dsConfig.dataSourceId);
const numericColumns = getNumericColumnsForDataSource(dsConfig.dataSourceId);
return (
<div key={dsConfig.dataSourceId} className="bg-muted/50 space-y-3 rounded-lg border p-3">
{/* 헤더 */}
<div className="flex items-center justify-between">
<h5 className="text-xs font-semibold">{dataSource?.name || dsConfig.dataSourceId}</h5>
<Button
variant="ghost"
size="sm"
onClick={() => handleRemoveDataSourceConfig(dsConfig.dataSourceId)}
className="h-6 w-6 p-0"
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
{/* X축 */}
<div className="space-y-1.5">
<Label className="text-xs">X축 (/)</Label>
<Select
value={dsConfig.xAxis}
onValueChange={(value) => handleXAxisChange(dsConfig.dataSourceId, value)}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="X축 선택" />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Y축 */}
<div className="space-y-1.5">
<Label className="text-xs">Y축 ()</Label>
<Select
value={dsConfig.yAxis[0] || ""}
onValueChange={(value) => handleYAxisChange(dsConfig.dataSourceId, value)}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="Y축 선택" />
</SelectTrigger>
<SelectContent>
{numericColumns.map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 🆕 개별 차트 타입 (병합 모드가 아닐 때만) */}
{!mergeMode && (
<div className="space-y-1.5">
<Label className="text-xs"> </Label>
<Select
value={dsConfig.chartType || "line"}
onValueChange={(value) =>
handleIndividualChartTypeChange(dsConfig.dataSourceId, value as "bar" | "line" | "area")
}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="차트 타입" />
</SelectTrigger>
<SelectContent>
<SelectItem value="bar" className="text-xs">
📊
</SelectItem>
<SelectItem value="line" className="text-xs">
📈
</SelectItem>
<SelectItem value="area" className="text-xs">
📉
</SelectItem>
</SelectContent>
</Select>
</div>
)}
</div>
);
})
)}
</div>
{/* 안내 메시지 */}
{dataSourceConfigs.length > 0 && (
2025-10-29 17:53:03 +09:00
<div className="rounded-lg bg-primary/10 p-3">
<p className="text-xs text-primary">
{mergeMode ? (
<>
🔗 {dataSourceConfigs.length} / .
<br />
<span className="text-[10px]">
중요: X축/Y축 .
<br />
.
<br />
💡 "컬럼 매핑" .
</span>
</>
) : (
<>
💡 {dataSourceConfigs.length} .
<br /> (//) .
</>
)}
</p>
</div>
)}
</div>
);
}