Initial commit: EatMe attendance tracker

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>
This commit is contained in:
Michal Pemcak
2026-08-12 11:57:55 +02:00
commit fffcb73ea4
90 changed files with 3411 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
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">&gt; 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 &middot; 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>
</>
);
}