restapi 여러개 띄우는거 작업 가능하게 하는거 진행중
This commit is contained in:
297
frontend/components/dashboard/widgets/ChartTestWidget.tsx
Normal file
297
frontend/components/dashboard/widgets/ChartTestWidget.tsx
Normal file
@@ -0,0 +1,297 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import { DashboardElement, ChartDataSource } from "@/components/admin/dashboard/types";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
BarChart,
|
||||
Bar,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
|
||||
interface ChartTestWidgetProps {
|
||||
element: DashboardElement;
|
||||
}
|
||||
|
||||
const COLORS = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
|
||||
|
||||
export default function ChartTestWidget({ element }: ChartTestWidgetProps) {
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
console.log("🧪 ChartTestWidget 렌더링!", element);
|
||||
|
||||
// 다중 데이터 소스 로딩
|
||||
const loadMultipleDataSources = useCallback(async () => {
|
||||
const dataSources = element?.dataSources;
|
||||
|
||||
if (!dataSources || dataSources.length === 0) {
|
||||
console.log("⚠️ 데이터 소스가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🔄 \${dataSources.length}개의 데이터 소스 로딩 시작...`);
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// 모든 데이터 소스를 병렬로 로딩
|
||||
const results = await Promise.allSettled(
|
||||
dataSources.map(async (source) => {
|
||||
try {
|
||||
console.log(`📡 데이터 소스 "\${source.name || source.id}" 로딩 중...`);
|
||||
|
||||
if (source.type === "api") {
|
||||
return await loadRestApiData(source);
|
||||
} else if (source.type === "database") {
|
||||
return await loadDatabaseData(source);
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (err: any) {
|
||||
console.error(`❌ 데이터 소스 "\${source.name || source.id}" 로딩 실패:`, err);
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 성공한 데이터만 병합
|
||||
const allData: any[] = [];
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === "fulfilled" && Array.isArray(result.value)) {
|
||||
const sourceData = result.value.map((item: any) => ({
|
||||
...item,
|
||||
_source: dataSources[index].name || dataSources[index].id || `소스 \${index + 1}`,
|
||||
}));
|
||||
allData.push(...sourceData);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`✅ 총 \${allData.length}개의 데이터 로딩 완료`);
|
||||
setData(allData);
|
||||
} catch (err: any) {
|
||||
console.error("❌ 데이터 로딩 중 오류:", err);
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [element?.dataSources]);
|
||||
|
||||
// REST API 데이터 로딩
|
||||
const loadRestApiData = async (source: ChartDataSource): Promise<any[]> => {
|
||||
if (!source.endpoint) {
|
||||
throw new Error("API endpoint가 없습니다.");
|
||||
}
|
||||
|
||||
const queryParams: Record<string, string> = {};
|
||||
if (source.queryParams) {
|
||||
source.queryParams.forEach((param) => {
|
||||
if (param.key && param.value) {
|
||||
queryParams[param.key] = param.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {};
|
||||
if (source.headers) {
|
||||
source.headers.forEach((header) => {
|
||||
if (header.key && header.value) {
|
||||
headers[header.key] = header.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const response = await fetch("/api/dashboards/fetch-external-api", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
url: source.endpoint,
|
||||
method: source.method || "GET",
|
||||
headers,
|
||||
queryParams,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API 호출 실패: \${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || "API 호출 실패");
|
||||
}
|
||||
|
||||
let apiData = result.data;
|
||||
if (source.jsonPath) {
|
||||
const pathParts = source.jsonPath.split(".");
|
||||
for (const part of pathParts) {
|
||||
apiData = apiData?.[part];
|
||||
}
|
||||
}
|
||||
|
||||
return Array.isArray(apiData) ? apiData : [apiData];
|
||||
};
|
||||
|
||||
// Database 데이터 로딩
|
||||
const loadDatabaseData = async (source: ChartDataSource): Promise<any[]> => {
|
||||
if (!source.query) {
|
||||
throw new Error("SQL 쿼리가 없습니다.");
|
||||
}
|
||||
|
||||
const response = await fetch("/api/dashboards/query", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
connectionType: source.connectionType || "current",
|
||||
externalConnectionId: source.externalConnectionId,
|
||||
query: source.query,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`데이터베이스 쿼리 실패: \${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || "쿼리 실패");
|
||||
}
|
||||
|
||||
return result.data || [];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (element?.dataSources && element.dataSources.length > 0) {
|
||||
loadMultipleDataSources();
|
||||
}
|
||||
}, [element?.dataSources, loadMultipleDataSources]);
|
||||
|
||||
const chartType = element?.subtype || "line";
|
||||
const chartConfig = element?.chartConfig || {};
|
||||
|
||||
const renderChart = () => {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<p className="text-sm text-muted-foreground">데이터가 없습니다</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const xAxis = chartConfig.xAxis || Object.keys(data[0])[0];
|
||||
const yAxis = chartConfig.yAxis || Object.keys(data[0])[1];
|
||||
|
||||
switch (chartType) {
|
||||
case "line":
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={data}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={xAxis} />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey={yAxis} stroke="#3b82f6" strokeWidth={2} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
||||
case "bar":
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={data}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={xAxis} />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey={yAxis} fill="#3b82f6" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
||||
case "pie":
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={data}
|
||||
dataKey={yAxis}
|
||||
nameKey={xAxis}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={80}
|
||||
label
|
||||
>
|
||||
{data.map((entry, index) => (
|
||||
<Cell key={`cell-\${index}`} fill={COLORS[index % COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
지원하지 않는 차트 타입: {chartType}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col bg-background">
|
||||
<div className="flex items-center justify-between border-b p-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">
|
||||
{element?.customTitle || "차트 테스트 (다중 데이터 소스)"}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{element?.dataSources?.length || 0}개 데이터 소스 연결됨
|
||||
</p>
|
||||
</div>
|
||||
{loading && <Loader2 className="h-4 w-4 animate-spin" />}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-4">
|
||||
{error ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<p className="text-sm text-destructive">{error}</p>
|
||||
</div>
|
||||
) : !element?.dataSources || element.dataSources.length === 0 ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
데이터 소스를 연결해주세요
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
renderChart()
|
||||
)}
|
||||
</div>
|
||||
|
||||
{data.length > 0 && (
|
||||
<div className="border-t p-2 text-xs text-muted-foreground">
|
||||
총 {data.length}개 데이터 표시 중
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user