Compare commits
3 Commits
bad91636b5
...
0b408c93ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b408c93ee | ||
|
|
0045a8f8d0 | ||
|
|
d04ab18ba1 |
63
README.md
63
README.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user