Compare commits

...

3 Commits

2 changed files with 69 additions and 18 deletions
+51 -12
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
+18 -6
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;