Michal Pemcak f917ed06a8 Add shift planning, month closure + payroll, and a dark modern redesign
Ports two modules from the friend's Google Apps Script build (kept as
reference in gscript/) onto the TypeScript stack, rewritten cleanly against
this app's own data model rather than copied 1:1:

- Shift planning: weekly template (Sun-Thu evening, Fri/Sat two slots),
  lazy idempotent generation per period (no cron needed), employee
  signup/cancel with collision + capacity checks, admin calendar view with
  slot editing and manual assignment.
- Month closure + payroll: employee confirms the month (blocked while a
  shift is still open), admin locks and finalizes pay (base hours * rate +
  tips/bonus/other), reopen to undo a premature lock, mark paid. Pay rates
  are versioned by date, defaulting the first-ever rate to apply
  retroactively to the employee's whole history.
- A shift left open more than 12h (forgotten clock-out) is auto-closed at
  clock_in + 12h, checked lazily on read instead of a background job.
- Full dark, sharp-edged modern restyle (theme.css replaces tui.css) with
  an amber accent, keeping every existing class name so no component
  logic needed to change.

Backend test coverage (jest) for all three workflows: shift planning,
closure/payroll, and the forgotten-clock-out auto-close.
2026-08-16 18:41:11 +02:00

EatMe — Docházka

Evidence docházky pro bistro EatMe. Zaměstnanci se přihlašují Google účtem a logují příchod / odchod / start a konec pauzy; admin (majitel) spravuje seznam zaměstnanců (jen e-mail, žádná hesla) a vidí souhrnné i individuální statistiky s grafem a exportem do CSV.

backend/       Express + TypeScript API, SQLite (better-sqlite3)
frontend/      React + TypeScript PWA (Vite), Google Sign-In, Zustand
docs/          OKF (Open Knowledge Format) — architektura, datový model, provoz
branding/      Logo a vygenerované favicony/PWA ikony z podkladu bistra
deploy/        Šablony configů pro hosty mimo tento repo (nginx server block)
Dockerfile     Multi-stage build — jeden image pro API i frontend
compose.yaml   Produkční nasazení hotového image (viz Produkce níž)

Vzhled je podle ~/doc/concepts/ui/light-mono-tui.md — světlý monochromní "TUI" styl (mono font, ostré hrany, žádné barvy, inverze pro aktivní stav).

Požadavky

  • Node.js 22+
  • Google Cloud projekt s nakonfigurovaným OAuth Client ID (Web application) — origins musí obsahovat http://localhost:5174 pro vývoj a produkční doménu. Client ID se nastavuje do GOOGLE_CLIENT_ID (backend) a VITE_GOOGLE_CLIENT_ID (frontend) — musí být stejné.

Vývoj

# backend
cd backend
cp .env.example .env   # doplň GOOGLE_CLIENT_ID, ADMIN_EMAILS, JWT_SECRET
npm install
npm run dev             # http://localhost:4000

# frontend (v druhém terminálu)
cd frontend
cp .env.example .env    # VITE_GOOGLE_CLIENT_ID stejné jako backend
npm install
npm run dev              # http://localhost:5174 (port je pinnutý — musí sedět s Google origin)

Frontend v dev módu proxuje /api/* na backend (viz frontend/vite.config.ts).

Typecheck / build

cd backend  && npm run typecheck && npm run build   # -> dist/index.js (esbuild bundle)
cd frontend && npm run build                          # -> dist/ (Vite, PWA)

Produkce (Docker)

Jeden multi-stage Dockerfile v rootu postaví frontend i backend a spustí je jako jeden kontejner — backend servíruje API na /api/* a staticky sbalený frontend na všem ostatním.

Image se buildí lokálně (pro platformu cílového serveru) a na server se posílá hotový, ne zdrojáky:

# buildni pro platformu serveru (na Apple Silicon Macu proti x86_64 serveru
# je --platform linux/amd64 povinné, jinak image tam vůbec nenaběhne)
docker build --platform linux/amd64 \
  --build-arg VITE_GOOGLE_CLIENT_ID=<client-id> \
  -t eatme:latest .

# přenes hotový image přes ssh (žádný build na serveru)
docker save eatme:latest | gzip | ssh <host> "gunzip | docker load"

# pošli jen compose.yaml + .env (viz .env.example) a nastartuj
scp compose.yaml <host>:~/eatme/compose.yaml
ssh <host> "cd ~/eatme && docker compose up -d"

VITE_GOOGLE_CLIENT_ID musí jít jako build arg (Vite ho zapéká při buildu, ne za běhu — v .env na serveru se proto řeší jen runtime proměnné). Pojmenovaný volume eatme_data (viz compose.yaml) drží SQLite databázi mimo kontejner, ať přežije redeploy.

Před kontejner patří TLS-terminující reverse proxy (mimo tento repo) — šablona server blocku pro nginx je v deploy/eatme.mipem.co, proxuje na http://<deploy-host>:8092.

Datový model / provoz

Zaměstnanci jsou whitelist e-mailů spravovaný adminem (ADMIN_EMAILS v env — samostatná role, není v tabulce zaměstnanců). Odebrání zaměstnance je pouze soft-delete (active = 0) — historie docházky zůstává, opětovným přidáním stejného e-mailu se účet reaktivuje. Statistiky se počítají po kalendářních měsících (?month=YYYY-MM), s možností listovat měsíci v adminově detailu zaměstnance (graf + export CSV).

Víc v docs/ (OKF bundle).

Description
EatMe bistro attendance, shift planning, and payroll system
Readme 3.3 MiB
Languages
HTML 49.8%
JavaScript 34%
TypeScript 14.8%
CSS 1.3%
Dockerfile 0.1%