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, 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([]) const [headers, setHeaders] = useState([]) const [loading, setLoading] = useState(true) const [search, setSearch] = useState('') const [sortKey, setSortKey] = useState(null) const [sortDir, setSortDir] = useState('asc') const [filterKey, setFilterKey] = useState('') const [filterValue, setFilterValue] = useState('') const [latenessRecords, setLatenessRecords] = useState([]) useEffect(() => { fetchRecords() fetchLateness() }, []) const fetchRecords = async () => { setLoading(true) try { const { data } = await api.get('/api/attendance?page=1&page_size=1000') setRecords(data.data || []) if (data.data && data.data.length > 0) { const keys = Object.keys(data.data[0]).filter(k => k !== '_row') setHeaders(keys) } } catch (err) { toast.error('Failed to load records') } finally { setLoading(false) } } const fetchLateness = async () => { try { const { data } = await api.get('/api/attendance/lateness/records') setLatenessRecords(data || []) } catch (err) { console.error('Failed to load lateness data:', err) } } // Create a map of late records for quick lookup const latenessMap = useMemo(() => { const map = {} latenessRecords.forEach(rec => { const key = `${rec.staff}-${rec.date}` map[key] = rec }) return map }, [latenessRecords]) // Filtered and sorted data const processedData = useMemo(() => { let result = [...records] // Filter if (filterKey && filterValue) { result = result.filter(r => { const val = String(r[filterKey] || '').toLowerCase() return val.includes(filterValue.toLowerCase()) }) } // 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, filterKey, filterValue]) const handleSort = (key) => { if (sortKey === key) { setSortDir(sortDir === 'asc' ? 'desc' : 'asc') } else { setSortKey(key) setSortDir('asc') } } const handleFilter = (key) => { setFilterKey(key) setFilterValue('') } const clearFilter = () => { setFilterKey('') setFilterValue('') } const getSortIcon = (key) => { if (sortKey !== key) return null return sortDir === 'asc' ? : } // Get lateness info for a record const getLatenessInfo = (record) => { // Find staff name and date columns const staffKey = Object.keys(record).find(k => k.toLowerCase().includes('staff') || k.toLowerCase().includes('name') || k.toLowerCase().includes('員工') ) const dateKey = Object.keys(record).find(k => k.toLowerCase().includes('date') || k.toLowerCase().includes('日期') ) if (!staffKey || !dateKey) return null const staff = record[staffKey] const date = record[dateKey] if (!staff || !date) return null const key = `${staff}-${String(date).slice(0, 10)}` return latenessMap[key] } 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" />
{/* Column Filter */}
{filterKey && ( <> setFilterValue(e.target.value)} placeholder="輸入篩選值..." className="text-sm border border-slate-300 rounded-md px-2 py-2 w-32" /> )}
{/* Result count */}
{processedData.length} 筆記錄 {latenessRecords.length > 0 && ( • {latenessRecords.length} 筆遲到 )}
{/* Table */}
{headers.slice(0, 6).map(h => ( ))} {loading ? ( ) : processedData.length === 0 ? ( ) : ( processedData.map((record) => { const lateInfo = getLatenessInfo(record) const isLate = lateInfo && lateInfo.minutes_late > 0 return ( 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]) : '-'} {isLate ? (
{lateInfo.minutes_late}分
) : (
正常
)}
e.stopPropagation()}>
) }