From 33a5e3b2ad2f558966228c15ff94f5c3a7300c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IT=E7=8B=97?= Date: Tue, 21 Jul 2026 18:10:43 +0800 Subject: [PATCH] StatusBadge: split badges per dimension (abnormal/late/early/OT <=30min); Attendance filter by status_code conditions for abnormal/late/early/OT/normal/leave types --- backend/main.py | 36 ++++++- frontend/src/components/StatusBadge.jsx | 123 +++++------------------- frontend/src/pages/attendance/List.jsx | 5 +- 3 files changed, 61 insertions(+), 103 deletions(-) diff --git a/backend/main.py b/backend/main.py index e7a24b5..ce5a1f3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1267,7 +1267,41 @@ async def attendance_records( if status: q = q.filter(AttendanceRecord.status_code == status) if status_text: - q = q.filter(AttendanceRecord.status_text.like(f"%{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) + ) + elif status_text == 'late': + q = q.filter( + (AttendanceRecord.late_minutes > 0) & + (AttendanceRecord.late_minutes <= 30) + ) + elif status_text == 'early': + q = q.filter( + (AttendanceRecord.early_minutes > 0) & + (AttendanceRecord.early_minutes <= 30) + ) + 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 == 'missing') + elif status_text in ('holiday', 'al', 'sl', 'cl', 'mixed_leave'): + q = q.filter(AttendanceRecord.status_code == status_text) + elif status_text == 'normal': + q = q.filter( + (AttendanceRecord.late_minutes == 0) & + (AttendanceRecord.early_minutes == 0) & + (AttendanceRecord.ot_minutes == 0) + ) + else: + # Fallback to text search + q = q.filter(AttendanceRecord.status_text.like(f"%{status_text}%")) if search: like = f"%{search}%" q = q.filter(or_( diff --git a/frontend/src/components/StatusBadge.jsx b/frontend/src/components/StatusBadge.jsx index 238a16f..9abfd67 100644 --- a/frontend/src/components/StatusBadge.jsx +++ b/frontend/src/components/StatusBadge.jsx @@ -2,25 +2,20 @@ import { CheckCircle, AlertCircle, Clock, X, AlertTriangle, Briefcase, Stethosco /** * Reusable status badge for attendance records. - * Handles all status codes including enriched leave types: - * normal / late / late_early / late_ot / early / early_ot / ot / abnormal / missing - * holiday / al / sl / cl / mixed_leave + * Renders one badge per status dimension (abnormal / late / early / OT / leave). * * Props: - * - code: status_code string - * - text: status_text string (already enriched) + * - code: status_code string (used for row-coloring and fallback) + * - late: late_minutes number + * - early: early_minutes number + * - ot: ot_minutes number * - size: 'sm' | 'md' (default 'sm') */ -export default function StatusBadge({ code, text = '', size = 'sm' }) { +export default function StatusBadge({ code, late = 0, early = 0, ot = 0, size = 'sm' }) { const sizeCls = size === 'md' ? 'px-3 py-1 text-sm' : 'px-2 py-0.5 text-xs' - // Split text into main + leave parts (after ' + ' separator) - const parts = text.split(/\s*\+\s*/).filter(Boolean) - const mainText = parts[0] || '' - const leaveParts = parts.slice(1) - const badge = (icon, label, colorCls) => ( {icon} @@ -28,97 +23,23 @@ export default function StatusBadge({ code, text = '', size = 'sm' }) { ) - // ============ Attendance status badges ============ - if (code === 'normal') { - return ( - - {badge(, mainText || '正常', 'bg-green-100 text-green-700')} - {leaveParts.map((lp, i) => ( - - + {lp} - - ))} - - ) - } - if (code === 'late' || code === 'late_early' || code === 'late_ot') { - return ( - - {badge(, mainText || code, 'bg-orange-100 text-orange-700')} - {leaveParts.map((lp, i) => ( - - + {lp} - - ))} - - ) - } - if (code === 'early' || code === 'early_ot') { - return ( - - {badge(, mainText || code, 'bg-blue-100 text-blue-700')} - {leaveParts.map((lp, i) => ( - - + {lp} - - ))} - - ) - } - if (code === 'ot') { - return ( - - {badge(, mainText || '加班', 'bg-purple-100 text-purple-700')} - {leaveParts.map((lp, i) => ( - - + {lp} - - ))} - - ) - } - if (code === 'abnormal') { - return ( - - {badge(, mainText || '⚠️異常', 'bg-red-100 text-red-700')} - {leaveParts.map((lp, i) => ( - - + {lp} - - ))} - - ) - } - if (code === 'missing') { - return ( - - {badge(, '缺勤', 'bg-gray-200 text-gray-600')} - {leaveParts.map((lp, i) => ( - - {lp} - - ))} - - ) + const parts = [] + + // Abnormal (any dimension > 30 min) + if (late > 30 || early > 30 || ot > 30) { + if (late > 30) parts.push(badge(, `異常-遲到${late}分`, 'bg-red-100 text-red-700')) + else if (early > 30) parts.push(badge(, `異常-早退${early}分`, 'bg-red-100 text-red-700')) + else parts.push(badge(, `異常-OT${ot}分`, 'bg-red-100 text-red-700')) + } else { + // Individual badges (≤ 30 min each) + if (late > 0) parts.push(badge(, `遲到${late}分`, 'bg-orange-100 text-orange-700')) + if (early > 0) parts.push(badge(, `早退${early}分`, 'bg-blue-100 text-blue-700')) + if (ot > 0) parts.push(badge(, `OT${ot}分`, 'bg-purple-100 text-purple-700')) } - // ============ Enriched leave status badges ============ - if (code === 'holiday') { - return badge(, mainText, 'bg-amber-100 text-amber-700') - } - if (code === 'al') { - return badge(, mainText || '年假', 'bg-emerald-100 text-emerald-700') - } - if (code === 'sl') { - return badge(, mainText || '病假', 'bg-pink-100 text-pink-700') - } - if (code === 'cl') { - return badge(, mainText || '補鐘', 'bg-indigo-100 text-indigo-700') - } - if (code === 'mixed_leave') { - return badge(, mainText || '混合假', 'bg-fuchsia-100 text-fuchsia-700') + if (parts.length === 0) { + return badge(, '正常', 'bg-green-100 text-green-700') } - // fallback - return {text || code || '-'} -} \ No newline at end of file + return {parts} +} diff --git a/frontend/src/pages/attendance/List.jsx b/frontend/src/pages/attendance/List.jsx index 48a26e5..ea02339 100644 --- a/frontend/src/pages/attendance/List.jsx +++ b/frontend/src/pages/attendance/List.jsx @@ -87,7 +87,10 @@ export default function AttendanceList() { } const getStatusBadge = (record) => { - return + const lm = record.late_minutes || 0 + const em = record.early_minutes || 0 + const om = record.ot_minutes || 0 + return } const getRowClass = (code) => {