Compare commits

..

2 Commits

Author SHA1 Message Date
Felix Schlusche
3f36ec3cc7 feat: implement maximum net working hours validation and auto-stop timer after 10 hours
All checks were successful
Build and Push Docker Image / build (push) Successful in 36s
2025-10-24 17:41:38 +02:00
Felix Schlusche
defc0f3161 docs: update installation instructions for Docker and local setup 2025-10-24 17:29:52 +02:00
2 changed files with 88 additions and 36 deletions

View File

@@ -166,15 +166,36 @@ Die App implementiert deutsches Arbeitszeitgesetz (ArbZG):
## 🚀 Installation & Ausführung ## 🚀 Installation & Ausführung
### 🐳 Option 1: Docker Compose (Empfohlen) ### <EFBFBD> Option 1: Vorgefertigtes Docker Image (Empfohlen)
**Voraussetzungen:** Docker & Docker Compose **Voraussetzungen:** Docker (& Docker Compose optional)
```bash ```bash
# Repository klonen # Image pullen (public registry, kein Login nötig)
git clone https://gitea.fx-se.de/maggot/timetracker.git docker pull gitea.fx-se.de/maggot/timetracker:latest
cd timetracker
# Container starten
docker run -d \
-p 3000:3000 \
-v $(pwd)/db:/app/db \
--name timetracker \
gitea.fx-se.de/maggot/timetracker:latest
```
**Oder mit docker-compose.yml:**
```yaml
version: '3.8'
services:
app:
image: gitea.fx-se.de/maggot/timetracker:latest
ports:
- "3000:3000"
volumes:
- ./db:/app/db
restart: unless-stopped
```
```bash
# Starten # Starten
docker-compose up -d docker-compose up -d
@@ -190,39 +211,13 @@ docker-compose down -v
**App läuft auf:** `http://localhost:3000` **App läuft auf:** `http://localhost:3000`
### 🐋 Option 2: Vorgefertigtes Docker Image (Gitea Registry) ### 🔨 Option 2: Docker (manuell bauen)
```bash ```bash
# Login zur Registry # Repository klonen
docker login gitea.fx-se.de git clone https://gitea.fx-se.de/maggot/timetracker.git
cd timetracker
# Image pullen
docker pull gitea.fx-se.de/maggot/timetracker:latest
# Container starten
docker run -d \
-p 3000:3000 \
-v $(pwd)/db:/app/db \
--name timetracker \
gitea.fx-se.de/maggot/timetracker:latest
```
**docker-compose.yml mit Registry-Image:**
```yaml
version: '3.8'
services:
app:
image: gitea.fx-se.de/maggot/timetracker:latest
ports:
- "3000:3000"
volumes:
- ./db:/app/db
restart: unless-stopped
```
### 🔨 Option 3: Docker (manuell bauen)
```bash
# Image bauen # Image bauen
docker build -t zeiterfassung . docker build -t zeiterfassung .
@@ -230,11 +225,15 @@ docker build -t zeiterfassung .
docker run -p 3000:3000 -v $(pwd)/db:/app/db zeiterfassung docker run -p 3000:3000 -v $(pwd)/db:/app/db zeiterfassung
``` ```
### 💻 Option 4: Lokal (ohne Docker) ### 💻 Option 3: Lokal (ohne Docker)
**Voraussetzungen:** Node.js 18+ **Voraussetzungen:** Node.js 18+
```bash ```bash
# Repository klonen
git clone https://gitea.fx-se.de/maggot/timetracker.git
cd timetracker
npm install npm install
npm start npm start
``` ```

View File

@@ -549,6 +549,14 @@ function updateTimer() {
document.getElementById('timerStatus').textContent = `Läuft seit ${timerStartTimeString} - Pause (${remainingMinutes} Min)`; document.getElementById('timerStatus').textContent = `Läuft seit ${timerStartTimeString} - Pause (${remainingMinutes} Min)`;
} else { } else {
elapsed = Math.floor((now - timerStartTime) / 1000) - timerPausedDuration; elapsed = Math.floor((now - timerStartTime) / 1000) - timerPausedDuration;
// Check if 10h net time reached - auto stop timer
const netHours = elapsed / 3600;
if (netHours >= 10) {
showNotification('🛑 Maximale Arbeitszeit (10h netto) erreicht. Timer wird automatisch gestoppt.', 'warning');
stopWork();
return;
}
} }
document.getElementById('timerDisplay').textContent = formatDuration(elapsed); document.getElementById('timerDisplay').textContent = formatDuration(elapsed);
@@ -1686,6 +1694,13 @@ async function handleFormSubmit(e) {
return; return;
} }
// Validate max 10h net time
const netHours = calculateNetHours(startTime, endTime, pauseMinutes);
if (netHours > 10) {
showNotification('❌ Maximale Arbeitszeit überschritten! Netto-Arbeitszeit darf maximal 10,0h betragen.', 'error');
return;
}
if (currentEditingId) { if (currentEditingId) {
// Update existing entry // Update existing entry
const success = await updateEntry(currentEditingId, date, startTime, endTime, pauseMinutes, location); const success = await updateEntry(currentEditingId, date, startTime, endTime, pauseMinutes, location);
@@ -1705,6 +1720,35 @@ async function handleFormSubmit(e) {
} }
} }
/**
* Calculate net hours from times and pause
*/
function calculateNetHours(startTime, endTime, pauseMinutes) {
const [startHour, startMin] = startTime.split(':').map(Number);
const [endHour, endMin] = endTime.split(':').map(Number);
const startMinutes = startHour * 60 + startMin;
const endMinutes = endHour * 60 + endMin;
let grossMinutes = endMinutes - startMinutes;
if (grossMinutes < 0) grossMinutes += 24 * 60; // Handle overnight
const grossHours = grossMinutes / 60;
// Calculate pause if not provided
let pause = pauseMinutes || 0;
if (pauseMinutes === null || pauseMinutes === undefined) {
if (grossHours >= 9) {
pause = 45;
} else if (grossHours >= 6) {
pause = 30;
}
}
const netHours = grossHours - (pause / 60);
return netHours;
}
/** /**
* Handle delete button click * Handle delete button click
*/ */
@@ -2872,6 +2916,15 @@ function handleCellClick(e) {
pauseToSend = null; // Trigger auto-calculation on server pauseToSend = null; // Trigger auto-calculation on server
} }
// Validate max 10h net time
const netHours = calculateNetHours(values.startTime, values.endTime, pauseToSend);
if (netHours > 10) {
showNotification('❌ Maximale Arbeitszeit überschritten! Netto-Arbeitszeit darf maximal 10,0h betragen.', 'error');
cell.classList.remove('editing');
cell.innerHTML = originalContent;
return;
}
// Update via API // Update via API
const success = await updateEntry( const success = await updateEntry(
entryId, entryId,