StatusBadge: simplify to 5 basic categories (正常/異常/缺勤/公假/請假)

- StatusBadge no longer shows detailed late/early/OT minute breakdowns
  nor AL/SL/CL sub-types; collapse to single basic-status badge
- List page: replace text-input column filter with status dropdown
  (server-side ?status_text= filter; backend now supports 'leave' IN-clause
  for al/sl/cl/mixed_leave group)
- List page: badge now driven by backend status_code/status_text
  (remove frontend latenessMap which contradicted backend status)
This commit is contained in:
IT狗
2026-07-21 21:33:42 +08:00
parent 8b378ec4c6
commit 4b967f7920
4 changed files with 88 additions and 178 deletions
+28 -46
View File
@@ -1,18 +1,22 @@
import { CheckCircle, AlertCircle, Clock, X, AlertTriangle, Briefcase, Stethoscope, Timer, PartyPopper, CalendarX, Palmtree } from 'lucide-react'
import { CheckCircle, AlertCircle, CalendarX, Palmtree, Briefcase } from 'lucide-react'
/**
* Reusable status badge for attendance records.
* Renders one badge per status dimension (abnormal / late / early / OT / leave).
* 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)
*
* Props:
* - code: status_code string (used for row-coloring and fallback)
* - late: late_minutes number
* - early: early_minutes number
* - ot: ot_minutes number
* - text: status_text string (used for leave / holiday / mixed)
* - code: status_code string
* - text: status_text string (fallback signal)
* - size: 'sm' | 'md' (default 'sm')
*/
export default function StatusBadge({ code, late = 0, early = 0, ot = 0, text = '', size = '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'
@@ -24,53 +28,31 @@ export default function StatusBadge({ code, late = 0, early = 0, ot = 0, text =
</span>
)
// Missing: show grey 缺勤 badge
if (code === 'missing' || text === '缺勤') {
const c = String(code || '').toLowerCase()
const t = String(text || '')
// 缺勤 (missing)
if (c === 'missing' || t === '缺勤') {
return badge(<CalendarX size={12} />, '缺勤', 'bg-gray-200 text-gray-700')
}
// Holiday: show purple 假期 badge
if (code === 'holiday' || text === '🏖️公假') {
// 公假 (holiday)
if (c === 'holiday' || t.includes('公假') || t.includes('🏖️')) {
return badge(<Palmtree size={12} />, '公假', 'bg-indigo-100 text-indigo-700')
}
// Annual leave
if (code === 'al' || text === '年假 AL') {
return badge(<Palmtree size={12} />, '年假 AL', 'bg-pink-100 text-pink-700')
// 請假 (any leave type)
if (['al', 'sl', 'cl', 'mixed_leave'].includes(c)
|| t.includes('年假') || t.includes('病假') || t.includes('補鐘')
|| t.includes('混合假')) {
return badge(<Briefcase size={12} />, '請假', 'bg-violet-100 text-violet-700')
}
// Sick leave
if (code === 'sl' || text === '病假 SL') {
return badge(<Stethoscope size={12} />, '病假 SL', 'bg-cyan-100 text-cyan-700')
}
// Compensation leave
if (code === 'cl' || text === '補鐘 CL') {
return badge(<Timer size={12} />, '補鐘 CL', 'bg-amber-100 text-amber-700')
}
// Mixed leave
if (code === 'mixed_leave' || text === '混合假') {
return badge(<Briefcase size={12} />, '混合假', 'bg-violet-100 text-violet-700')
}
const parts = []
// Abnormal (any dimension > 30 min)
if (late > 30 || early > 30 || ot > 30) {
if (late > 30) parts.push(badge(<AlertTriangle size={12} />, `異常-遲到${late}`, 'bg-red-100 text-red-700'))
else if (early > 30) parts.push(badge(<AlertTriangle size={12} />, `異常-早退${early}`, 'bg-red-100 text-red-700'))
else parts.push(badge(<AlertTriangle size={12} />, `異常-OT${ot}`, 'bg-red-100 text-red-700'))
} else {
// Individual badges (≤ 30 min each)
if (late > 0) parts.push(badge(<AlertCircle size={12} />, `遲到${late}`, 'bg-orange-100 text-orange-700'))
if (early > 0) parts.push(badge(<Clock size={12} />, `早退${early}`, 'bg-blue-100 text-blue-700'))
if (ot > 0) parts.push(badge(<Clock size={12} />, `OT${ot}`, 'bg-purple-100 text-purple-700'))
}
if (parts.length === 0) {
// 正常
if (c === 'normal' && !t) {
return badge(<CheckCircle size={12} />, '正常', 'bg-green-100 text-green-700')
}
return <span className="inline-flex items-center gap-1 flex-wrap">{parts}</span>
// 異常 (late/early/ot/abnormal/late_early/late_ot/early_ot/...)
return badge(<AlertCircle size={12} />, '異常', 'bg-orange-100 text-orange-700')
}