This commit is contained in:
Carsten Graf
2026-01-23 18:33:33 +01:00
parent 587bdd2722
commit 563d4fc373
5 changed files with 132 additions and 43 deletions

View File

@@ -64,6 +64,9 @@ document.addEventListener('DOMContentLoaded', async function() {
// Ping-IP laden
loadPingIP();
// Statistiken laden
loadUserStats();
loadWeek();
document.getElementById('prevWeek').addEventListener('click', function() {
@@ -520,6 +523,7 @@ function updateOvertimeDisplay() {
// Gesamtstunden berechnen - direkt aus DOM-Elementen lesen für Echtzeit-Aktualisierung
let totalHours = 0;
let vacationHours = 0;
const startDateObj = new Date(startDate);
for (let i = 0; i < 7; i++) {
const date = new Date(startDateObj);
@@ -532,8 +536,30 @@ function updateOvertimeDisplay() {
const sickCheckbox = document.querySelector(`input[data-date="${dateStr}"][data-field="sick_status"]`);
const sickStatus = sickCheckbox ? sickCheckbox.checked : (currentEntries[dateStr]?.sick_status || false);
// Wenn Urlaub oder Krank, zähle nur diese Stunden (nicht zusätzlich Arbeitsstunden)
if (vacationType === 'full') {
totalHours += 8; // Ganzer Tag Urlaub = 8 Stunden
vacationHours += 8; // Ganzer Tag Urlaub = 8 Stunden
} else if (vacationType === 'half') {
vacationHours += 4; // Halber Tag Urlaub = 4 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`);
const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`);
const breakInput = document.querySelector(`input[data-date="${dateStr}"][data-field="break_minutes"]`);
const startTime = startInput ? startInput.value : '';
const endTime = endInput ? endInput.value : '';
const breakMinutes = parseInt(breakInput ? breakInput.value : 0) || 0;
if (startTime && endTime) {
const start = new Date(`2000-01-01T${startTime}`);
const end = new Date(`2000-01-01T${endTime}`);
const diffMs = end - start;
const hours = (diffMs / (1000 * 60 * 60)) - (breakMinutes / 60);
totalHours += hours;
} else if (currentEntries[dateStr]?.total_hours) {
// Fallback auf gespeicherte Werte
totalHours += parseFloat(currentEntries[dateStr].total_hours) || 0;
}
} else if (sickStatus) {
totalHours += 8; // Krank = 8 Stunden
} else {
@@ -559,24 +585,6 @@ function updateOvertimeDisplay() {
}
}
// Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit)
let vacationHours = 0;
const startDateObj2 = new Date(startDate);
for (let i = 0; i < 7; i++) {
const date = new Date(startDateObj2);
date.setDate(date.getDate() + i);
const dateStr = formatDate(date);
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
const vacationType = vacationSelect ? vacationSelect.value : (currentEntries[dateStr]?.vacation_type || '');
if (vacationType === 'full') {
vacationHours += 8; // Ganzer Tag = 8 Stunden
} else if (vacationType === 'half') {
vacationHours += 4; // Halber Tag = 4 Stunden
}
}
// Genommene Überstunden berechnen - direkt aus DOM lesen
let overtimeTaken = 0;
const startDateObj3 = new Date(startDate);
@@ -1172,7 +1180,8 @@ async function submitWeekWithReason(versionReason) {
const versionText = result.version ? ` (Version ${result.version})` : '';
alert(`Stundenzettel wurde erfolgreich eingereicht${versionText}!`);
loadWeek(); // Neu laden um Status zu aktualisieren
// Statistiken werden durch updateOvertimeDisplay() aktualisiert
// Statistiken aktualisieren
loadUserStats();
} else {
console.error('Fehler-Details:', result);
alert(result.error || 'Fehler beim Einreichen des Stundenzettels');

View File

@@ -260,18 +260,27 @@ function registerUserRoutes(app) {
let weekVacationHours = 0;
entries.forEach(entry => {
if (entry.total_hours) {
weekTotalHours += entry.total_hours;
}
if (entry.overtime_taken_hours) {
weekOvertimeTaken += entry.overtime_taken_hours;
}
// Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
if (entry.vacation_type === 'full') {
weekVacationDays += 1;
weekVacationHours += 8;
weekVacationHours += 8; // Ganzer Tag = 8 Stunden
// Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') {
weekVacationDays += 0.5;
weekVacationHours += 4;
weekVacationHours += 4; // Halber Tag = 4 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
if (entry.total_hours) {
weekTotalHours += entry.total_hours;
}
} else {
// Kein Urlaub - zähle nur Arbeitsstunden
if (entry.total_hours) {
weekTotalHours += entry.total_hours;
}
}
});

View File

@@ -146,18 +146,27 @@ function registerVerwaltungRoutes(app) {
let vacationHours = 0;
entries.forEach(entry => {
if (entry.total_hours) {
totalHours += entry.total_hours;
}
if (entry.overtime_taken_hours) {
overtimeTaken += entry.overtime_taken_hours;
}
// Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
if (entry.vacation_type === 'full') {
vacationDays += 1;
vacationHours += 8; // Ganzer Tag = 8 Stunden
// Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') {
vacationDays += 0.5;
vacationHours += 4; // Halber Tag = 4 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
if (entry.total_hours) {
totalHours += entry.total_hours;
}
} else {
// Kein Urlaub - zähle nur Arbeitsstunden
if (entry.total_hours) {
totalHours += entry.total_hours;
}
}
});

View File

@@ -221,15 +221,21 @@ function generatePDF(timesheetId, req, res) {
doc.fontSize(10);
}
// Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
if (entry.vacation_type === 'full') {
vacationHours += 8; // Ganzer Tag = 8 Stunden
// Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') {
vacationHours += 4; // Halber Tag = 4 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
if (entry.total_hours) {
totalHours += entry.total_hours;
}
} else {
// Kein Urlaub - zähle nur Arbeitsstunden
if (entry.total_hours) {
totalHours += entry.total_hours;
}
// Urlaubsstunden für Überstunden-Berechnung sammeln
if (entry.vacation_type === 'full') {
vacationHours += 8; // Ganzer Tag = 8 Stunden
} else if (entry.vacation_type === 'half') {
vacationHours += 4; // Halber Tag = 4 Stunden
}
doc.moveDown(0.5);
@@ -249,9 +255,9 @@ function generatePDF(timesheetId, req, res) {
// Überstunden berechnen und anzeigen
const wochenstunden = timesheet.wochenstunden || 0;
// Überstunden = Gesamtstunden - Wochenstunden
// Urlaub zählt als normale Arbeitszeit, daher sind Urlaubsstunden bereits in totalHours enthalten
const overtimeHours = totalHours - wochenstunden;
// Überstunden = (Tatsächliche Stunden + Urlaubsstunden) - Wochenstunden
const totalHoursWithVacation = totalHours + vacationHours;
const overtimeHours = totalHoursWithVacation - wochenstunden;
doc.moveDown(0.3);
doc.font('Helvetica-Bold');
@@ -432,14 +438,21 @@ function generatePDFToBuffer(timesheetId, req) {
doc.fontSize(10);
}
// Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
if (entry.vacation_type === 'full') {
vacationHours += 8; // Ganzer Tag = 8 Stunden
// Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') {
vacationHours += 4; // Halber Tag = 4 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
if (entry.total_hours) {
totalHours += entry.total_hours;
}
} else {
// Kein Urlaub - zähle nur Arbeitsstunden
if (entry.total_hours) {
totalHours += entry.total_hours;
}
if (entry.vacation_type === 'full') {
vacationHours += 8;
} else if (entry.vacation_type === 'half') {
vacationHours += 4;
}
doc.moveDown(0.5);
@@ -457,7 +470,9 @@ function generatePDFToBuffer(timesheetId, req) {
doc.text(`Gesamtstunden: ${totalHours.toFixed(2)} h`, 50, doc.y);
const wochenstunden = timesheet.wochenstunden || 0;
const overtimeHours = totalHours - wochenstunden;
// Überstunden = (Tatsächliche Stunden + Urlaubsstunden) - Wochenstunden
const totalHoursWithVacation = totalHours + vacationHours;
const overtimeHours = totalHoursWithVacation - wochenstunden;
doc.moveDown(0.3);
doc.font('Helvetica-Bold');

View File

@@ -55,6 +55,53 @@
<p class="help-text">Stunden werden automatisch gespeichert. Am Ende der Woche können Sie die Stunden abschicken.</p>
</div>
</div>
<!-- Rechte Seitenleiste mit Statistiken und Erfassungs-URLs -->
<div class="user-stats-panel">
<!-- Statistik-Karten -->
<div class="stat-card">
<div class="stat-label">Aktuelle Überstunden</div>
<div class="stat-value" id="currentOvertime">-</div>
<div class="stat-unit">Stunden</div>
</div>
<div class="stat-card stat-vacation">
<div class="stat-label">Verbleibende Urlaubstage</div>
<div class="stat-value" id="remainingVacation">-</div>
<div class="stat-unit">von <span id="totalVacation">-</span> Tagen</div>
</div>
<!-- URL-Erfassung -->
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e0e0e0;">
<h3 style="font-size: 14px; margin-bottom: 15px; color: #2c3e50;">Zeiterfassung per URL</h3>
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-in URL</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="checkinUrl" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
<button onclick="copyToClipboard('checkinUrl')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
</div>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-out URL</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="checkoutUrl" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
<button onclick="copyToClipboard('checkoutUrl')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
</div>
</div>
</div>
<!-- IP-Erfassung -->
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e0e0e0;">
<h3 style="font-size: 14px; margin-bottom: 15px; color: #2c3e50;">IP-basierte Zeiterfassung</h3>
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Ping-IP Adresse</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="pingIpInput" placeholder="z.B. 192.168.1.100" style="flex: 1; padding: 8px; font-size: 12px; border: 1px solid #ddd; border-radius: 4px;">
<button onclick="window.savePingIP()" class="btn btn-sm btn-success" style="padding: 8px 12px;">Speichern</button>
</div>
<p style="font-size: 11px; color: #666; margin-top: 5px; font-style: italic;">Ihre IP-Adresse für automatische Zeiterfassung</p>
</div>
</div>
</div>
</div>
</div>