"use client"; import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { RefreshCw, AlertTriangle, Cloud, Construction, Database as DatabaseIcon } from "lucide-react"; import { DashboardElement, ChartDataSource } from "@/components/admin/dashboard/types"; import { getApiUrl } from "@/lib/utils/apiUrl"; type AlertType = "accident" | "weather" | "construction" | "system" | "security" | "other"; interface Alert { id: string; type: AlertType; severity: "high" | "medium" | "low"; title: string; location?: string; description: string; timestamp: string; source?: string; } interface RiskAlertTestWidgetProps { element: DashboardElement; } export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProps) { const [alerts, setAlerts] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [filter, setFilter] = useState("all"); const [lastRefreshTime, setLastRefreshTime] = useState(null); const dataSources = useMemo(() => { return element?.dataSources || element?.chartConfig?.dataSources; }, [element?.dataSources, element?.chartConfig?.dataSources]); const parseTextData = (text: string): any[] => { // XML ํ˜•์‹ ๊ฐ์ง€ if (text.trim().startsWith("")) { // console.log("๐Ÿ“„ XML ํ˜•์‹ ๋ฐ์ดํ„ฐ ๊ฐ์ง€"); return parseXmlData(text); } // CSV ํ˜•์‹ (๊ธฐ์ƒ์ฒญ ํŠน๋ณด) // console.log("๐Ÿ“„ CSV ํ˜•์‹ ๋ฐ์ดํ„ฐ ๊ฐ์ง€"); const lines = text.split("\n").filter((line) => { const trimmed = line.trim(); return trimmed && !trimmed.startsWith("#") && trimmed !== "="; }); return lines.map((line) => { const values = line.split(","); const obj: any = {}; if (values.length >= 11) { obj.code = values[0]; obj.region = values[1]; obj.subCode = values[2]; obj.subRegion = values[3]; obj.tmFc = values[4]; obj.tmEf = values[5]; obj.warning = values[6]; obj.level = values[7]; obj.status = values[8]; obj.period = values[9]; obj.name = obj.subRegion || obj.region || obj.code; } else { values.forEach((value, index) => { obj[`field_${index}`] = value; }); } return obj; }); }; const parseXmlData = (xmlText: string): any[] => { try { // ๊ฐ„๋‹จํ•œ XML ํŒŒ์‹ฑ (DOMParser ์‚ฌ์šฉ) const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlText, "text/xml"); const records = xmlDoc.getElementsByTagName("record"); const results: any[] = []; for (let i = 0; i < records.length; i++) { const record = records[i]; const obj: any = {}; // ๋ชจ๋“  ์ž์‹ ๋…ธ๋“œ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ for (let j = 0; j < record.children.length; j++) { const child = record.children[j]; obj[child.tagName] = child.textContent || ""; } results.push(obj); } // console.log(`โœ… XML ํŒŒ์‹ฑ ์™„๋ฃŒ: ${results.length}๊ฐœ ๋ ˆ์ฝ”๋“œ`); return results; } catch (error) { console.error("โŒ XML ํŒŒ์‹ฑ ์‹คํŒจ:", error); return []; } }; const loadRestApiData = useCallback(async (source: ChartDataSource) => { // ๐Ÿ†• ์™ธ๋ถ€ ์—ฐ๊ฒฐ ID๊ฐ€ ์žˆ์œผ๋ฉด ๋จผ์ € ์™ธ๋ถ€ ์—ฐ๊ฒฐ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ด let actualEndpoint = source.endpoint; let actualQueryParams = source.queryParams; let actualHeaders = source.headers; if (source.externalConnectionId) { // console.log("๐Ÿ”— ์™ธ๋ถ€ ์—ฐ๊ฒฐ ID ๊ฐ์ง€:", source.externalConnectionId); try { const { ExternalDbConnectionAPI } = await import("@/lib/api/externalDbConnection"); const connection = await ExternalDbConnectionAPI.getApiConnectionById(Number(source.externalConnectionId)); if (connection) { // console.log("โœ… ์™ธ๋ถ€ ์—ฐ๊ฒฐ ์ •๋ณด ๊ฐ€์ ธ์˜ด:", connection); // ์ „์ฒด ์—”๋“œํฌ์ธํŠธ URL ์ƒ์„ฑ actualEndpoint = connection.endpoint_path ? `${connection.base_url}${connection.endpoint_path}` : connection.base_url; // console.log("๐Ÿ“ ์‹ค์ œ ์—”๋“œํฌ์ธํŠธ:", actualEndpoint); // ๊ธฐ๋ณธ ํ—ค๋” ์ ์šฉ const headers: any[] = []; if (connection.default_headers && Object.keys(connection.default_headers).length > 0) { Object.entries(connection.default_headers).forEach(([key, value]) => { headers.push({ key, value }); }); } // ์ธ์ฆ ์ •๋ณด ์ ์šฉ const queryParams: any[] = []; if (connection.auth_type && connection.auth_type !== "none" && connection.auth_config) { const authConfig = connection.auth_config; if (connection.auth_type === "api-key") { if (authConfig.keyLocation === "header" && authConfig.keyName && authConfig.keyValue) { headers.push({ key: authConfig.keyName, value: authConfig.keyValue }); // console.log("๐Ÿ”‘ API Key ํ—ค๋” ์ถ”๊ฐ€:", authConfig.keyName); } else if (authConfig.keyLocation === "query" && authConfig.keyName && authConfig.keyValue) { const actualKeyName = authConfig.keyName === "apiKey" ? "key" : authConfig.keyName; queryParams.push({ key: actualKeyName, value: authConfig.keyValue }); // console.log("๐Ÿ”‘ API Key ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€:", actualKeyName); } } else if (connection.auth_type === "bearer" && authConfig.token) { headers.push({ key: "Authorization", value: `Bearer ${authConfig.token}` }); // console.log("๐Ÿ”‘ Bearer Token ํ—ค๋” ์ถ”๊ฐ€"); } else if (connection.auth_type === "basic" && authConfig.username && authConfig.password) { const credentials = btoa(`${authConfig.username}:${authConfig.password}`); headers.push({ key: "Authorization", value: `Basic ${credentials}` }); // console.log("๐Ÿ”‘ Basic Auth ํ—ค๋” ์ถ”๊ฐ€"); } } actualHeaders = headers; actualQueryParams = queryParams; // console.log("โœ… ์ตœ์ข… ํ—ค๋”:", actualHeaders); // console.log("โœ… ์ตœ์ข… ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ:", actualQueryParams); } } catch (err) { console.error("โŒ ์™ธ๋ถ€ ์—ฐ๊ฒฐ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ:", err); } } if (!actualEndpoint) { throw new Error("API endpoint๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); } // ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ฒ˜๋ฆฌ const queryParamsObj: Record = {}; if (actualQueryParams && Array.isArray(actualQueryParams)) { actualQueryParams.forEach((param) => { if (param.key && param.value) { queryParamsObj[param.key] = param.value; } }); } // ํ—ค๋” ์ฒ˜๋ฆฌ const headersObj: Record = {}; if (actualHeaders && Array.isArray(actualHeaders)) { actualHeaders.forEach((header) => { if (header.key && header.value) { headersObj[header.key] = header.value; } }); } // console.log("๐ŸŒ API ํ˜ธ์ถœ ์ค€๋น„:", { // endpoint: actualEndpoint, // queryParams: queryParamsObj, // headers: headersObj, // }); const response = await fetch(getApiUrl("/api/dashboards/fetch-external-api"), { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ url: actualEndpoint, method: "GET", headers: headersObj, queryParams: queryParamsObj, }), }); // console.log("๐ŸŒ API ์‘๋‹ต ์ƒํƒœ:", response.status); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || "API ํ˜ธ์ถœ ์‹คํŒจ"); } let apiData = result.data; // console.log("๐Ÿ” API ์‘๋‹ต ๋ฐ์ดํ„ฐ ํƒ€์ž…:", typeof apiData); // console.log("๐Ÿ” API ์‘๋‹ต ๋ฐ์ดํ„ฐ (์ฒ˜์Œ 500์ž):", typeof apiData === "string" ? apiData.substring(0, 500) : JSON.stringify(apiData).substring(0, 500)); // ๋ฐฑ์—”๋“œ๊ฐ€ {text: "XML..."} ํ˜•ํƒœ๋กœ ๊ฐ์‹ผ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ if (apiData && typeof apiData === "object" && apiData.text && typeof apiData.text === "string") { // console.log("๐Ÿ“ฆ ๋ฐฑ์—”๋“œ๊ฐ€ text ํ•„๋“œ๋กœ ๊ฐ์‹ผ ๋ฐ์ดํ„ฐ ๊ฐ์ง€"); apiData = parseTextData(apiData.text); // console.log("โœ… ํŒŒ์‹ฑ ์„ฑ๊ณต:", apiData.length, "๊ฐœ ํ–‰"); } else if (typeof apiData === "string") { // console.log("๐Ÿ“„ ํ…์ŠคํŠธ ํ˜•์‹ ๋ฐ์ดํ„ฐ ๊ฐ์ง€, ํŒŒ์‹ฑ ์‹œ๋„"); apiData = parseTextData(apiData); // console.log("โœ… ํŒŒ์‹ฑ ์„ฑ๊ณต:", apiData.length, "๊ฐœ ํ–‰"); } else if (Array.isArray(apiData)) { // console.log("โœ… ์ด๋ฏธ ๋ฐฐ์—ด ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค."); } else { // console.log("โš ๏ธ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฐ์ดํ„ฐ ํ˜•์‹์ž…๋‹ˆ๋‹ค. ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ ์‹œ๋„."); apiData = [apiData]; } // JSON Path ์ ์šฉ if (source.jsonPath && typeof apiData === "object" && !Array.isArray(apiData)) { const paths = source.jsonPath.split("."); for (const path of paths) { if (apiData && typeof apiData === "object" && path in apiData) { apiData = apiData[path]; } } } const rows = Array.isArray(apiData) ? apiData : [apiData]; return convertToAlerts(rows, source.name || source.id || "API"); }, []); const loadDatabaseData = useCallback(async (source: ChartDataSource) => { if (!source.query) { throw new Error("SQL ์ฟผ๋ฆฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); } if (source.connectionType === "external" && source.externalConnectionId) { const { ExternalDbConnectionAPI } = await import("@/lib/api/externalDbConnection"); const externalResult = await ExternalDbConnectionAPI.executeQuery( parseInt(source.externalConnectionId), source.query ); if (!externalResult.success || !externalResult.data) { throw new Error(externalResult.message || "์™ธ๋ถ€ DB ์ฟผ๋ฆฌ ์‹คํ–‰ ์‹คํŒจ"); } const resultData = externalResult.data as unknown as { rows: Record[] }; return convertToAlerts(resultData.rows, source.name || source.id || "Database"); } else { const { dashboardApi } = await import("@/lib/api/dashboard"); const result = await dashboardApi.executeQuery(source.query); return convertToAlerts(result.rows, source.name || source.id || "Database"); } }, []); const convertToAlerts = useCallback((rows: any[], sourceName: string): Alert[] => { // console.log("๐Ÿ”„ convertToAlerts ํ˜ธ์ถœ:", rows.length, "๊ฐœ ํ–‰"); return rows.map((row: any, index: number) => { // ํƒ€์ž… ๊ฒฐ์ • (UTIC XML ๊ธฐ์ค€) let type: AlertType = "other"; // incidenteTypeCd: 1=์‚ฌ๊ณ , 2=๊ณต์‚ฌ, 3=ํ–‰์‚ฌ, 4=๊ธฐํƒ€ if (row.incidenteTypeCd) { const typeCode = String(row.incidenteTypeCd); if (typeCode === "1") { type = "accident"; } else if (typeCode === "2") { type = "construction"; } } // ๊ธฐ์ƒ ํŠน๋ณด ๋ฐ์ดํ„ฐ (warning ํ•„๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฌด์กฐ๊ฑด ๋‚ ์”จ) else if (row.warning) { type = "weather"; } // ์ผ๋ฐ˜ ๋ฐ์ดํ„ฐ else if (row.type || row.ํƒ€์ž… || row.alert_type) { type = (row.type || row.ํƒ€์ž… || row.alert_type) as AlertType; } // ์‹ฌ๊ฐ๋„ ๊ฒฐ์ • let severity: "high" | "medium" | "low" = "medium"; if (type === "accident") { severity = "high"; // ์‚ฌ๊ณ ๋Š” ํ•ญ์ƒ ๋†’์Œ } else if (type === "construction") { severity = "medium"; // ๊ณต์‚ฌ๋Š” ์ค‘๊ฐ„ } else if (row.level === "๊ฒฝ๋ณด") { severity = "high"; } else if (row.level === "์ฃผ์˜" || row.level === "์ฃผ์˜๋ณด") { severity = "medium"; } else if (row.severity || row.์‹ฌ๊ฐ๋„ || row.priority) { severity = (row.severity || row.์‹ฌ๊ฐ๋„ || row.priority) as "high" | "medium" | "low"; } // ์ œ๋ชฉ ์ƒ์„ฑ (UTIC XML ๊ธฐ์ค€) let title = ""; if (type === "accident") { // incidenteSubTypeCd: 1=์ถ”๋Œ, 2=์ ‘์ด‰, 3=์ „๋ณต, 4=์ถ”๋ฝ, 5=ํ™”์žฌ, 6=์นจ์ˆ˜, 7=๊ธฐํƒ€ const subType = row.incidenteSubTypeCd; const subTypeMap: { [key: string]: string } = { "1": "์ถ”๋Œ์‚ฌ๊ณ ", "2": "์ ‘์ด‰์‚ฌ๊ณ ", "3": "์ „๋ณต์‚ฌ๊ณ ", "4": "์ถ”๋ฝ์‚ฌ๊ณ ", "5": "ํ™”์žฌ์‚ฌ๊ณ ", "6": "์นจ์ˆ˜์‚ฌ๊ณ ", "7": "๊ธฐํƒ€์‚ฌ๊ณ " }; title = subTypeMap[String(subType)] || "๊ตํ†ต์‚ฌ๊ณ "; } else if (type === "construction") { title = "๋„๋กœ๊ณต์‚ฌ"; } else if (type === "weather" && row.warning && row.level) { // ๋‚ ์”จ ํŠน๋ณด: ๊ณต๋ฐฑ ์ œ๊ฑฐ const warning = String(row.warning).trim(); const level = String(row.level).trim(); title = `${warning} ${level}`; } else { title = row.title || row.์ œ๋ชฉ || row.name || "์•Œ๋ฆผ"; } // ์œ„์น˜ ์ •๋ณด (UTIC XML ๊ธฐ์ค€) - ๊ณต๋ฐฑ ์ œ๊ฑฐ let location = row.addressJibun || row.addressNew || row.roadName || row.linkName || row.subRegion || row.region || row.location || row.์œ„์น˜ || undefined; if (location && typeof location === "string") { location = location.trim(); } // ์„ค๋ช… ์ƒ์„ฑ (๊ฐ„๊ฒฐํ•˜๊ฒŒ) let description = ""; if (row.incidentMsg) { description = row.incidentMsg; } else if (row.eventContent) { description = row.eventContent; } else if (row.period) { description = `๋ฐœํšจ ๊ธฐ๊ฐ„: ${row.period}`; } else if (row.description || row.์„ค๋ช… || row.content) { description = row.description || row.์„ค๋ช… || row.content; } else { // ์„ค๋ช…์ด ์—†์œผ๋ฉด ์œ„์น˜ ์ •๋ณด๋งŒ ํ‘œ์‹œ description = location || "์ƒ์„ธ ์ •๋ณด ์—†์Œ"; } // ํƒ€์ž„์Šคํƒฌํ”„ const timestamp = row.startDate || row.eventDate || row.tmFc || row.tmEf || row.timestamp || row.created_at || new Date().toISOString(); const alert: Alert = { id: row.id || row.alert_id || row.incidentId || row.eventId || row.code || row.subCode || `${sourceName}-${index}-${Date.now()}`, type, severity, title, location, description, timestamp, source: sourceName, }; // console.log(` โœ… Alert ${index}:`, alert); return alert; }); }, []); const loadMultipleDataSources = useCallback(async () => { if (!dataSources || dataSources.length === 0) { return; } setLoading(true); setError(null); // console.log("๐Ÿ”„ RiskAlertTestWidget ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์‹œ์ž‘:", dataSources.length, "๊ฐœ ์†Œ์Šค"); try { const results = await Promise.allSettled( dataSources.map(async (source, index) => { // console.log(`๐Ÿ“ก ๋ฐ์ดํ„ฐ ์†Œ์Šค ${index + 1} ๋กœ๋”ฉ ์ค‘:`, source.name, source.type); if (source.type === "api") { const alerts = await loadRestApiData(source); // console.log(`โœ… ๋ฐ์ดํ„ฐ ์†Œ์Šค ${index + 1} ์™„๋ฃŒ:`, alerts.length, "๊ฐœ ์•Œ๋ฆผ"); return alerts; } else { const alerts = await loadDatabaseData(source); // console.log(`โœ… ๋ฐ์ดํ„ฐ ์†Œ์Šค ${index + 1} ์™„๋ฃŒ:`, alerts.length, "๊ฐœ ์•Œ๋ฆผ"); return alerts; } }) ); const allAlerts: Alert[] = []; results.forEach((result, index) => { if (result.status === "fulfilled") { // console.log(`โœ… ๊ฒฐ๊ณผ ${index + 1} ๋ณ‘ํ•ฉ:`, result.value.length, "๊ฐœ ์•Œ๋ฆผ"); allAlerts.push(...result.value); } else { console.error(`โŒ ๊ฒฐ๊ณผ ${index + 1} ์‹คํŒจ:`, result.reason); } }); // console.log("โœ… ์ด", allAlerts.length, "๊ฐœ ์•Œ๋ฆผ ๋กœ๋”ฉ ์™„๋ฃŒ"); allAlerts.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); setAlerts(allAlerts); setLastRefreshTime(new Date()); } catch (err: any) { console.error("โŒ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์‹คํŒจ:", err); setError(err.message || "๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์‹คํŒจ"); } finally { setLoading(false); } }, [dataSources, loadRestApiData, loadDatabaseData]); // ์ˆ˜๋™ ์ƒˆ๋กœ๊ณ ์นจ ํ•ธ๋“ค๋Ÿฌ const handleManualRefresh = useCallback(() => { // console.log("๐Ÿ”„ ์ˆ˜๋™ ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ ํด๋ฆญ"); loadMultipleDataSources(); }, [loadMultipleDataSources]); // ์ดˆ๊ธฐ ๋กœ๋“œ useEffect(() => { if (dataSources && dataSources.length > 0) { loadMultipleDataSources(); } }, [dataSources, loadMultipleDataSources]); // ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ useEffect(() => { if (!dataSources || dataSources.length === 0) return; // ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ค‘ ๊ฐ€์žฅ ์งง์€ refreshInterval ์ฐพ๊ธฐ const intervals = dataSources .map((ds) => ds.refreshInterval) .filter((interval): interval is number => typeof interval === "number" && interval > 0); if (intervals.length === 0) return; const minInterval = Math.min(...intervals); // console.log(`โฑ๏ธ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ์„ค์ •: ${minInterval}์ดˆ๋งˆ๋‹ค`); const intervalId = setInterval(() => { // console.log("๐Ÿ”„ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ์‹คํ–‰"); loadMultipleDataSources(); }, minInterval * 1000); return () => { // console.log("โน๏ธ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ์ •๋ฆฌ"); clearInterval(intervalId); }; }, [dataSources, loadMultipleDataSources]); const getTypeIcon = (type: AlertType) => { switch (type) { case "accident": return ; case "weather": return ; case "construction": return ; default: return ; } }; const getSeverityColor = (severity: "high" | "medium" | "low") => { switch (severity) { case "high": return "bg-red-500"; case "medium": return "bg-yellow-500"; case "low": return "bg-blue-500"; } }; const filteredAlerts = filter === "all" ? alerts : alerts.filter(a => a.type === filter); if (loading) { return (

๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ค‘...

); } if (error) { return (

โš ๏ธ {error}

); } if (!dataSources || dataSources.length === 0) { return (
๐Ÿšจ

๋ฆฌ์Šคํฌ/์•Œ๋ฆผ

๋‹ค์ค‘ ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ง€์›

  • โ€ข ์—ฌ๋Ÿฌ REST API ๋™์‹œ ์—ฐ๊ฒฐ
  • โ€ข ์—ฌ๋Ÿฌ Database ๋™์‹œ ์—ฐ๊ฒฐ
  • โ€ข REST API + Database ํ˜ผํ•ฉ ๊ฐ€๋Šฅ
  • โ€ข ์•Œ๋ฆผ ํƒ€์ž…๋ณ„ ํ•„ํ„ฐ๋ง

โš™๏ธ ์„ค์ • ๋ฐฉ๋ฒ•

๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์ €์žฅํ•˜์„ธ์š”

); } return (
{/* ํ—ค๋” */}

{element?.customTitle || "๋ฆฌ์Šคํฌ/์•Œ๋ฆผ"}

{dataSources?.length || 0}๊ฐœ ๋ฐ์ดํ„ฐ ์†Œ์Šค โ€ข {alerts.length}๊ฐœ ์•Œ๋ฆผ {lastRefreshTime && ( โ€ข {lastRefreshTime.toLocaleTimeString("ko-KR")} )}

{/* ์ปจํ…์ธ  */}
{["accident", "weather", "construction"].map((type) => { const count = alerts.filter(a => a.type === type).length; return ( ); })}
{filteredAlerts.length === 0 ? (

์•Œ๋ฆผ์ด ์—†์Šต๋‹ˆ๋‹ค

) : ( filteredAlerts.map((alert) => (
{getTypeIcon(alert.type)}

{alert.title}

{alert.severity === "high" && "๊ธด๊ธ‰"} {alert.severity === "medium" && "์ฃผ์˜"} {alert.severity === "low" && "์ •๋ณด"}
{alert.location && (

๐Ÿ“ {alert.location}

)}

{alert.description}

{new Date(alert.timestamp).toLocaleString("ko-KR")} {alert.source && ยท {alert.source}}
)) )}
); }