Files
aars/frontend/src/pages/attendance/Detail.jsx
T
IT狗 eafc1c33fc Add full AARS system - Attendance & Accident Record System
- 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
2026-07-16 03:16:33 +08:00

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 AttendanceDetail() {
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/attendance?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="/attendance"
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('/attendance')}
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="/attendance"
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>
)
}