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,39 @@
import type { Session, StatsSummary } from '../api/types';
/**
* The API computes worked/break durations at fetch time, so an open (still
* running) session freezes until the next request. This recomputes an open
* session's durations against a live clock so the UI can tick without polling.
*/
function recomputeOpenSession(session: Session, now: Date): Session {
if (!session.open) return session;
const start = new Date(session.clockIn).getTime();
const breakMs = session.breaks.reduce((sum, b) => {
const end = b.end ? new Date(b.end).getTime() : now.getTime();
return sum + Math.max(0, end - new Date(b.start).getTime());
}, 0);
const workedMs = Math.max(0, now.getTime() - start - breakMs);
return { ...session, breakMs, workedMs };
}
export function withLiveTime(stats: StatsSummary, now: Date): StatsSummary {
let deltaWorked = 0;
let deltaBreak = 0;
const sessions = stats.sessions.map((s) => {
if (!s.open) return s;
const live = recomputeOpenSession(s, now);
deltaWorked += live.workedMs - s.workedMs;
deltaBreak += live.breakMs - s.breakMs;
return live;
});
return {
...stats,
sessions,
totalWorkedMs: stats.totalWorkedMs + deltaWorked,
totalBreakMs: stats.totalBreakMs + deltaBreak,
};
}