import { useState, useEffect, useMemo } from 'react' import { Link } from 'react-router-dom' import api from '../../api' import toast from 'react-hot-toast' import { Search, ChevronUp, ChevronDown, Filter, X, Users, Calendar } from 'lucide-react' import StatusBadge from '../../components/StatusBadge' export default function AttendanceList() { const [records, setRecords] = useState([]) const [total, setTotal] = useState(0) const [loading, setLoading] = useState(true) const [search, setSearch] = useState('') const [sortBy, setSortBy] = useState('date') const [sortOrder, setSortOrder] = useState('desc') const [dateFrom, setDateFrom] = useState('') const [dateTo, setDateTo] = useState('') const [staffFilter, setStaffFilter] = useState('') const [statusFilter, setStatusFilter] = useState('') const [page, setPage] = useState(1) const [statusOptions, setStatusOptions] = useState([]) const perPage = 50 const [staffList, setStaffList] = useState([]) useEffect(() => { fetchRecords() fetchStatusTexts() // eslint-disable-next-line react-hooks/exhaustive-deps }, [page, sortBy, sortOrder, dateFrom, dateTo, staffFilter, statusFilter, search]) const fetchStatusTexts = async () => { try { const { data } = await api.get('/api/attendance/status_texts') setStatusOptions(data || []) } catch (e) { console.error('Failed to fetch status texts', e) } } const fetchRecords = async () => { setLoading(true) try { const params = new URLSearchParams({ page, per_page: perPage, sort_by: sortBy, sort_order: sortOrder, }) if (search) params.append('search', search) if (dateFrom) params.append('date_from', dateFrom) if (dateTo) params.append('date_to', dateTo) if (staffFilter) params.append('staff', staffFilter) if (statusFilter) params.append('status_text', statusFilter) const { data } = await api.get(`/api/attendance/records?${params}`) setRecords(data.records || []) setTotal(data.total || 0) // Extract unique staff names const names = [...new Set((data.records || []).map(r => r.employee_name).filter(Boolean))] setStaffList(names.sort()) } catch (err) { toast.error('載入失敗') console.error(err) } finally { setLoading(false) } } const handleSearch = (e) => { e.preventDefault() setPage(1) fetchRecords() } const handleSort = (key) => { if (sortBy === key) { setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') } else { setSortBy(key) setSortOrder('asc') } setPage(1) } const getSortIcon = (key) => { if (sortBy !== key) return null return sortOrder === 'asc' ? : } const getStatusBadge = (record) => { return } const getRowClass = (code) => { if (code === 'abnormal') return 'bg-red-50 hover:bg-red-100' if (code === 'missing') return 'bg-gray-100 hover:bg-gray-200' if (code === 'late' || code === 'late_early' || code === 'late_ot') return 'bg-orange-50 hover:bg-orange-100' if (code === 'early' || code === 'early_ot') return 'bg-blue-50 hover:bg-blue-100' if (code === 'ot') return 'bg-purple-50 hover:bg-purple-100' return 'hover:bg-slate-50' } const totalPages = Math.ceil(total / perPage) const columns = [ { key: 'employee_name', label: '員工' }, { key: 'date', label: '日期' }, { key: 'weekday', label: '星期' }, { key: 'shift_code', label: '班次' }, { key: 'actual_in', label: '上班' }, { key: 'actual_out', label: '下班' }, { key: 'expected_in', label: '應上班' }, { key: 'expected_out', label: '應下班' }, ] return (
{/* Header */}

出勤記錄 Attendance

Dashboard 上傳 Excel
{/* Filters */}
{/* Search row */}
setSearch(e.target.value)} placeholder="搜尋員工姓名..." className="w-full pl-9 pr-4 py-2 text-sm border border-slate-300 rounded-md" />
{/* Filter row */}
日期: { setDateFrom(e.target.value); setPage(1) }} className="text-sm border border-slate-300 rounded-md px-2 py-1.5" placeholder="由" /> - { setDateTo(e.target.value); setPage(1) }} className="text-sm border border-slate-300 rounded-md px-2 py-1.5" placeholder="至" />
員工:
狀態:
{/* Stats bar */}
{total} 筆記錄 {staffFilter && • 員工: {staffFilter}} {statusFilter && • 狀態: {statusFilter}} {(dateFrom || dateTo) && • {dateFrom || '...'} 至 {dateTo || '...'}}
{/* Table */}
{columns.map(col => ( ))} {loading ? ( ) : records.length === 0 ? ( ) : ( records.map((record, idx) => ( )) )}
# handleSort(col.key)} className="px-3 py-2.5 text-left text-xs font-semibold text-slate-600 cursor-pointer hover:bg-slate-200" >
{col.label} {getSortIcon(col.key)}
狀態 操作
載入中...
沒有記錄
{(page - 1) * perPage + idx + 1} {record.employee_name || '-'} {record.date || '-'} {record.weekday || '-'} {record.shift_code || '-'} {record.actual_in || '-'} {record.actual_out || '-'} {record.expected_in || '-'} {record.expected_out || '-'} {getStatusBadge(record)}
{/* Pagination */} {totalPages > 1 && (
第 {page} / {totalPages} 頁,共 {total} 筆記錄
)}
) }