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, FileCode } from 'lucide-react' import StatusBadge from '../components/StatusBadge' 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([]) const [headers, setHeaders] = useState([]) const [loading, setLoading] = useState(true) const [search, setSearch] = useState('') const [sortKey, setSortKey] = useState(null) const [sortDir, setSortDir] = useState('asc') const [statusFilter, setStatusFilter] = useState('') useEffect(() => { fetchRecords() }, [statusFilter]) const fetchRecords = async () => { setLoading(true) try { const params = { page: 1, per_page: 1000 } if (statusFilter) params.status_text = statusFilter const { data } = await api.get('/api/attendance/records', { params }) setRecords(data.records || []) if (data.records && data.records.length > 0) { const keys = Object.keys(data.records[0]).filter(k => k !== '_row') setHeaders(keys) } } catch (err) { toast.error('Failed to load records') } finally { setLoading(false) } } // Filtered and sorted data (client-side: search + sort only; status handled by backend) const processedData = useMemo(() => { let result = [...records] // Search if (search) { const searchLower = search.toLowerCase() result = result.filter(r => Object.values(r).some(v => v && String(v).toLowerCase().includes(searchLower) ) ) } // Sort if (sortKey) { result.sort((a, b) => { const aVal = a[sortKey] || '' const bVal = b[sortKey] || '' const aNum = Number(aVal) const bNum = Number(bVal) let cmp = 0 if (!isNaN(aNum) && !isNaN(bNum)) { cmp = aNum - bNum } else { cmp = String(aVal).localeCompare(String(bVal)) } return sortDir === 'asc' ? cmp : -cmp }) } return result }, [records, search, sortKey, sortDir]) const handleSort = (key) => { if (sortKey === key) { setSortDir(sortDir === 'asc' ? 'desc' : 'asc') } else { setSortKey(key) setSortDir('asc') } } const getSortIcon = (key) => { if (sortKey !== key) return null return sortDir === 'asc' ? : } return (
{/* Header */}

出勤記錄 Attendance

上傳 Excel
{/* Filters */}
{/* Search */}
setSearch(e.target.value)} placeholder="搜尋所有欄位..." className="w-full pl-9 pr-4 py-2 text-sm border border-slate-300 rounded-md" />
{/* Status Filter (basic categories — server-side) */}
{statusFilter && ( )}
{/* Result count */}
{processedData.length} 筆記錄 {statusFilter && ( • 已篩選:{statusFilter} )}
{/* Table */}
{headers.slice(0, 6).map(h => ( ))} {loading ? ( ) : processedData.length === 0 ? ( ) : ( processedData.map((record) => ( window.location.href = `/attendance/${record._row}`} > {headers.slice(0, 6).map(h => ( ))} )) )}
# handleSort(h)} >
{h} {getSortIcon(h)}
狀態 操作
載入中...
沒有記錄
{record._row} {record[h] !== null && record[h] !== undefined ? String(record[h]) : '-'} e.stopPropagation()}>
) }