"use client"; import React, { useState, useEffect } 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 } from "lucide-react"; import { apiClient } from "@/lib/api/client"; import { DashboardElement } from "@/components/admin/dashboard/types"; // 알림 타입 type AlertType = "accident" | "weather" | "construction"; // 알림 인터페이스 interface Alert { id: string; type: AlertType; severity: "high" | "medium" | "low"; title: string; location: string; description: string; timestamp: string; } interface RiskAlertWidgetProps { element?: DashboardElement; } export default function RiskAlertWidget({ element }: RiskAlertWidgetProps) { const [alerts, setAlerts] = useState([]); const [isRefreshing, setIsRefreshing] = useState(false); const [filter, setFilter] = useState("all"); const [lastUpdated, setLastUpdated] = useState(null); const [newAlertIds, setNewAlertIds] = useState>(new Set()); // 데이터 로드 (백엔드 캐시 조회) const loadData = async () => { setIsRefreshing(true); try { // 백엔드 API 호출 (캐시된 데이터) const response = await apiClient.get<{ success: boolean; data: Alert[]; count: number; lastUpdated?: string; cached?: boolean; }>("/risk-alerts"); if (response.data.success && response.data.data) { const newData = response.data.data; // 새로운 알림 감지 const oldIds = new Set(alerts.map(a => a.id)); const newIds = new Set(); newData.forEach(alert => { if (!oldIds.has(alert.id)) { newIds.add(alert.id); } }); setAlerts(newData); setNewAlertIds(newIds); setLastUpdated(new Date()); // 3초 후 새 알림 애니메이션 제거 if (newIds.size > 0) { setTimeout(() => setNewAlertIds(new Set()), 3000); } } else { console.error("❌ 리스크 알림 데이터 로드 실패"); setAlerts([]); } } catch (error: any) { console.error("❌ 리스크 알림 API 오류:", error.message); // API 오류 시 빈 배열 유지 setAlerts([]); } finally { setIsRefreshing(false); } }; // 강제 새로고침 (실시간 API 호출) const forceRefresh = async () => { setIsRefreshing(true); try { // 강제 갱신 API 호출 (실시간 데이터) const response = await apiClient.post<{ success: boolean; data: Alert[]; count: number; message?: string; }>("/risk-alerts/refresh", {}); if (response.data.success && response.data.data) { const newData = response.data.data; // 새로운 알림 감지 const oldIds = new Set(alerts.map(a => a.id)); const newIds = new Set(); newData.forEach(alert => { if (!oldIds.has(alert.id)) { newIds.add(alert.id); } }); setAlerts(newData); setNewAlertIds(newIds); setLastUpdated(new Date()); // 3초 후 새 알림 애니메이션 제거 if (newIds.size > 0) { setTimeout(() => setNewAlertIds(new Set()), 3000); } } else { console.error("❌ 리스크 알림 강제 갱신 실패"); } } catch (error: any) { console.error("❌ 리스크 알림 강제 갱신 오류:", error.message); } finally { setIsRefreshing(false); } }; useEffect(() => { loadData(); // 1분마다 자동 새로고침 (60000ms) const interval = setInterval(loadData, 60000); return () => clearInterval(interval); }, []); // 필터링된 알림 const filteredAlerts = filter === "all" ? alerts : alerts.filter((alert) => alert.type === filter); // 알림 타입별 아이콘 const getAlertIcon = (type: AlertType) => { switch (type) { case "accident": return ; case "weather": return ; case "construction": return ; } }; // 알림 타입별 한글명 const getAlertTypeName = (type: AlertType) => { switch (type) { case "accident": return "교통사고"; case "weather": return "날씨특보"; case "construction": return "도로공사"; } }; // 시간 포맷 const formatTime = (isoString: string) => { const date = new Date(isoString); const now = new Date(); const diffMinutes = Math.floor((now.getTime() - date.getTime()) / 60000); if (diffMinutes < 1) return "방금 전"; if (diffMinutes < 60) return `${diffMinutes}분 전`; const diffHours = Math.floor(diffMinutes / 60); if (diffHours < 24) return `${diffHours}시간 전`; return `${Math.floor(diffHours / 24)}일 전`; }; // 통계 계산 const stats = { accident: alerts.filter((a) => a.type === "accident").length, weather: alerts.filter((a) => a.type === "weather").length, construction: alerts.filter((a) => a.type === "construction").length, high: alerts.filter((a) => a.severity === "high").length, }; return (
{/* 헤더 */}

{element?.customTitle || "리스크 / 알림"}

{stats.high > 0 && ( 긴급 {stats.high}건 )}
{lastUpdated && newAlertIds.size > 0 && ( 새 알림 {newAlertIds.size}건 )} {lastUpdated && ( {lastUpdated.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' })} )}
{/* 통계 카드 */}
setFilter(filter === "accident" ? "all" : "accident")} >
교통사고
{stats.accident}건
setFilter(filter === "weather" ? "all" : "weather")} >
날씨특보
{stats.weather}건
setFilter(filter === "construction" ? "all" : "construction")} >
도로공사
{stats.construction}건
{/* 필터 상태 표시 */} {filter !== "all" && (
{getAlertTypeName(filter)} 필터 적용 중
)} {/* 알림 목록 */}
{filteredAlerts.length === 0 ? (
알림이 없습니다
) : ( filteredAlerts.map((alert) => (
{getAlertIcon(alert.type)}

{alert.title}

{newAlertIds.has(alert.id) && ( NEW )} {alert.severity === "high" ? "긴급" : alert.severity === "medium" ? "주의" : "정보"}

{alert.location}

{alert.description}

{formatTime(alert.timestamp)}
)) )}
{/* 안내 메시지 */}
💡 1분마다 자동으로 업데이트됩니다
); }