Add HTML5 report export for Attendance and Accident

- Backend: /api/report/{section}/html endpoint generates self-contained HTML5 report
- Frontend: Export buttons added to Dashboard, Attendance List, Accident List
- Report features: embedded data, 3 interactive Chart.js charts, stats cards,
  sortable/filterable table with color-coded rows, popup detail on click,
  click chart segment to auto-filter table, print support
- Template: report_template.html with responsive design, Chinese labels
This commit is contained in:
IT狗
2026-07-20 23:21:12 +08:00
parent eafc1c33fc
commit 535d10a885
5 changed files with 735 additions and 15 deletions
+16 -1
View File
@@ -1,7 +1,14 @@
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import api from '../api'
import { Users, AlertTriangle, FileSpreadsheet, RefreshCw, TrendingUp, MapPin, Clock, AlertCircle } from 'lucide-react'
import { Users, AlertTriangle, FileSpreadsheet, RefreshCw, TrendingUp, MapPin, Clock, AlertCircle, FileCode } from 'lucide-react'
const handleExportHTML = (section) => {
const token = localStorage.getItem('aars_token')
if (token) {
window.open(`/api/report/${section}/html?token=${token}`, '_blank')
}
}
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, LineChart, Line } from 'recharts'
const COLORS = ['#3b82f6', '#ef4444', '#22c55e', '#f59e0b', '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16']
@@ -326,6 +333,14 @@ export default function Dashboard() {
{/* Quick Actions */}
<div className="flex flex-wrap gap-3">
<button onClick={() => handleExportHTML('attendance')} className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm rounded-md hover:bg-blue-700">
<FileCode size={16} />
Attendance HTML Report
</button>
<button onClick={() => handleExportHTML('accident')} className="flex items-center gap-2 px-4 py-2 bg-red-600 text-white text-sm rounded-md hover:bg-red-700">
<FileCode size={16} />
Accident HTML Report
</button>
<Link to="/upload" className="flex items-center gap-2 px-4 py-2 bg-primary-600 text-white text-sm rounded-md hover:bg-primary-700">
<FileSpreadsheet size={16} />
上傳 Excel
+23 -7
View File
@@ -2,7 +2,14 @@ import { useState, useEffect, useMemo } from 'react'
import { Link } from 'react-router-dom'
import api from '../../api'
import toast from 'react-hot-toast'
import { Search, Eye, ChevronUp, ChevronDown, Filter, X } from 'lucide-react'
import { Search, Eye, ChevronUp, ChevronDown, Filter, X, FileCode } from 'lucide-react'
const handleExportHTML = (section) => {
const token = localStorage.getItem('aars_token')
if (token) {
window.open(`/api/report/${section}/html?token=${token}`, '_blank')
}
}
export default function AccidentList() {
const [records, setRecords] = useState([])
@@ -107,12 +114,21 @@ export default function AccidentList() {
{/* Header */}
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold text-slate-900">意外記錄 Accident</h1>
<Link
to="/upload"
className="flex items-center gap-2 px-4 py-2 bg-red-600 text-white text-sm rounded-md hover:bg-red-700"
>
上傳 Excel
</Link>
<div className="flex gap-2">
<button
onClick={() => handleExportHTML('accident')}
className="flex items-center gap-2 px-4 py-2 bg-slate-700 text-white text-sm rounded-md hover:bg-slate-800"
>
<FileCode size={16} />
匯出 HTML Report
</button>
<Link
to="/upload"
className="flex items-center gap-2 px-4 py-2 bg-red-600 text-white text-sm rounded-md hover:bg-red-700"
>
上傳 Excel
</Link>
</div>
</div>
{/* Filters */}
+23 -7
View File
@@ -2,7 +2,14 @@ import { useState, useEffect, useMemo } from 'react'
import { Link } from 'react-router-dom'
import api from '../../api'
import toast from 'react-hot-toast'
import { Search, Eye, ChevronUp, ChevronDown, Filter, X, AlertCircle, Clock } from 'lucide-react'
import { Search, Eye, ChevronUp, ChevronDown, Filter, X, AlertCircle, Clock, FileCode } from 'lucide-react'
const handleExportHTML = (section) => {
const token = localStorage.getItem('aars_token')
if (token) {
window.open(`/api/report/${section}/html?token=${token}`, '_blank')
}
}
export default function AttendanceList() {
const [records, setRecords] = useState([])
@@ -149,12 +156,21 @@ export default function AttendanceList() {
{/* Header */}
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold text-slate-900">出勤記錄 Attendance</h1>
<Link
to="/upload"
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm rounded-md hover:bg-blue-700"
>
上傳 Excel
</Link>
<div className="flex gap-2">
<button
onClick={() => handleExportHTML('attendance')}
className="flex items-center gap-2 px-4 py-2 bg-slate-700 text-white text-sm rounded-md hover:bg-slate-800"
>
<FileCode size={16} />
匯出 HTML Report
</button>
<Link
to="/upload"
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm rounded-md hover:bg-blue-700"
>
上傳 Excel
</Link>
</div>
</div>
{/* Filters */}