diff --git a/backend/main.py b/backend/main.py index 206e973..5fa41f3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1099,9 +1099,14 @@ async def attendance_status_texts( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): - """Return distinct status_text values for filter dropdown.""" - rows = db.query(AttendanceRecord.status_text).distinct().all() - return [r[0] for r in rows if r[0]] + """Return BASIC status categories for filter dropdown. + + The frontend renders only 5 basic statuses (正常/異常/缺勤/公假/請假), + so the dropdown should offer those exact values rather than every + raw status_text variant (e.g. '異常-遲到39分'). Keeping it server-driven + so client and server agree on the filter vocabulary. + """ + return ['正常', '異常', '缺勤', '公假', '請假'] @app.get("/api/attendance/stats") async def attendance_stats( @@ -1263,41 +1268,35 @@ async def attendance_records( if status: q = q.filter(AttendanceRecord.status_code == status) if status_text: - # Map status_text filter to actual conditions - if status_text == 'abnormal': - q = q.filter( - (AttendanceRecord.late_minutes > 30) | - (AttendanceRecord.early_minutes > 30) | - (AttendanceRecord.ot_minutes > 30) - ) + # Map status_text filter to status_code conditions so the result set + # stays aligned with the badge rendered from (post-enrich) status_code. + # Previously used late/early/OT thresholds which mismatched because + # the import path can leave late_minutes=0 while status_code='abnormal' + # (e.g. act_in=18:00 vs exp_in=11:00), and missing records also have + # all three == 0 despite status_code='missing'. + # Accept both English status_code values AND the Chinese basic-status + # labels returned by /api/attendance/status_texts dropdown. + abnormal_codes = ('abnormal', 'late', 'early', 'ot', + 'late_early', 'late_ot', 'early_ot') + if status_text in ('abnormal', '異常'): + q = q.filter(AttendanceRecord.status_code.in_(abnormal_codes)) elif status_text == 'late': - q = q.filter( - (AttendanceRecord.late_minutes > 0) & - (AttendanceRecord.late_minutes <= 30) - ) + q = q.filter(AttendanceRecord.status_code.in_(('late', 'late_early', 'late_ot'))) elif status_text == 'early': - q = q.filter( - (AttendanceRecord.early_minutes > 0) & - (AttendanceRecord.early_minutes <= 30) - ) + q = q.filter(AttendanceRecord.status_code.in_(('early', 'late_early', 'early_ot'))) elif status_text == 'ot': - q = q.filter( - (AttendanceRecord.ot_minutes > 0) & - (AttendanceRecord.ot_minutes <= 30) - ) - elif status_text == 'missing': + q = q.filter(AttendanceRecord.status_code.in_(('ot', 'late_ot', 'early_ot'))) + elif status_text in ('missing', '缺勤'): q = q.filter(AttendanceRecord.status_code == 'missing') - elif status_text in ('holiday', 'al', 'sl', 'cl', 'mixed_leave'): + elif status_text in ('holiday', '公假'): + q = q.filter(AttendanceRecord.status_code == 'holiday') + elif status_text in ('al', 'sl', 'cl', 'mixed_leave'): q = q.filter(AttendanceRecord.status_code == status_text) - elif status_text == 'leave': + elif status_text in ('leave', '請假'): # Any leave type — group filter q = q.filter(AttendanceRecord.status_code.in_(['al', 'sl', 'cl', 'mixed_leave'])) - elif status_text == 'normal': - q = q.filter( - (AttendanceRecord.late_minutes == 0) & - (AttendanceRecord.early_minutes == 0) & - (AttendanceRecord.ot_minutes == 0) - ) + elif status_text in ('normal', '正常'): + q = q.filter(AttendanceRecord.status_code == 'normal') else: # Fallback to text search q = q.filter(AttendanceRecord.status_text.like(f"%{status_text}%")) diff --git a/frontend/src/pages/attendance/List.jsx b/frontend/src/pages/attendance/List.jsx index 09b7903..eac8988 100644 --- a/frontend/src/pages/attendance/List.jsx +++ b/frontend/src/pages/attendance/List.jsx @@ -87,18 +87,20 @@ export default function AttendanceList() { } const getStatusBadge = (record) => { - const lm = record.late_minutes || 0 - const em = record.early_minutes || 0 - const om = record.ot_minutes || 0 - return + // StatusBadge now drives from status_code + status_text only (basic 5 categories). + 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' + // Match the basic-status grouping so highlights line up with the badge. + const c = String(code || '').toLowerCase() + if (c === 'abnormal' || c === 'late' || c === 'early' || c === 'ot' + || c === 'late_early' || c === 'late_ot' || c === 'early_ot') { + return 'bg-orange-50 hover:bg-orange-100' + } + if (c === 'missing') return 'bg-gray-100 hover:bg-gray-200' + if (c === 'holiday') return 'bg-indigo-50 hover:bg-indigo-100' + if (['al', 'sl', 'cl', 'mixed_leave'].includes(c)) return 'bg-violet-50 hover:bg-violet-100' return 'hover:bg-slate-50' }