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'
}