import { useState, useEffect, useMemo, useRef } from 'react' import { Link } from 'react-router-dom' import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, Legend, LineChart, Line } from 'recharts' import { AlertTriangle, MapPin, Users, FileText, Calendar, TrendingUp, XCircle, CheckCircle2, Download, Search, ChevronUp, ChevronDown, Filter, X, Activity } from 'lucide-react' import api from '../../api' import toast from 'react-hot-toast' const SEVERITY_COLORS = { '1': { bg: '#D1FAE5', fg: '#065F46', label: '低 Low' }, '2': { bg: '#DBEAFE', fg: '#1E40AF', label: '中 Medium' }, '3': { bg: '#FEF3C7', fg: '#92400E', label: '高 High' }, '4': { bg: '#FED7AA', fg: '#9A3412', label: '嚴重 Severe' }, '5': { bg: '#FEE2E2', fg: '#7F1D1D', label: '極嚴重 Critical' }, } export default function AccidentDashboard() { const [summary, setSummary] = useState(null) const [stats, setStats] = useState(null) const [records, setRecords] = useState([]) const [loading, setLoading] = useState(true) const [dateFrom, setDateFrom] = useState('') const [dateTo, setDateTo] = useState('') const dashboardRef = useRef(null) const fmt = (d) => d.toISOString().slice(0, 10) const weekRange = (offsetWeeks) => { const now = new Date() const dow = now.getDay() const daysFromMonday = (dow + 6) % 7 const start = new Date(now) start.setDate(now.getDate() - daysFromMonday + offsetWeeks * 7) const end = new Date(start) end.setDate(start.getDate() + 6) return [fmt(start), fmt(end)] } const today = new Date().toISOString().slice(0, 10) const monthStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().slice(0, 10) useEffect(() => { fetchData() }, [dateFrom, dateTo]) const fetchData = async () => { setLoading(true) try { const params = {} if (dateFrom) params.date_from = dateFrom if (dateTo) params.date_to = dateTo const [sumRes, statRes, recRes] = await Promise.all([ api.get('/api/accident/dashboard/summary', { params }), api.get('/api/accident/dashboard/stats', { params }), api.get('/api/accident/records', { params: { ...params, per_page: 1000 } }).catch(() => ({ data: { records: [] } })), ]) setSummary(sumRes.data) setStats(statRes.data) setRecords(recRes.data.records || []) } catch (err) { toast.error('載入失敗') console.error(err) } finally { setLoading(false) } } const handleExportExcel = async () => { try { const params = {} if (dateFrom) params.date_from = dateFrom if (dateTo) params.date_to = dateTo const response = await api.get('/api/accident/export/excel', { params, responseType: 'blob' }) const url = window.URL.createObjectURL(new Blob([response.data])) const link = document.createElement('a') link.href = url let suffix = '' if (dateFrom && dateTo) suffix = `_${dateFrom}_${dateTo}` else if (dateFrom) suffix = `_from_${dateFrom}` else if (dateTo) suffix = `_to_${dateTo}` link.setAttribute('download', `accident${suffix}.xlsx`) document.body.appendChild(link) link.click() link.remove() toast.success('Excel 已下載') } catch (err) { toast.error('匯出失敗') } } // Build donut chart data (severity distribution) const severityData = useMemo(() => { if (!summary?.severity_count) return [] return Object.entries(summary.severity_count) .filter(([_, count]) => count > 0) .map(([sev, count]) => ({ name: SEVERITY_COLORS[sev]?.label || `Sev ${sev}`, value: count, severity: sev, color: SEVERITY_COLORS[sev]?.fg || '#6B7280', })) }, [summary]) // Build line chart (monthly trend) const monthlyTrend = useMemo(() => { const byMonth = {} for (const r of records) { if (!r.date) continue const ym = r.date.substring(0, 7) byMonth[ym] = (byMonth[ym] || 0) + 1 } return Object.entries(byMonth) .sort(([a], [b]) => a.localeCompare(b)) .map(([month, count]) => ({ month, count })) }, [records]) if (loading && !summary) { return
顯示 {dateFrom || '開始'} ~ {dateTo || today} 的數據
)}暫無數據
) : (暫無數據
) : ({label}
{value}
暫無數據
) : data.map((s, i) => (