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,52 @@
import { db } from "../db";
import type { Employee } from "../types";
export function findActiveEmployeeByEmail(email: string): Employee | undefined {
return db
.prepare<[string], Employee>(
"SELECT * FROM employees WHERE email = ? AND active = 1"
)
.get(email.toLowerCase());
}
export function findEmployeeById(id: number): Employee | undefined {
return db.prepare<[number], Employee>("SELECT * FROM employees WHERE id = ?").get(id);
}
export function listEmployees(): Employee[] {
return db
.prepare<[], Employee>("SELECT * FROM employees ORDER BY active DESC, email ASC")
.all();
}
/** Fills in the display name the first time we see a Google profile for this employee. */
export function touchEmployeeName(id: number, name: string | null): void {
if (!name) return;
db.prepare("UPDATE employees SET name = ? WHERE id = ? AND name IS NULL").run(name, id);
}
export function addEmployee(email: string, name: string | null): Employee {
const normalized = email.trim().toLowerCase();
const existing = db
.prepare<[string], Employee>("SELECT * FROM employees WHERE email = ?")
.get(normalized);
if (existing) {
db.prepare("UPDATE employees SET active = 1, name = COALESCE(?, name) WHERE id = ?").run(
name,
existing.id
);
return findEmployeeById(existing.id)!;
}
const result = db
.prepare("INSERT INTO employees (email, name) VALUES (?, ?)")
.run(normalized, name);
return findEmployeeById(Number(result.lastInsertRowid))!;
}
/** Soft delete: revokes login access but keeps attendance history for stats. */
export function deactivateEmployee(id: number): boolean {
const result = db.prepare("UPDATE employees SET active = 0 WHERE id = ?").run(id);
return result.changes > 0;
}