404329d3fa
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.
102 lines
3.6 KiB
React
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'))
|
|
} |