diff --git a/checkin-server.js b/checkin-server.js
index e1789d4..e9c387a 100644
--- a/checkin-server.js
+++ b/checkin-server.js
@@ -47,7 +47,7 @@ checkinApp.get('/api/checkin/:userId', (req, res) => {
const currentTime = getCurrentTime();
// Prüfe ob User existiert
- db.get('SELECT id FROM users WHERE id = ?', [userId], (err, user) => {
+ db.get('SELECT id, default_break_minutes FROM users WHERE id = ?', [userId], (err, user) => {
if (err || !user) {
return sendResponse(req, res, false, { error: 'Benutzer nicht gefunden', status: 404 });
}
@@ -61,10 +61,14 @@ checkinApp.get('/api/checkin/:userId', (req, res) => {
const successTitle = 'Hallo, du wurdest erfolgreich eingecheckt';
+ const userDefaultBreakMinutes = Number.isInteger(user?.default_break_minutes) && user.default_break_minutes >= 0
+ ? user.default_break_minutes
+ : 30;
+
if (!entry) {
// Kein Eintrag existiert → Erstelle neuen mit start_time
- db.run(`INSERT INTO timesheet_entries (user_id, date, start_time, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)`,
- [userId, currentDate, currentTime], (err) => {
+ db.run(`INSERT INTO timesheet_entries (user_id, date, start_time, break_minutes, updated_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ [userId, currentDate, currentTime, userDefaultBreakMinutes], (err) => {
if (err) {
return sendResponse(req, res, false, { error: 'Fehler beim Erstellen des Eintrags', status: 500 });
}
diff --git a/public/css/style.css b/public/css/style.css
index 5c7f43f..b21245a 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -840,6 +840,17 @@ table input[type="text"] {
color: #7f8c8d;
}
+/* Überstunden-Farbklassen (global genutzt, z. B. Verwaltung & Auswertung) */
+.overtime-positive {
+ color: #27ae60;
+ font-weight: 600;
+}
+
+.overtime-negative {
+ color: #e74c3c;
+ font-weight: 600;
+}
+
/* Activities/Tätigkeiten */
.activities-row {
background-color: #f8f9fa;
@@ -864,7 +875,7 @@ table input[type="text"] {
.activity-row {
display: grid;
- grid-template-columns: 1fr 150px 120px;
+ grid-template-columns: 1fr 150px 150px;
gap: 15px;
align-items: center;
}
@@ -893,7 +904,7 @@ table input[type="text"] {
}
.activity-hours-input {
- width: 80px;
+ width: 64px;
}
.activity-hours-label {
@@ -901,6 +912,11 @@ table input[type="text"] {
color: #555;
}
+.activity-hours-hh-input,
+.activity-hours-mm-input {
+ text-align: center;
+}
+
.activity-project {
display: flex;
align-items: center;
diff --git a/public/js/dashboard.js b/public/js/dashboard.js
index 0472980..b6bb9cf 100644
--- a/public/js/dashboard.js
+++ b/public/js/dashboard.js
@@ -374,6 +374,52 @@ function getFullDayHours() {
return userWochenstunden && userArbeitstage ? (userWochenstunden / userArbeitstage) : 8;
}
+function decimalHoursToParts(decimalHours) {
+ const parsed = Number(decimalHours);
+ if (!Number.isFinite(parsed) || parsed <= 0) {
+ return { hh: '', mm: '' };
+ }
+
+ const totalMinutes = Math.round(parsed * 60);
+ const hh = Math.floor(totalMinutes / 60);
+ const mm = totalMinutes % 60;
+ return {
+ hh: String(hh),
+ mm: String(mm).padStart(2, '0')
+ };
+}
+
+function parseActivityHoursFromInputs(hoursInput, minutesInput, fallbackValue) {
+ const hoursRaw = hoursInput ? hoursInput.value.trim() : '';
+ const minutesRaw = minutesInput ? minutesInput.value.trim() : '';
+
+ if (hoursRaw === '' && minutesRaw === '') {
+ return 0;
+ }
+
+ const parsedHours = parseInt(hoursRaw, 10);
+ const parsedMinutes = parseInt(minutesRaw, 10);
+
+ const safeHours = Number.isFinite(parsedHours) && parsedHours >= 0 ? parsedHours : 0;
+ const safeMinutes = Number.isFinite(parsedMinutes) && parsedMinutes >= 0 ? parsedMinutes : 0;
+ const totalMinutes = (safeHours * 60) + safeMinutes;
+
+ if (!Number.isFinite(totalMinutes) || totalMinutes < 0) {
+ return Number.isFinite(fallbackValue) ? fallbackValue : 0;
+ }
+
+ const normalizedHours = Math.floor(totalMinutes / 60);
+ const normalizedMinutes = totalMinutes % 60;
+ if (hoursInput) {
+ hoursInput.value = totalMinutes > 0 ? String(normalizedHours) : '';
+ }
+ if (minutesInput) {
+ minutesInput.value = totalMinutes > 0 ? String(normalizedMinutes).padStart(2, '0') : '';
+ }
+
+ return totalMinutes / 60;
+}
+
// Woche laden
async function loadWeek() {
try {
@@ -603,7 +649,9 @@ function renderWeek() {
@@ -1038,7 +1100,8 @@ function handleOvertimeChange(dateStr, overtimeHours) {
// (Überstunden werden nur im PDF angezeigt, nicht als Tätigkeit)
for (let i = 1; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_desc"]`);
- const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours"]`);
+ const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours_hh"]`);
+ const minutesInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours_mm"]`);
if (descInput && descInput.value && descInput.value.trim().toLowerCase() === 'überstunden') {
descInput.value = '';
@@ -1047,6 +1110,10 @@ function handleOvertimeChange(dateStr, overtimeHours) {
hoursInput.value = '';
saveEntry(hoursInput);
}
+ if (minutesInput) {
+ minutesInput.value = '';
+ saveEntry(minutesInput);
+ }
}
}
@@ -1155,7 +1222,18 @@ async function saveEntry(input) {
// Wichtig: Leere Strings werden zu null konvertiert, aber ein Wert sollte vorhanden sein
const start_time = actualStartTime;
const end_time = actualEndTime;
- let break_minutes = breakInput && breakInput.value ? (parseInt(breakInput.value) || 0) : (parseInt(currentEntries[date].break_minutes) || 0);
+ let break_minutes = defaultBreakMinutes;
+ if (breakInput && breakInput.value !== '') {
+ const parsedBreak = parseInt(breakInput.value, 10);
+ break_minutes = Number.isFinite(parsedBreak) && parsedBreak >= 0 ? parsedBreak : defaultBreakMinutes;
+ } else if (
+ currentEntries[date].break_minutes !== null &&
+ currentEntries[date].break_minutes !== undefined &&
+ currentEntries[date].break_minutes !== ''
+ ) {
+ const parsedStoredBreak = parseInt(currentEntries[date].break_minutes, 10);
+ break_minutes = Number.isFinite(parsedStoredBreak) && parsedStoredBreak >= 0 ? parsedStoredBreak : defaultBreakMinutes;
+ }
const notes = notesInput ? (notesInput.value || '') : (currentEntries[date].notes || '');
const vacation_type = vacationSelect && vacationSelect.value ? vacationSelect.value : (currentEntries[date].vacation_type || null);
@@ -1168,12 +1246,16 @@ async function saveEntry(input) {
const activities = [];
for (let i = 1; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_desc"]`);
- const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours"]`);
+ const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours_hh"]`);
+ const minutesInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours_mm"]`);
const projectInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_project_number"]`);
+ const fallbackHours = parseFloat(currentEntries[date][`activity${i}_hours`]) || 0;
activities.push({
desc: descInput ? (descInput.value || null) : (currentEntries[date][`activity${i}_desc`] || null),
- hours: hoursInput ? (parseFloat(hoursInput.value) || 0) : (parseFloat(currentEntries[date][`activity${i}_hours`]) || 0),
+ hours: (hoursInput || minutesInput)
+ ? parseActivityHoursFromInputs(hoursInput, minutesInput, fallbackHours)
+ : fallbackHours,
projectNumber: projectInput ? (projectInput.value || null) : (currentEntries[date][`activity${i}_project_number`] || null)
});
}
@@ -1337,21 +1419,27 @@ async function saveEntry(input) {
// 5. Bei ganztägigem Urlaub: Setze "Urlaub" als erste Tätigkeit und leere andere
if (isFullDayVacation) {
const descInput = document.querySelector(`input[data-date="${date}"][data-field="activity1_desc"]`);
- const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity1_hours"]`);
+ const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity1_hours_hh"]`);
+ const minutesInput = document.querySelector(`input[data-date="${date}"][data-field="activity1_hours_mm"]`);
if (descInput) {
descInput.value = 'Urlaub';
currentEntries[date].activity1_desc = 'Urlaub';
}
if (hoursInput) {
- hoursInput.value = fullDayHours.toFixed(2);
+ const fullDayParts = decimalHoursToParts(fullDayHours);
+ hoursInput.value = fullDayParts.hh;
+ if (minutesInput) {
+ minutesInput.value = fullDayParts.mm;
+ }
currentEntries[date].activity1_hours = fullDayHours;
}
// Leere andere Tätigkeiten
for (let i = 2; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_desc"]`);
- const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours"]`);
+ const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours_hh"]`);
+ const minutesInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours_mm"]`);
const projectInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_project_number"]`);
if (descInput) {
@@ -1360,6 +1448,9 @@ async function saveEntry(input) {
}
if (hoursInput) {
hoursInput.value = '';
+ if (minutesInput) {
+ minutesInput.value = '';
+ }
currentEntries[date][`activity${i}_hours`] = 0;
}
if (projectInput) {
@@ -1371,7 +1462,8 @@ async function saveEntry(input) {
// Bei Abwahl von Urlaub (nicht full): Alle Tätigkeitsfelder leeren
for (let i = 1; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_desc"]`);
- const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours"]`);
+ const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours_hh"]`);
+ const minutesInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours_mm"]`);
const projectInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_project_number"]`);
if (descInput) {
@@ -1380,6 +1472,9 @@ async function saveEntry(input) {
}
if (hoursInput) {
hoursInput.value = '';
+ if (minutesInput) {
+ minutesInput.value = '';
+ }
currentEntries[date][`activity${i}_hours`] = 0;
}
if (projectInput) {
@@ -2118,7 +2213,8 @@ function toggleSickStatus(dateStr) {
// Leere alle Tätigkeitsfelder
for (let i = 1; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_desc"]`);
- const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours"]`);
+ const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours_hh"]`);
+ const minutesInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours_mm"]`);
const projectInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_project_number"]`);
if (descInput) {
@@ -2127,6 +2223,9 @@ function toggleSickStatus(dateStr) {
}
if (hoursInput) {
hoursInput.value = '';
+ if (minutesInput) {
+ minutesInput.value = '';
+ }
currentEntries[dateStr][`activity${i}_hours`] = 0;
}
if (projectInput) {
@@ -2137,7 +2236,8 @@ function toggleSickStatus(dateStr) {
} else {
// Bei Aktivierung: Setze "Krank" als erste Tätigkeit und leere andere
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_desc"]`);
- const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_hours"]`);
+ const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_hours_hh"]`);
+ const minutesInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_hours_mm"]`);
const fullDayHours = getFullDayHours();
if (descInput) {
@@ -2145,14 +2245,19 @@ function toggleSickStatus(dateStr) {
currentEntries[dateStr].activity1_desc = 'Krank';
}
if (hoursInput) {
- hoursInput.value = fullDayHours.toFixed(2);
+ const fullDayParts = decimalHoursToParts(fullDayHours);
+ hoursInput.value = fullDayParts.hh;
+ if (minutesInput) {
+ minutesInput.value = fullDayParts.mm;
+ }
currentEntries[dateStr].activity1_hours = fullDayHours;
}
// Leere andere Tätigkeiten
for (let i = 2; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_desc"]`);
- const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours"]`);
+ const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours_hh"]`);
+ const minutesInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours_mm"]`);
const projectInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_project_number"]`);
if (descInput) {
@@ -2161,6 +2266,9 @@ function toggleSickStatus(dateStr) {
}
if (hoursInput) {
hoursInput.value = '';
+ if (minutesInput) {
+ minutesInput.value = '';
+ }
currentEntries[dateStr][`activity${i}_hours`] = 0;
}
if (projectInput) {
diff --git a/routes/timesheet-routes.js b/routes/timesheet-routes.js
index 5b7125d..bee7172 100644
--- a/routes/timesheet-routes.js
+++ b/routes/timesheet-routes.js
@@ -20,6 +20,11 @@ function registerTimesheetRoutes(app) {
overtime_taken_hours, vacation_type, sick_status, weekend_travel
} = req.body;
const userId = req.session.userId;
+ const hasExplicitBreakMinutes = break_minutes !== undefined && break_minutes !== null && break_minutes !== '';
+ const parsedRequestedBreakMinutes = hasExplicitBreakMinutes ? parseInt(break_minutes, 10) : null;
+ const requestedBreakMinutes = Number.isFinite(parsedRequestedBreakMinutes) && parsedRequestedBreakMinutes >= 0
+ ? parsedRequestedBreakMinutes
+ : null;
// Normalisiere end_time: Leere Strings werden zu null
const normalizedEndTime = (end_time && typeof end_time === 'string' && end_time.trim() !== '') ? end_time.trim() : (end_time || null);
@@ -55,7 +60,7 @@ function registerTimesheetRoutes(app) {
}
// User-Daten laden (für Überstunden-Berechnung)
- db.get('SELECT wochenstunden, arbeitstage FROM users WHERE id = ?', [userId], (err, user) => {
+ db.get('SELECT wochenstunden, arbeitstage, default_break_minutes FROM users WHERE id = ?', [userId], (err, user) => {
if (err) {
console.error('Fehler beim Laden der User-Daten:', err);
return res.status(500).json({ error: 'Fehler beim Laden der User-Daten' });
@@ -63,6 +68,10 @@ function registerTimesheetRoutes(app) {
const wochenstunden = user?.wochenstunden || 0;
const arbeitstage = user?.arbeitstage || 5;
+ const defaultBreakMinutes = Number.isInteger(user?.default_break_minutes) && user.default_break_minutes >= 0
+ ? user.default_break_minutes
+ : 30;
+ let effectiveBreakMinutes = requestedBreakMinutes !== null ? requestedBreakMinutes : defaultBreakMinutes;
const overtimeValue = overtime_taken_hours ? parseFloat(overtime_taken_hours) : 0;
const fullDayHours = wochenstunden > 0 && arbeitstage > 0 ? wochenstunden / arbeitstage : 0;
@@ -106,7 +115,7 @@ function registerTimesheetRoutes(app) {
const start = new Date(`2000-01-01T${normalizedStartTime}`);
const end = new Date(`2000-01-01T${normalizedEndTime}`);
const diffMs = end - start;
- total_hours = (diffMs / (1000 * 60 * 60)) - (break_minutes / 60);
+ total_hours = (diffMs / (1000 * 60 * 60)) - (effectiveBreakMinutes / 60);
// Wochenend-Prozentsatz anwenden (nur wenn weekend_travel aktiviert UND es ist ein Wochenendtag)
if (isWeekend && isWeekendTravel && total_hours > 0 && !isSick && vacation_type !== 'full') {
@@ -124,9 +133,26 @@ function registerTimesheetRoutes(app) {
// Sie werden über overtime_taken_hours in der PDF angezeigt
// Prüfen ob Eintrag existiert - verwende den neuesten Eintrag falls mehrere existieren
- db.get('SELECT id, applied_weekend_percentage FROM timesheet_entries WHERE user_id = ? AND date = ? ORDER BY updated_at DESC, id DESC LIMIT 1',
+ db.get('SELECT id, break_minutes, applied_weekend_percentage FROM timesheet_entries WHERE user_id = ? AND date = ? ORDER BY updated_at DESC, id DESC LIMIT 1',
[userId, date], (err, row) => {
if (row) {
+ if (requestedBreakMinutes === null && row.break_minutes !== null && row.break_minutes !== undefined) {
+ effectiveBreakMinutes = row.break_minutes;
+ }
+
+ if (normalizedStartTime && normalizedEndTime && !isSick && vacation_type !== 'full' && !isFullDayOvertime) {
+ const start = new Date(`2000-01-01T${normalizedStartTime}`);
+ const end = new Date(`2000-01-01T${normalizedEndTime}`);
+ const diffMs = end - start;
+ total_hours = (diffMs / (1000 * 60 * 60)) - (effectiveBreakMinutes / 60);
+ if (isWeekend && isWeekendTravel && total_hours > 0) {
+ const weekendPercentage = getWeekendPercentage(date);
+ if (weekendPercentage >= 100) {
+ total_hours = total_hours * (weekendPercentage / 100);
+ }
+ }
+ }
+
// Wenn bereits ein gespeicherter Prozentsatz existiert, diesen verwenden (historische Einträge bleiben unverändert)
let finalAppliedPercentage = appliedWeekendPercentage;
if (row.applied_weekend_percentage !== null && row.applied_weekend_percentage !== undefined) {
@@ -138,7 +164,7 @@ function registerTimesheetRoutes(app) {
const start = new Date(`2000-01-01T${normalizedStartTime}`);
const end = new Date(`2000-01-01T${normalizedEndTime}`);
const diffMs = end - start;
- const baseHours = (diffMs / (1000 * 60 * 60)) - (break_minutes / 60);
+ const baseHours = (diffMs / (1000 * 60 * 60)) - (effectiveBreakMinutes / 60);
if (baseHours > 0 && finalAppliedPercentage >= 100) {
total_hours = baseHours * (finalAppliedPercentage / 100);
}
@@ -165,7 +191,7 @@ function registerTimesheetRoutes(app) {
updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[
- finalStartTime, finalEndTime, break_minutes, total_hours, notes,
+ finalStartTime, finalEndTime, effectiveBreakMinutes, total_hours, notes,
finalActivity1Desc || null, finalActivity1Hours, activity1_project_number || null,
finalActivity2Desc || null, parseFloat(activity2_hours) || 0, activity2_project_number || null,
finalActivity3Desc || null, parseFloat(activity3_hours) || 0, activity3_project_number || null,
@@ -197,7 +223,7 @@ function registerTimesheetRoutes(app) {
overtime_taken_hours, vacation_type, sick_status, weekend_travel, applied_weekend_percentage)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
- userId, date, finalStartTime, finalEndTime, break_minutes, total_hours, notes,
+ userId, date, finalStartTime, finalEndTime, effectiveBreakMinutes, total_hours, notes,
finalActivity1Desc || null, finalActivity1Hours, activity1_project_number || null,
finalActivity2Desc || null, parseFloat(activity2_hours) || 0, activity2_project_number || null,
finalActivity3Desc || null, parseFloat(activity3_hours) || 0, activity3_project_number || null,
diff --git a/services/ping-service.js b/services/ping-service.js
index 90f10b6..5761440 100644
--- a/services/ping-service.js
+++ b/services/ping-service.js
@@ -5,7 +5,7 @@ const { db } = require('../database');
const { getCurrentDate, getCurrentTime, updateTotalHours } = require('../helpers/utils');
// Ping-Funktion für einen User
-async function pingUserIP(userId, ip, currentDate, currentTime) {
+async function pingUserIP(userId, ip, defaultBreakMinutes, currentDate, currentTime) {
try {
const result = await ping.promise.probe(ip, {
timeout: 3,
@@ -31,6 +31,10 @@ async function pingUserIP(userId, ip, currentDate, currentTime) {
return;
}
+ const userDefaultBreakMinutes = Number.isInteger(defaultBreakMinutes) && defaultBreakMinutes >= 0
+ ? defaultBreakMinutes
+ : 30;
+
if (isReachable) {
// IP ist erreichbar
if (!pingStatus) {
@@ -67,9 +71,9 @@ async function pingUserIP(userId, ip, currentDate, currentTime) {
});
} else if (!entry) {
// Kein Eintrag existiert → Erstelle neuen mit start_time
- db.run(`INSERT INTO timesheet_entries (user_id, date, start_time, updated_at)
- VALUES (?, ?, ?, CURRENT_TIMESTAMP)`,
- [userId, currentDate, currentTime], (err) => {
+ db.run(`INSERT INTO timesheet_entries (user_id, date, start_time, break_minutes, updated_at)
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ [userId, currentDate, currentTime, userDefaultBreakMinutes], (err) => {
if (err) {
console.error(`Fehler beim Erstellen des Eintrags für User ${userId}:`, err);
} else {
@@ -161,7 +165,7 @@ function setupPingService() {
const currentTime = getCurrentTime();
// Hole alle User mit IP-Adresse
- db.all('SELECT id, ping_ip FROM users WHERE ping_ip IS NOT NULL AND ping_ip != ""', (err, users) => {
+ db.all('SELECT id, ping_ip, default_break_minutes FROM users WHERE ping_ip IS NOT NULL AND ping_ip != ""', (err, users) => {
if (err) {
console.error('Fehler beim Abrufen der User mit IP-Adressen:', err);
return;
@@ -173,7 +177,7 @@ function setupPingService() {
// Ping alle User parallel
users.forEach(user => {
- pingUserIP(user.id, user.ping_ip, currentDate, currentTime);
+ pingUserIP(user.id, user.ping_ip, user.default_break_minutes, currentDate, currentTime);
});
});
}, 60000); // Jede Minute
diff --git a/views/overtime-breakdown.ejs b/views/overtime-breakdown.ejs
index 1fc1913..a5a0484 100644
--- a/views/overtime-breakdown.ejs
+++ b/views/overtime-breakdown.ejs
@@ -145,15 +145,11 @@
Davon genommen:
-
-
- Verbleibend:
- -
-
Manuelle Korrektur (Verwaltung):
-
-
Lade Daten...
@@ -323,12 +323,16 @@
corrections.forEach(c => {
const dt = parseSqliteDatetime(c.corrected_at);
const dateText = dt ? dt.toLocaleDateString('de-DE') : '';
- const hoursText = formatHoursMin(c.correction_hours);
+ const rawHours = Number(c.correction_hours) || 0;
+ const absHours = Math.abs(rawHours);
+ const signPrefix = rawHours >= 0 ? '+' : '-';
+ const hoursClass = rawHours >= 0 ? 'overtime-positive' : 'overtime-negative';
+ const hoursDisplay = signPrefix + formatHoursMin(absHours);
const reason = (c && c.reason != null) ? String(c.reason).trim() : '';
const li = document.createElement('li');
- li.textContent = reason
- ? `Korrektur am ${dateText} ${hoursText} – ${reason}`
- : `Korrektur am ${dateText} ${hoursText}`;
+ li.innerHTML = reason
+ ? `Korrektur am ${dateText} ${hoursDisplay} – ${reason}`
+ : `Korrektur am ${dateText} ${hoursDisplay}`;
correctionsListEl.appendChild(li);
});
diff --git a/views/verwaltung.ejs b/views/verwaltung.ejs
index 3ee1559..00fb0b5 100644
--- a/views/verwaltung.ejs
+++ b/views/verwaltung.ejs
@@ -645,12 +645,16 @@
corrections.forEach(c => {
const dt = parseSqliteDatetime(c.corrected_at);
const dateText = dt ? dt.toLocaleDateString('de-DE') : '';
- const hoursText = formatHoursMin(c.correction_hours);
+ const rawHours = Number(c.correction_hours) || 0;
+ const absHours = Math.abs(rawHours);
+ const signPrefix = rawHours >= 0 ? '+' : '-';
+ const hoursClass = rawHours >= 0 ? 'overtime-positive' : 'overtime-negative';
+ const hoursDisplay = signPrefix + formatHoursMin(absHours);
const reason = (c && c.reason != null) ? String(c.reason).trim() : '';
const li = document.createElement('li');
- li.textContent = reason
- ? `Korrektur am ${dateText} ${hoursText} – ${reason}`
- : `Korrektur am ${dateText} ${hoursText}`;
+ li.innerHTML = reason
+ ? `Korrektur am ${dateText} ${hoursDisplay} – ${reason}`
+ : `Korrektur am ${dateText} ${hoursDisplay}`;
if (listEl) listEl.appendChild(li);
});
} catch (e) {
|