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>
40 lines
1.2 KiB
TypeScript
40 lines
1.2 KiB
TypeScript
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,
|
|
};
|
|
}
|