// My Attendance — a logged-in employee's own attendance: today's actions // (check in / break / check out) + stat cards + a history table where every // break session is listed on its own line. Rendered as a full page (side-menu // "My Attendance") and, in compact mode, as a Dashboard card. (() => { const { useState, useEffect, useCallback } = React; const Icon = window.Icon || (() => null); function fmtTime(t) { if (!t) return '—'; try { return new Date(String(t).replace(' ', 'T') + (String(t).includes('Z') ? '' : 'Z')).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); } catch { return '—'; } } function fmtDate(d) { if (!d) return '—'; try { return new Date(String(d).slice(0, 10) + 'T00:00:00').toLocaleDateString('en', { day: '2-digit', month: 'short', year: 'numeric' }); } catch { return String(d).slice(0, 10); } } function minsBetween(a, b) { try { return Math.max(0, Math.round((new Date(b) - new Date(a)) / 60000)); } catch { return 0; } } function fmtDur(mins) { mins = Math.round(mins || 0); const h = Math.floor(mins / 60), m = mins % 60; return h ? `${h}h ${m}m` : `${m}m`; } function breaksOf(a) { return Array.isArray(a && a.breaks) ? a.breaks : []; } function StatBox({ label, value, sub, tone }) { return (
{label}
{value}
{sub ?
{sub}
: null}
); } function MyAttendance({ compact }) { const [today, setToday] = useState(null); const [linked, setLinked] = useState(true); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const load = useCallback(async () => { if (!window.API || !window.API.getMyAttendance) { setLinked(false); setLoading(false); return; } try { const t = await window.API.getMyAttendance(); setLinked(t.linked !== false); setToday(t.data || null); } catch {} try { const h = await window.API.getMyAttendanceHistory(); if (h.data) setRows(h.data); } catch {} setLoading(false); }, []); useEffect(() => { load(); }, [load]); async function act(fn) { if (busy || !fn) return; setBusy(true); try { const r = await fn(); if (r.data) { setToday(r.data); load(); } else if (r.message) alert(r.message); } catch { alert('Could not update attendance. Please try again.'); } setBusy(false); } if (loading) return
Loading…
; if (!linked) return (
You are not in the employee directory yet. Ask an admin to add you (with your email) to use attendance.
); const checkedIn = !!(today && today.check_in); const checkedOut = !!(today && today.check_out); const onBreak = checkedIn && !checkedOut && breaksOf(today).some(b => b && b.in && !b.out); // Stats across loaded history (last 30 days by default). let totalBreak = 0, totalWorked = 0, days = 0; rows.forEach(r => { const bm = Number(r.break_minutes) || breaksOf(r).reduce((s, b) => s + (b.in && b.out ? minsBetween(b.in, b.out) : 0), 0); totalBreak += bm; totalWorked += (Number(r.worked_hours) || 0) * 60; if (r.check_in) days++; }); const avgBreak = days ? Math.round(totalBreak / days) : 0; const actionBar = (
{!checkedIn ? ( ) : checkedOut ? ( <> Done for today ) : onBreak ? ( <> ) : ( <> )}
); function btn(bg) { return { height: 38, padding: '0 16px', background: bg, color: '#fff', border: 'none', borderRadius: 'var(--radius-md)', cursor: busy ? 'wait' : 'pointer', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', fontWeight: 'var(--fw-bold)' }; } // Today summary line const todayLine = !checkedIn ? 'Not checked in yet' : `Checked in ${fmtTime(today.check_in)}` + (checkedOut ? ` · Checked out ${fmtTime(today.check_out)}` : onBreak ? ' · On break' : ''); const statCards = (
); const table = (
{['Date', 'Check in', 'Check out', 'Late', 'Early leave', 'Breaks', 'Break total', 'Worked', 'Status'].map((h, i) => ( ))} {rows.length === 0 && } {rows.map(r => { const bks = breaksOf(r); const bm = Number(r.break_minutes) || bks.reduce((s, b) => s + (b.in && b.out ? minsBetween(b.in, b.out) : 0), 0); return ( {/* Own lateness against the assigned shift — visible to the employee too. */} ); })}
= 3 && i <= 4) || (i >= 6 && i <= 7) ? 'right' : 'left', padding: '10px 14px', fontSize: 'var(--text-2xs)', fontWeight: 'var(--fw-bold)', textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-subtle)', whiteSpace: 'nowrap' }}>{h}
No attendance recorded yet.
{fmtDate(r.date)}{r.is_late ? LATE : null} {fmtTime(r.check_in)} {fmtTime(r.check_out)}{r.late_minutes ? fmtDur(r.late_minutes) : (r.check_in ? 'On time' : '—')} {r.early_leave_minutes ? fmtDur(r.early_leave_minutes) : (r.check_out ? 'Full day' : '—')} {bks.length === 0 ? '—' : bks.map((b, i) => (
{fmtTime(b.in)} – {b.out ? fmtTime(b.out) : 'ongoing'}{b.in && b.out ? ` (${fmtDur(minsBetween(b.in, b.out))})` : ''}
))}
{bm ? fmtDur(bm) : '—'} {r.worked_hours ? Number(r.worked_hours) + 'h' + (Number(r.overtime_hours) ? ` (+${Number(r.overtime_hours)} OT)` : '') : '—'} {r.status === 'on_break' ? 'on break' : (r.status || '—')}
); // Compact = dashboard card (today actions + stats + short table) if (compact) { return (
My attendance
{todayLine}
{actionBar}
{statCards} {table}
); } // Full page return (

My attendance

{todayLine}

{actionBar}
{statCards} {table}
); } window.MyAttendance = MyAttendance; })();