Files
aars/frontend/src/components/StatusBadge.jsx
T
IT狗 404329d3fa Attendance: detect rest day in enrich → render 休息 instead of 缺勤
When roster says the shift rests on a given weekday (e.g. Sunday for S2/S3),
the employee wasn't scheduled to work. Import still marks the record
status_code='missing' because no shift times were available, but the
right user-facing label is '休息' not '缺勤'.

Enrich now: if status_code='missing' and get_shift_schedule_full returns
None (shift defined no schedule for that weekday), override to
status_code='rest' / status_text='休息'.

Added '休息' to the basic-status vocabulary: dropdown option, filter
condition, StatusBadge (slate grey + coffee icon), and row highlight.
2026-07-21 23:54:57 +08:00

102 lines
3.6 KiB
React

import { CheckCircle, AlertCircle, CalendarX, Palmtree, Briefcase, Coffee } from 'lucide-react'
/**
* Reusable status badge for attendance records.
* Simplified to BASIC status categories — no detailed breakdowns.
*
* Status mapping (driven by backend status_code / status_text):
* - missing → 缺勤 (grey)
* - holiday → 公假 (indigo)
* - al / sl / cl / mixed_leave → 請假 (violet)
* - normal → 正常 (green)
* - everything else (late/early/ot/abnormal/late_early/...) → 異常 (orange)
*
* If status_text carries a leave/holiday suffix (e.g. '+ SL2h', '🎉香港…'),
* a small secondary chip is rendered next to the main badge so the list page
* shows the underlying leave/holiday detail without expanding the row.
*
* Props:
* - code: status_code string
* - text: status_text string (fallback signal + suffix source)
* - size: 'sm' | 'md' (default 'sm')
*/
export default function StatusBadge({ code = '', text = '', size = 'sm' }) {
const sizeCls = size === 'md'
? 'px-3 py-1 text-sm'
: 'px-2 py-0.5 text-xs'
const badge = (icon, label, colorCls) => (
<span className={`inline-flex items-center gap-1 ${sizeCls} rounded-full font-medium ${colorCls}`}>
{icon}
{label}
</span>
)
// Extract leave suffix from status_text (holiday suffix dropped — the main
// badge already says 公假, so repeating the long name like
// '香港特別行政區成立紀念日' clutters the row).
// '異常-遲到48分 + SL2h' → ['SL2h']
// 'OT3分 + AL4h + SL3h' → ['AL4h', 'SL3h']
// '正常 + CL4h' → ['CL4h']
const t = String(text || '')
const noteParts = []
const split = t.split(' + ').map(s => s.trim())
if (split.length > 1) {
for (let i = 1; i < split.length; i++) {
noteParts.push(split[i].replace(/^\(|\)$/g, '').trim())
}
}
const noteChips = noteParts.filter(Boolean).map((part, idx) => {
const cls = 'bg-violet-50 text-violet-600 border border-violet-200'
return (
<span
key={idx}
className={`inline-flex items-center ${sizeCls} rounded-full font-normal ${cls}`}
title={part}
>
+{part}
</span>
)
})
const wrap = (content) => (
<span className="inline-flex items-center gap-1 flex-wrap">
{content}
{noteChips}
</span>
)
const c = String(code || '').toLowerCase()
// 缺勤 (missing)
if (c === 'missing' || t === '缺勤') {
return wrap(badge(<CalendarX size={12} />, '缺勤', 'bg-gray-200 text-gray-700'))
}
// 休息 (rest day — roster says not scheduled, employee didn't clock in)
if (c === 'rest' || t === '休息') {
return wrap(badge(<Coffee size={12} />, '休息', 'bg-slate-100 text-slate-600'))
}
// 公假 (holiday)
if (c === 'holiday' || t.includes('公假') || t.includes('🏖️')) {
return wrap(badge(<Palmtree size={12} />, '公假', 'bg-indigo-100 text-indigo-700'))
}
// 請假 (any leave type)
if (['al', 'sl', 'cl', 'mixed_leave'].includes(c)
|| t.includes('年假') || t.includes('病假') || t.includes('補鐘')
|| t.includes('混合假')) {
return wrap(badge(<Briefcase size={12} />, '請假', 'bg-violet-100 text-violet-700'))
}
// 正常 (only when status_code is 'normal' — text suffix like '+ CL4h' is allowed,
// we still want to flag it as 正常 first since the employee did clock in/out normally)
if (c === 'normal') {
return wrap(badge(<CheckCircle size={12} />, '正常', 'bg-green-100 text-green-700'))
}
// 異常 (late/early/ot/abnormal/late_early/late_ot/early_ot/...)
return wrap(badge(<AlertCircle size={12} />, '異常', 'bg-orange-100 text-orange-700'))
}