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

331 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 && (
<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>
);
}