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) => {