eafc1c33fc
- FastAPI backend with SQLite database - React + TailwindCSS frontend - JWT authentication - Excel upload/download for attendance and accident records - Shift roster management with S7a/S7b split - Lateness/OT calculation based on shift times - Roster info API endpoints - Export API with token query parameter support - Docker compose deployment
130 lines
3.8 KiB
React
130 lines
3.8 KiB
React
import { useState, useEffect } from 'react'
|
|
import { useParams, useNavigate, Link } from 'react-router-dom'
|
|
import api from '../../api'
|
|
import toast from 'react-hot-toast'
|
|
import { ArrowLeft, FileText } from 'lucide-react'
|
|
|
|
export default function AccidentDetail() {
|
|
const { id } = useParams()
|
|
const navigate = useNavigate()
|
|
const [record, setRecord] = useState(null)
|
|
const [headers, setHeaders] = useState([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState(null)
|
|
|
|
useEffect(() => {
|
|
fetchRecord()
|
|
}, [id])
|
|
|
|
const fetchRecord = async () => {
|
|
setLoading(true)
|
|
setError(null)
|
|
try {
|
|
// Get all records to find the one with this row id
|
|
const { data } = await api.get(`/api/accident?page=1&page_size=1000`)
|
|
|
|
if (data.data && data.data.length > 0) {
|
|
// Extract headers from first record
|
|
const keys = Object.keys(data.data[0]).filter(k => k !== '_row')
|
|
setHeaders(keys)
|
|
|
|
// Find the record with matching _row
|
|
const found = data.data.find(r => r._row === parseInt(id))
|
|
if (found) {
|
|
setRecord(found)
|
|
} else {
|
|
setError('Record not found')
|
|
}
|
|
} else {
|
|
setError('No data available')
|
|
}
|
|
} catch (err) {
|
|
console.error('Error fetching record:', err)
|
|
setError('Failed to load record')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center py-12">
|
|
<div className="text-slate-500">載入中...</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-4">
|
|
<Link
|
|
to="/accident"
|
|
className="flex items-center gap-2 text-slate-600 hover:text-slate-900"
|
|
>
|
|
<ArrowLeft size={20} />
|
|
返回列表
|
|
</Link>
|
|
</div>
|
|
<div className="card p-8 text-center">
|
|
<div className="text-red-500">{error}</div>
|
|
<button
|
|
onClick={() => navigate('/accident')}
|
|
className="mt-4 px-4 py-2 text-sm text-primary-600 hover:text-primary-700"
|
|
>
|
|
返回列表
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Link
|
|
to="/accident"
|
|
className="flex items-center gap-2 text-slate-600 hover:text-slate-900"
|
|
>
|
|
<ArrowLeft size={20} />
|
|
返回
|
|
</Link>
|
|
<h1 className="text-2xl font-bold text-slate-900">
|
|
意外記錄 #{id}
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Record Details */}
|
|
<div className="card">
|
|
<div className="p-4 border-b border-slate-200 bg-slate-50">
|
|
<div className="flex items-center gap-3">
|
|
<FileText size={24} className="text-slate-400" />
|
|
<div>
|
|
<div className="font-medium text-slate-900">記錄詳情</div>
|
|
<div className="text-sm text-slate-500">Excel 原始數據</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="divide-y divide-slate-100">
|
|
{headers.map(key => (
|
|
<div key={key} className="grid grid-cols-12">
|
|
<div className="col-span-3 px-4 py-3 text-sm font-medium text-slate-500 bg-slate-50">
|
|
{key}
|
|
</div>
|
|
<div className="col-span-9 px-4 py-3 text-sm text-slate-900">
|
|
{record[key] !== null && record[key] !== undefined
|
|
? String(record[key])
|
|
: '-'}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|