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 - Timer persistiert über Seiten-Reloads
- Manuelle Startzeit-Eingabe möglich - Manuelle Startzeit-Eingabe möglich
- Visueller Indikator (blinkendes Uhr-Icon) bei laufendem Timer - 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**: - **Flexible Eingabemodi**:
- Manuelle Eingabe (Datum, Start, Ende, Pause) - Manuelle Eingabe (Datum, Start, Ende, Pause)
- Inline-Bearbeitung direkt in der Tabelle - Inline-Bearbeitung direkt in der Tabelle
@@ -38,10 +46,16 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
- **Automatische Pausenberechnung** (deutsches Arbeitszeitgesetz) - **Automatische Pausenberechnung** (deutsches Arbeitszeitgesetz)
- **10-Stunden-Cap** für maximale Nettoarbeitszeit pro Tag - **10-Stunden-Cap** für maximale Nettoarbeitszeit pro Tag
- **Live-Statistiken** mit laufendem Timer: - **Live-Statistiken** mit laufendem Timer:
- Soll-Stunden (basierend auf Arbeitstagen) - Soll-Stunden (basierend auf Arbeitstagen mit Einträgen)
- Ist-Stunden (inkl. aktuell laufender Timer) - Ist-Stunden (inkl. aktuell laufender Timer)
- Monatssaldo + Gesamtsaldo mit Vormonatsübertrag - Monatssaldo + Gesamtsaldo mit Vormonatsübertrag
- Arbeitstage-Zählung - 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**: - **Urlaubsverwaltung**:
- Konfigurierbares Jahres-Kontingent - Konfigurierbares Jahres-Kontingent
- Tracking: Genommen, Geplant, Verfügbar - Tracking: Genommen, Geplant, Verfügbar
@@ -50,6 +64,10 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
### 🗓️ Bundesland-spezifische Feiertage ### 🗓️ Bundesland-spezifische Feiertage
- **16 Bundesländer** mit korrekten regionalen Feiertagen - **16 Bundesländer** mit korrekten regionalen Feiertagen
- **Persistente Einstellung** (gespeichert in Datenbank) - **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 - **Kollisionserkennung**: Warnung bei Feiertagen mit bestehenden Einträgen
- **Alle Feiertage**: Bundeseinheitlich + regional (z.B. Fronleichnam, Reformationstag) - **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 - 🔴 Rot: Fehlende Arbeitstage
- ⚫ Grau: Wochenenden - ⚫ Grau: Wochenenden
- 🔵 Blau: Feiertage (mit Namen) - 🔵 Blau: Feiertage (mit Namen)
- 💙 Blauer Rand: Heutiger Tag
- **Navigation**: Vor/Zurück-Buttons zum Monatswechsel - **Navigation**: Vor/Zurück-Buttons zum Monatswechsel
- **Auto-Fill**: Automatisches Befüllen des Monats mit Standard-Arbeitszeiten (9:00-17:30) - **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 - **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 - **CSV-Export (Abweichungen)**: Nur Tage ≠ 8,0h
- Ideal für Gleitzeit-Nachweise - Ideal für Gleitzeit-Nachweise
- **PDF-Export**: - **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) - **Bulk-Export**: Exportiert ausgewählte Einträge (im Bulk-Modus)
- Professionelles Layout mit Mitarbeiter-Info und Statistiken - Professionelles Layout mit Mitarbeiter-Info und Statistiken
- Automatische Tabelle mit allen Einträgen - Automatische Tabelle mit allen Einträgen
@@ -105,6 +126,8 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
### 🎨 Modernes UI/UX ### 🎨 Modernes UI/UX
- **Premium Design**: Glass-Morphism, Gradients, Schatten, Animationen - **Premium Design**: Glass-Morphism, Gradients, Schatten, Animationen
- **Responsive**: Desktop, Tablet, Mobile - **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 - **Dark Mode**: Augenschonendes dunkles Design
- **Toast-Benachrichtigungen**: Visuelles Feedback - **Toast-Benachrichtigungen**: Visuelles Feedback
- **Icons**: Lucide Icons für klare Symbolik - **Icons**: Lucide Icons für klare Symbolik
@@ -113,7 +136,7 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
## 🏗️ Technologie-Stack ## 🏗️ Technologie-Stack
**Backend:** **Backend:**
- Node.js 18+ mit Express.js - Node.js 20+ mit Express.js
- SQLite (better-sqlite3) für dateibasierte Persistenz - SQLite (better-sqlite3) für dateibasierte Persistenz
- Modulare Architektur (config, utils, routes) - Modulare Architektur (config, utils, routes)
@@ -127,6 +150,8 @@ Eine Full-Stack-Zeiterfassungsanwendung, entwickelt mit Node.js, Express, SQLite
**Infrastructure:** **Infrastructure:**
- Docker & Docker Compose - Docker & Docker Compose
- Multi-Stage Build für optimierte Images - 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 Actions CI/CD für automatische Builds
- Gitea Container Registry für Image-Hosting - Gitea Container Registry für Image-Hosting
@@ -142,15 +167,16 @@ timetracker/
│ ├── index.html # Single-Page Application │ ├── index.html # Single-Page Application
│ ├── favicon.svg # App Icon │ ├── favicon.svg # App Icon
│ └── js/ │ └── js/
│ ├── state.js # Globaler State │ ├── state.js # Globaler State (companyHolidayPreference, targetHours)
│ ├── utils.js # Hilfsfunktionen │ ├── utils.js # Hilfsfunktionen
│ ├── holidays.js # Feiertagsberechnung │ ├── holidays.js # Feiertagsberechnung (16 Bundesländer)
│ ├── bridge-days.js # Brückentag-Optimierung
│ ├── api.js # Backend-Kommunikation │ ├── api.js # Backend-Kommunikation
│ └── main.js # Hauptlogik (~3700 Zeilen) │ └── main.js # Hauptlogik (~4000 Zeilen)
├── .gitea/workflows/ # CI/CD Workflows ├── .gitea/workflows/ # CI/CD Workflows
│ └── docker-build.yml # Docker Build & Push │ └── docker-build.yml # Docker Build & Push
├── media/screenshots/ # App-Screenshots ├── media/screenshots/ # App-Screenshots
├── Dockerfile # Container-Image ├── Dockerfile # Container-Image (Node 20 + Python)
├── docker-compose.yml # Orchestrierung ├── docker-compose.yml # Orchestrierung
└── package.json └── package.json
``` ```
@@ -227,7 +253,7 @@ docker run -p 3000:3000 -v $(pwd)/db:/app/db zeiterfassung
### 💻 Option 3: Lokal (ohne Docker) ### 💻 Option 3: Lokal (ohne Docker)
**Voraussetzungen:** Node.js 18+ **Voraussetzungen:** Node.js 20+
```bash ```bash
# Repository klonen # Repository klonen
@@ -247,6 +273,9 @@ Die App bietet mehrere Export-Modi für verschiedene Anwendungsfälle:
### PDF-Export 📄 ### PDF-Export 📄
**Monats-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 - Klicke auf "PDF Export" in der Monatsansicht
- Exportiert alle Einträge des aktuellen Monats - Exportiert alle Einträge des aktuellen Monats
- Professionelles Layout mit: - 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 **Architektur:** Single-Page Application (SPA) mit REST-API Backend
**Tech-Details:** **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) - Flatpickr für Touch-optimierte Picker (auch in Tabellen-Inline-Edit)
- Lucide Icons für Symbolik - Lucide Icons für Symbolik
- jsPDF + autoTable für PDF-Generierung - jsPDF + autoTable für PDF-Generierung
- SQLite für dateibasierte Persistenz - SQLite für dateibasierte Persistenz
- Server-seitige Berechnungen für Datenintegrität - Server-seitige Berechnungen für Datenintegrität
- Responsive Design (Tailwind CSS via CDN) - Responsive Design (Tailwind CSS via CDN)
- Live-Updates während Timer läuft (Saldo, Nettostunden)
**Datenpersistenz:** **Datenpersistenz:**
- SQLite-Datenbank: `db/timetracker.db` - 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 - JSON-basierte Backups für Migration
**Code-Organisation:** **Code-Organisation:**
- `state.js`: Globaler Application State - `state.js`: Globaler Application State (companyHolidayPreference, targetHours)
- `utils.js`: Hilfsfunktionen (Datum, Zeit, Format) - `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 - `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 ## 📄 Lizenz

View File

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