Initiale Arbeitszeiterfassung (Next.js 16 + SQLite)

Web-App zur Erfassung der Arbeitszeit, Layout/Architektur an Projekt logbuch
angelehnt, mit grüner Grundfarbe und SQLite3 statt MySQL.

- Eingabemaske: Datum (mit Wochentag), Ort (Kunde FFM/Homeoffice/Andrena),
  Beginn/Ende, Reisezeit, Vertriebsunterstützung (default 2h), Kommentar (max 500)
- Berechnung: Arbeitszeit = Ende-Beginn; Mehrarbeit = Arbeitszeit - 8h - Pause
- Pause als globale Einstellung (Einstellungen-Tab)
- Tabs Eingabe/Liste/Einstellungen, letzte 10 Einträge unter der Maske
- Single-User-Login (Benutzer Birgit + Passwort fest in .env), JWT-Cookie via jose
- better-sqlite3, Docker-Setup für docker.citysensor.de

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 09:53:44 +02:00
commit 01a7ac8e0b
38 changed files with 9062 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
// Reine Hilfsfunktionen für Datum-/Zeit-Berechnungen (ohne DB-Zugriff).
const WOCHENTAGE = [
'Sonntag',
'Montag',
'Dienstag',
'Mittwoch',
'Donnerstag',
'Freitag',
'Samstag',
];
/** "HH:MM" → Minuten seit Mitternacht, oder null bei ungültiger Eingabe. */
export function parseHM(hm: string): number | null {
const m = /^(\d{1,2}):(\d{2})$/.exec(hm ?? '');
if (!m) return null;
const h = Number(m[1]);
const min = Number(m[2]);
if (h < 0 || h > 23 || min < 0 || min > 59) return null;
return h * 60 + min;
}
/**
* Differenz zwischen Beginn und Ende in dezimalen Stunden.
* Gibt 0 zurück, wenn eine Zeit ungültig ist. Ende vor Beginn ⇒ 0.
*/
export function toDecimalHours(beginn: string, ende: string): number {
const b = parseHM(beginn);
const e = parseHM(ende);
if (b === null || e === null) return 0;
const diff = e - b;
if (diff <= 0) return 0;
return diff / 60;
}
/** Mehrarbeit = Arbeitszeit 8 Stunden Pause (kann negativ sein). */
export function mehrarbeit(arbeitszeitStd: number, pauseStd: number): number {
return arbeitszeitStd - 8 - pauseStd;
}
/** Deutscher Wochentagsname für ein "YYYY-MM-DD"-Datum, oder "" bei ungültig. */
export function weekdayName(datum: string): string {
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(datum ?? '');
if (!m) return '';
const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
if (isNaN(d.getTime())) return '';
return WOCHENTAGE[d.getDay()];
}
/** Dezimalstunden deutsch formatieren, z.B. 0.5 → "0,5". */
export function formatHours(value: number): string {
return value.toLocaleString('de-DE', {
minimumFractionDigits: 1,
maximumFractionDigits: 2,
});
}