// web-employee.jsx — Karyawan: absen (GPS asli), riwayat, akun const { useState: useSE, useEffect: useEE } = React; function greetWeb(h) { return h < 11 ? 'Selamat pagi' : h < 15 ? 'Selamat siang' : h < 18 ? 'Selamat sore' : 'Selamat malam'; } // live GPS strip function GpsLive({ gps, busy, onRetry }) { if (busy) return (
Mendeteksi lokasi…
Mohon izinkan akses lokasi
); if (!gps || !gps.ok) return (
Lokasi belum aktif
{gps ? gps.err : 'GPS dimatikan'}
); const lab = MA.locLabel(gps.lat, gps.lng); return (
{lab.name}
{gps.lat}, {gps.lng} · ±{gps.acc}m
{lab.inArea ? 'Di dealer' : 'Di luar'}
); } function WebBeranda({ user, now, gps, locBusy, retryLoc, phase, masuk, pulang, onAct, acting, burst }) { const done = phase === 'pulang'; const isMasuk = phase === 'belum'; return (
{greetWeb(now.getHours())},
{user.name.split(' ')[0]} · {user.jabatan}
Waktu Sekarang
{MA.hhmmss(now)}
{MA.dateLong(now)}
{[['Masuk', masuk || '—', masuk ? 'var(--success)' : null], ['Pulang', pulang || '—', pulang ? 'var(--accent)' : null], ['Status', phase === 'belum' ? '—' : (masuk && masuk > MA.DEALER.lateAfter ? 'Telat' : 'Hadir'), null]].map(([l, v, col], i) => ( {i > 0 &&
}
{v}
{l}
))}
); } function WebRiwayat({ user, history }) { return (
} />
Tingkat Kehadiran
Bulan {MA.monthLabel()}
{[[`${user.hadir}/${user.total}`, 'Hari hadir', 'var(--text)'], [user.late, 'Telat', 'var(--warn)'], [user.ot + 'j', 'Lembur', 'var(--accent)']].map(([v, l, c]) => (
{v}
{l}
))}
Detail Harian
{history === null &&
Memuat…
} {(history || []).map((h, i) => (
{h.d}
{h.date.split(',')[0]}
{h.in !== '—' ? `${h.in} – ${h.out}` : h.loc}
{h.hours}
))}
); } function WebAkun({ user, onLogout, theme, onToggleTheme }) { const item = (icon, label, right, danger, onClick) => (
{label}
{right || }
); return (
{user.name}
{user.jabatan} · {user.id}
{MA.DEALER.branch}
{[['Kehadiran', user.pct + '%'], ['Jam Kerja', user.hours + 'j'], ['Lembur', user.ot + 'j']].map(([l, v]) => (
{v}
{l}
))}
{item('user', 'Data Pribadi')} {item('settings', 'Mode Gelap', ( ), false, () => {})}
{item('logout', 'Keluar', null, true, onLogout)}
); } function WebEmployee({ user, now, onLogout, theme, onToggleTheme }) { const [tab, setTab] = useSE('beranda'); const [phase, setPhase] = useSE('belum'); const [masuk, setMasuk] = useSE(null); const [pulang, setPulang] = useSE(null); const [gps, setGps] = useSE(null); const [locBusy, setLocBusy] = useSE(true); const [acting, setActing] = useSE(false); const [burst, setBurst] = useSE(false); const [toast, setToast] = useSE(null); const [history, setHistory] = useSE(null); const loadLoc = () => { setLocBusy(true); MA.getLocation().then(g => { // di mode demo, jika GPS diblokir (mis. di pratinjau), pakai lokasi dealer sbg contoh if ((!g || !g.ok) && MA.DEMO) g = { ok: true, lat: MA.DEALER.lat + 0.0004, lng: MA.DEALER.lng - 0.0003, acc: 12 }; setGps(g); setLocBusy(false); }); }; useEE(() => { loadLoc(); MA.myStatus(user.id).then(r => { if (r.ok) { setPhase(r.phase); setMasuk(r.masuk || null); setPulang(r.pulang || null); } }); MA.myHistory(user.id).then(r => setHistory(r.ok ? r.history : [])); }, []); const flash = (m) => { setToast(m); setTimeout(() => setToast(null), 2800); }; const act = async () => { let g = gps; if (!g || !g.ok) { setLocBusy(true); g = await MA.getLocation(); if ((!g || !g.ok) && MA.DEMO) g = { ok: true, lat: MA.DEALER.lat, lng: MA.DEALER.lng, acc: 15 }; setGps(g); setLocBusy(false); } setActing(true); if (phase === 'belum') { const r = await MA.clockIn(user.id, g); setActing(false); if (r.ok) { setPhase('masuk'); setMasuk(r.time); setBurst(true); setTimeout(() => setBurst(false), 600); flash(`Absen masuk tercatat · ${r.time}`); } else flash(r.err || 'Gagal absen'); } else if (phase === 'masuk') { const r = await MA.clockOut(user.id, g); setActing(false); if (r.ok) { setPhase('pulang'); setPulang(r.time); flash(`Absen pulang tercatat · ${r.time}`); } else flash(r.err || 'Gagal absen'); } }; const tabs = [{ id: 'beranda', icon: 'home', label: 'Absen' }, { id: 'riwayat', icon: 'history', label: 'Riwayat' }, { id: 'akun', icon: 'user', label: 'Akun' }]; return ( <>
{tab === 'beranda' && } {tab === 'riwayat' && } {tab === 'akun' && }
{toast && {toast}} ); } const stripWrap = { display: 'flex', alignItems: 'center', gap: 11, padding: '11px 14px', background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 14 }; const stripIcon = { width: 34, height: 34, borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }; const stripTitle = { fontSize: 13.5, fontWeight: 700, color: 'var(--text)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }; const stripSub = { fontSize: 11.5, color: 'var(--text-dim)', fontWeight: 500 }; window.WebEmployee = WebEmployee;