Google-SSO PWA for bistro employee clock-in/out, admin employee management, and stats with CSV export. Express + SQLite backend, React + Zustand frontend in the light-mono-tui design language. Multi-stage Dockerfile, compose.yaml for image-based deploys, nginx reverse-proxy template, and an OKF documentation bundle in docs/. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
import { useEffect } from 'react';
|
|
import useAttendanceStore from '../store/attendanceStore';
|
|
import { ClockControls } from '../components/ClockControls';
|
|
import { StatusBadge } from '../components/StatusBadge';
|
|
import { StatsSummary } from '../components/StatsSummary';
|
|
import { SessionList } from '../components/SessionList';
|
|
|
|
export function EmployeeApp() {
|
|
const status = useAttendanceStore((s) => s.status);
|
|
const busy = useAttendanceStore((s) => s.busy);
|
|
const error = useAttendanceStore((s) => s.error);
|
|
const live = useAttendanceStore((s) => s.live);
|
|
const load = useAttendanceStore((s) => s.load);
|
|
const recordEvent = useAttendanceStore((s) => s.recordEvent);
|
|
const startPolling = useAttendanceStore((s) => s.startPolling);
|
|
const stopPolling = useAttendanceStore((s) => s.stopPolling);
|
|
|
|
useEffect(() => {
|
|
load();
|
|
}, [load]);
|
|
|
|
useEffect(() => {
|
|
if (status === null || status === 'clocked_out') {
|
|
stopPolling();
|
|
return;
|
|
}
|
|
startPolling();
|
|
return () => stopPolling();
|
|
}, [status, startPolling, stopPolling]);
|
|
|
|
if (!status || !live) {
|
|
return <div className="empty-line">> načítám…</div>;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="panel">
|
|
<p className="panel-title">Docházka</p>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '1rem' }}>
|
|
<StatusBadge status={status} />
|
|
</div>
|
|
<ClockControls status={status} busy={busy} onAction={recordEvent} />
|
|
{error && <div className="login-error">{error}</div>}
|
|
</div>
|
|
|
|
<div className="panel">
|
|
<p className="panel-title">Statistiky · tento měsíc</p>
|
|
<StatsSummary
|
|
totalWorkedMs={live.totalWorkedMs}
|
|
totalBreakMs={live.totalBreakMs}
|
|
shiftCount={live.shiftCount}
|
|
/>
|
|
</div>
|
|
|
|
<div className="panel">
|
|
<p className="panel-title">Směny</p>
|
|
<SessionList sessions={live.sessions} />
|
|
</div>
|
|
</>
|
|
);
|
|
}
|