Compare commits

...

3 Commits

Author SHA1 Message Date
Felix Schlusche
0b408c93ee feat: enhance README with updates on Node.js version, responsive timer layout, and new features including live timer metrics and intelligent PDF export activation
All checks were successful
Build and Push Docker Image / build (push) Successful in 26s
2025-10-31 18:55:46 +01:00
Felix Schlusche
0045a8f8d0 feat: update PDF export functionality to prevent incomplete month exports 2025-10-31 18:54:47 +01:00
Felix Schlusche
d04ab18ba1 feat: enhance timer metrics and workday calculations to include entries and running timer status 2025-10-31 18:54:17 +01:00
2 changed files with 69 additions and 18 deletions

View File

@@ -25,6 +25,14 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
- Timer persistiert über Seiten-Reloads
- Manuelle Startzeit-Eingabe möglich
- Visueller Indikator (blinkendes Uhr-Icon) bei laufendem Timer
- **Timer-Metriken** bei laufendem Timer:
- Läuft seit: Startzeit mit Icon-Styling
- Soll erreicht: Uhrzeit wann Tagesziel erreicht wird (inkl. Pausen)
- Zeit bis Soll: Live-Countdown zur Zielzeit
- Saldo bei Soll: Prognostizierter Gesamtsaldo nach Erreichen der geplanten Zeit
- **Anpassbare Arbeitszeit**: Dropdown 4h-10h für flexible Arbeitstage
- Einstellung bleibt über Reloads erhalten (nur während Timer läuft)
- Wird bei Timer-Stop auf 8h zurückgesetzt
- **Flexible Eingabemodi**:
- Manuelle Eingabe (Datum, Start, Ende, Pause)
- Inline-Bearbeitung direkt in der Tabelle
@@ -38,10 +46,16 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
- **Automatische Pausenberechnung** (deutsches Arbeitszeitgesetz)
- **10-Stunden-Cap** für maximale Nettoarbeitszeit pro Tag
- **Live-Statistiken** mit laufendem Timer:
- Soll-Stunden (basierend auf Arbeitstagen)
- Soll-Stunden (basierend auf Arbeitstagen mit Einträgen)
- Ist-Stunden (inkl. aktuell laufender Timer)
- Monatssaldo + Gesamtsaldo mit Vormonatsübertrag
- Arbeitstage-Zählung
- **Intelligente Soll-Berechnung**: Berücksichtigt nur Tage mit Einträgen oder laufendem Timer
- **Laufendes Saldo** in Monatsansicht:
- Spalte "Saldo" zeigt kumulatives Saldo bis zu jedem Tag
- Live-Updates während Timer läuft
- Farbcodierung: Grün (positiv) / Rot (negativ)
- Berücksichtigt Flextime-Tage korrekt (-8h)
- **Urlaubsverwaltung**:
- Konfigurierbares Jahres-Kontingent
- Tracking: Genommen, Geplant, Verfügbar
@@ -50,6 +64,10 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
### 🗓️ Bundesland-spezifische Feiertage
- **16 Bundesländer** mit korrekten regionalen Feiertagen
- **Persistente Einstellung** (gespeichert in Datenbank)
- **Betriebsfreie Tage**: Wählbar zwischen Heiligabend (24.12.) oder Silvester (31.12.)
- Toggle in Einstellungen
- Wird als "Betriebsfrei" markiert
- Verhindert doppelte Einträge an diesen Tagen
- **Kollisionserkennung**: Warnung bei Feiertagen mit bestehenden Einträgen
- **Alle Feiertage**: Bundeseinheitlich + regional (z.B. Fronleichnam, Reformationstag)
@@ -62,6 +80,7 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
- 🔴 Rot: Fehlende Arbeitstage
- ⚫ Grau: Wochenenden
- 🔵 Blau: Feiertage (mit Namen)
- 💙 Blauer Rand: Heutiger Tag
- **Navigation**: Vor/Zurück-Buttons zum Monatswechsel
- **Auto-Fill**: Automatisches Befüllen des Monats mit Standard-Arbeitszeiten (9:00-17:30)
- **Quick-Actions**: Plus-Buttons für schnelles Hinzufügen von Einträgen
@@ -83,7 +102,9 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
- **CSV-Export (Abweichungen)**: Nur Tage ≠ 8,0h
- Ideal für Gleitzeit-Nachweise
- **PDF-Export**:
- **Monats-Export**: Exportiert aktuellen Monat als formatierten PDF
- **Monats-Export**: Nur verfügbar wenn letzter Tag des Monats vollständig erfasst ist
- Verhindert versehentliche Exports unvollständiger Monate
- Button wird automatisch angezeigt sobald Bedingung erfüllt
- **Bulk-Export**: Exportiert ausgewählte Einträge (im Bulk-Modus)
- Professionelles Layout mit Mitarbeiter-Info und Statistiken
- Automatische Tabelle mit allen Einträgen
@@ -105,6 +126,8 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
### 🎨 Modernes UI/UX
- **Premium Design**: Glass-Morphism, Gradients, Schatten, Animationen
- **Responsive**: Desktop, Tablet, Mobile
- Timer-Metriken: Rechts neben Timer auf großen Displays, darunter auf mobil
- Adaptive Layouts für alle Bildschirmgrößen
- **Dark Mode**: Augenschonendes dunkles Design
- **Toast-Benachrichtigungen**: Visuelles Feedback
- **Icons**: Lucide Icons für klare Symbolik
@@ -113,7 +136,7 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
## 🏗️ Technologie-Stack
**Backend:**
- Node.js 18+ mit Express.js
- Node.js 20+ mit Express.js
- SQLite (better-sqlite3) für dateibasierte Persistenz
- Modulare Architektur (config, utils, routes)
@@ -127,6 +150,8 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
**Infrastructure:**
- Docker & Docker Compose
- Multi-Stage Build für optimierte Images
- Node.js 20 Alpine Linux base image
- Python build dependencies für native Module
- Gitea Actions CI/CD für automatische Builds
- Gitea Container Registry für Image-Hosting
@@ -142,15 +167,16 @@ timetracker/
│ ├── index.html # Single-Page Application
│ ├── favicon.svg # App Icon
│ └── js/
│ ├── state.js # Globaler State
│ ├── state.js # Globaler State (companyHolidayPreference, targetHours)
│ ├── utils.js # Hilfsfunktionen
│ ├── holidays.js # Feiertagsberechnung
│ ├── holidays.js # Feiertagsberechnung (16 Bundesländer)
│ ├── bridge-days.js # Brückentag-Optimierung
│ ├── api.js # Backend-Kommunikation
│ └── main.js # Hauptlogik (~3700 Zeilen)
│ └── main.js # Hauptlogik (~4000 Zeilen)
├── .gitea/workflows/ # CI/CD Workflows
│ └── docker-build.yml # Docker Build & Push
├── media/screenshots/ # App-Screenshots
├── Dockerfile # Container-Image
├── Dockerfile # Container-Image (Node 20 + Python)
├── docker-compose.yml # Orchestrierung
└── package.json
```
@@ -227,7 +253,7 @@ docker run -p 3000:3000 -v $(pwd)/db:/app/db zeiterfassung
### 💻 Option 3: Lokal (ohne Docker)
**Voraussetzungen:** Node.js 18+
**Voraussetzungen:** Node.js 20+
```bash
# Repository klonen
@@ -247,6 +273,9 @@ Die App bietet mehrere Export-Modi für verschiedene Anwendungsfälle:
### PDF-Export 📄
**Monats-Export:**
- Button erscheint nur wenn letzter Tag des Monats vollständig erfasst ist
- Startzeit und Endzeit müssen vorhanden sein
- Verhindert versehentliche Exports unvollständiger Monate
- Klicke auf "PDF Export" in der Monatsansicht
- Exportiert alle Einträge des aktuellen Monats
- Professionelles Layout mit:
@@ -362,13 +391,14 @@ Die App verwendet Gitea Actions für automatische Builds und Deployments:
**Architektur:** Single-Page Application (SPA) mit REST-API Backend
**Tech-Details:**
- Modulare Frontend-Architektur (5 separate JS-Dateien)
- Modulare Frontend-Architektur (6 separate JS-Dateien)
- Flatpickr für Touch-optimierte Picker (auch in Tabellen-Inline-Edit)
- Lucide Icons für Symbolik
- jsPDF + autoTable für PDF-Generierung
- SQLite für dateibasierte Persistenz
- Server-seitige Berechnungen für Datenintegrität
- Responsive Design (Tailwind CSS via CDN)
- Live-Updates während Timer läuft (Saldo, Nettostunden)
**Datenpersistenz:**
- SQLite-Datenbank: `db/timetracker.db`
@@ -377,11 +407,20 @@ Die App verwendet Gitea Actions für automatische Builds und Deployments:
- JSON-basierte Backups für Migration
**Code-Organisation:**
- `state.js`: Globaler Application State
- `state.js`: Globaler Application State (companyHolidayPreference, targetHours)
- `utils.js`: Hilfsfunktionen (Datum, Zeit, Format)
- `holidays.js`: Feiertagsberechnung (16 Bundesländer)
- `holidays.js`: Feiertagsberechnung (16 Bundesländer + Betriebsfrei)
- `bridge-days.js`: Brückentag-Optimierung
- `api.js`: Backend-Kommunikation
- `main.js`: Hauptlogik, UI, Event-Handler
- `main.js`: Hauptlogik, UI, Event-Handler (~4000 Zeilen)
**Neue Features:**
- Timer-Metriken mit Live-Berechnung
- Anpassbare tägliche Arbeitszeit (4h-10h)
- Laufendes Saldo in Tabelle
- Intelligente PDF-Export-Aktivierung
- Betriebsfreier Tag (Weihnachten/Silvester)
- Responsive Timer-Layout
## 📄 Lizenz

View File

@@ -1162,6 +1162,15 @@ async function updateStatistics(entries) {
let futureFlextimeDays = 0; // Count future flextime days in current month
// Create a map to check if a day has an entry
const entriesMap = {};
entries.forEach(entry => {
entriesMap[entry.date] = entry;
});
const todayISO = getTodayISO();
const isCurrentMonth = currentYear === today.getFullYear() && currentMonth === today.getMonth();
for (let day = 1; day <= lastDay; day++) {
const dateObj = new Date(currentYear, currentMonth, day);
const year = dateObj.getFullYear();
@@ -1172,31 +1181,35 @@ async function updateStatistics(entries) {
const isVacation = vacationDays.has(dateISO);
const isFlextime = flextimeDays.has(dateISO);
const isWeekendHoliday = isWeekendOrHoliday(dateObj);
const hasEntry = entriesMap[dateISO];
const isToday = dateISO === todayISO;
// For today: only count as workday if there's an entry OR timer is running
const shouldCountToday = !isToday || hasEntry || (timerStartTime && isCurrentMonth);
if (!isWeekendHoliday && !isVacation && !isFlextime) {
// Normal workday (excluding vacation and flextime days)
totalWorkdaysInMonth++;
workdaysCount++;
if (dateObj <= today) {
if (dateObj <= today && shouldCountToday) {
workdaysPassed++;
}
} else if (!isWeekendHoliday && !isVacation && isFlextime) {
// Flextime on a workday - still counts as workday in calendar
totalWorkdaysInMonth++;
workdaysCount++;
if (dateObj <= today) {
if (dateObj <= today && shouldCountToday) {
workdaysPassed++;
} else {
// Future flextime in current month
const isCurrentMonth = currentYear === today.getFullYear() && currentMonth === today.getMonth();
if (isCurrentMonth) {
if (isCurrentMonth && dateObj > today) {
futureFlextimeDays++;
}
}
} else if (isFlextime && isWeekendHoliday) {
// Flextime on weekend/holiday counts as additional workday
totalWorkdaysInMonth++;
if (new Date(dateISO) <= today) {
if (new Date(dateISO) <= today && shouldCountToday) {
workdaysPassed++;
}
}
@@ -1212,7 +1225,6 @@ async function updateStatistics(entries) {
.reduce((sum, entry) => sum + entry.netHours, 0);
// Add currently running timer hours to actual hours (only for current month)
const isCurrentMonth = currentYear === today.getFullYear() && currentMonth === today.getMonth();
if (timerStartTime && isCurrentMonth) {
const now = Date.now();
let elapsedSeconds;