Umstellung auf anzeige x h y min
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const { db } = require('./database');
|
||||
const { getCurrentDate, getCurrentTime, updateTotalHours } = require('./helpers/utils');
|
||||
const { getCurrentDate, getCurrentTime, updateTotalHours, formatHoursMin } = require('./helpers/utils');
|
||||
|
||||
const checkinApp = express();
|
||||
const CHECKIN_PORT = 3334;
|
||||
@@ -139,7 +139,7 @@ checkinApp.get('/api/checkout/:userId', (req, res) => {
|
||||
return sendResponse(req, res, false, { error: 'Fehler beim Aktualisieren', status: 500 });
|
||||
}
|
||||
const successTitle = 'Schönen Feierabend, du wurdest erfolgreich ausgecheckt';
|
||||
const successMessage = `End-Zeit erfasst: ${currentTime}. Gesamtstunden: ${totalHours.toFixed(2)} h`;
|
||||
const successMessage = `End-Zeit erfasst: ${currentTime}. Gesamtstunden: ${formatHoursMin(totalHours)}`;
|
||||
sendResponse(req, res, true, {
|
||||
title: successTitle,
|
||||
message: successMessage,
|
||||
|
||||
@@ -132,6 +132,22 @@ function getWeekStart(dateStr) {
|
||||
return `${year}-${month}-${dayOfMonth}`;
|
||||
}
|
||||
|
||||
// Helper: Dezimalstunden in Anzeige "X h Y min" (z. B. 8.25 -> "8 h 15 min", -1.5 -> "-1 h 30 min")
|
||||
function formatHoursMin(decimalHours) {
|
||||
if (decimalHours == null || !Number.isFinite(Number(decimalHours))) return '0 h 0 min';
|
||||
const n = Number(decimalHours);
|
||||
const sign = n < 0 ? -1 : 1;
|
||||
const absVal = Math.abs(n);
|
||||
let h = Math.floor(absVal);
|
||||
let min = Math.round((absVal - h) * 60);
|
||||
if (min >= 60) {
|
||||
h += 1;
|
||||
min = 0;
|
||||
}
|
||||
const prefix = sign < 0 ? '-' : '';
|
||||
return prefix + h + ' h ' + min + ' min';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hasRole,
|
||||
getDefaultRole,
|
||||
@@ -143,5 +159,6 @@ module.exports = {
|
||||
formatDateTime,
|
||||
getCalendarWeek,
|
||||
getWeekDatesFromCalendarWeek,
|
||||
getWeekStart
|
||||
getWeekStart,
|
||||
formatHoursMin
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ async function loadUserStats() {
|
||||
const currentOvertimeEl = document.getElementById('currentOvertime');
|
||||
if (currentOvertimeEl) {
|
||||
const overtime = stats.currentOvertime || 0;
|
||||
currentOvertimeEl.textContent = overtime >= 0 ? `+${overtime.toFixed(2)}` : overtime.toFixed(2);
|
||||
currentOvertimeEl.textContent = (overtime >= 0 ? '+' : '') + formatHoursMin(overtime);
|
||||
currentOvertimeEl.style.color = overtime >= 0 ? '#27ae60' : '#e74c3c';
|
||||
// Auch die Border-Farbe des Cards anpassen
|
||||
const overtimeCard = currentOvertimeEl.closest('.stat-card');
|
||||
@@ -548,27 +548,27 @@ function renderWeek() {
|
||||
// Stunden-Anzeige für halben Tag Urlaub berechnen
|
||||
let hoursDisplay = '';
|
||||
if (isFullDayVacation) {
|
||||
hoursDisplay = fullDayHours.toFixed(2) + ' h (Urlaub)';
|
||||
hoursDisplay = formatHoursMin(fullDayHours) + ' (Urlaub)';
|
||||
} else if (isHalfDayVacation) {
|
||||
const halfHours = fullDayHours / 2;
|
||||
const workHours = hours || 0; // Das sind die gearbeiteten Stunden (ohne Urlaub)
|
||||
const totalHours = halfHours + workHours;
|
||||
if (workHours > 0.01) {
|
||||
hoursDisplay = totalHours.toFixed(2) + ' h (' + halfHours.toFixed(2) + ' h Urlaub + ' + workHours.toFixed(2) + ' h)';
|
||||
hoursDisplay = formatHoursMin(totalHours) + ' (' + formatHoursMin(halfHours) + ' Urlaub + ' + formatHoursMin(workHours) + ')';
|
||||
} else {
|
||||
hoursDisplay = halfHours.toFixed(2) + ' h (Urlaub)';
|
||||
hoursDisplay = formatHoursMin(halfHours) + ' (Urlaub)';
|
||||
}
|
||||
} else if (isSick) {
|
||||
hoursDisplay = fullDayHours.toFixed(2) + ' h (Krank)';
|
||||
hoursDisplay = formatHoursMin(fullDayHours) + ' (Krank)';
|
||||
} else if (isHoliday && isWeekend) {
|
||||
// Feiertag am Wochenende: keine Tagesarbeitsstunden
|
||||
hoursDisplay = (hours ? hours.toFixed(2) : '0') + ' h (Feiertag)';
|
||||
hoursDisplay = formatHoursMin(hours || 0) + ' (Feiertag)';
|
||||
} else if (isHoliday && !hours) {
|
||||
hoursDisplay = fullDayHours.toFixed(2) + ' h (Feiertag)';
|
||||
hoursDisplay = formatHoursMin(fullDayHours) + ' (Feiertag)';
|
||||
} else if (isHoliday && hours) {
|
||||
hoursDisplay = fullDayHours.toFixed(2) + ' + ' + hours.toFixed(2) + ' h (Überst.)';
|
||||
hoursDisplay = formatHoursMin(fullDayHours) + ' + ' + formatHoursMin(hours) + ' (Überst.)';
|
||||
} else {
|
||||
hoursDisplay = hours.toFixed(2) + ' h';
|
||||
hoursDisplay = formatHoursMin(hours);
|
||||
}
|
||||
|
||||
const requiredBreak = (startTime && endTime) ? calculateRequiredBreakMinutes(startTime, endTime) : null;
|
||||
@@ -741,7 +741,7 @@ function renderWeek() {
|
||||
`;
|
||||
|
||||
document.getElementById('timesheetTable').innerHTML = html;
|
||||
document.getElementById('totalHours').textContent = totalHours.toFixed(2) + ' h';
|
||||
document.getElementById('totalHours').textContent = formatHoursMin(totalHours);
|
||||
|
||||
// Überstunden-Berechnung (startDate und endDate sind bereits oben deklariert)
|
||||
|
||||
@@ -1017,14 +1017,14 @@ function updateOvertimeDisplay() {
|
||||
if (overtimeSummaryItem && overtimeHoursSpan) {
|
||||
overtimeSummaryItem.style.display = 'block';
|
||||
const sign = overtimeHours >= 0 ? '+' : '';
|
||||
overtimeHoursSpan.textContent = `${sign}${overtimeHours.toFixed(2)} h`;
|
||||
overtimeHoursSpan.textContent = (sign === '+' ? '+' : '') + formatHoursMin(overtimeHours);
|
||||
overtimeHoursSpan.style.color = overtimeHours >= 0 ? '#27ae60' : '#e74c3c';
|
||||
}
|
||||
|
||||
// Gesamtstunden-Anzeige aktualisieren
|
||||
const totalHoursElement = document.getElementById('totalHours');
|
||||
if (totalHoursElement) {
|
||||
totalHoursElement.textContent = totalHoursWithVacation.toFixed(2) + ' h';
|
||||
totalHoursElement.textContent = formatHoursMin(totalHoursWithVacation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1272,7 +1272,7 @@ async function saveEntry(input) {
|
||||
if (hoursElement) {
|
||||
if (isFullDayVacation) {
|
||||
// Ganzer Tag Urlaub: Zeige fullDayHours mit "(Urlaub)" Label
|
||||
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Urlaub)';
|
||||
hoursElement.textContent = formatHoursMin(fullDayHours) + ' (Urlaub)';
|
||||
currentEntries[date].total_hours = fullDayHours;
|
||||
} else if (isHalfDayVacation) {
|
||||
// Halber Tag Urlaub: Berechne Stunden aus Start/Ende falls vorhanden
|
||||
@@ -1293,9 +1293,9 @@ async function saveEntry(input) {
|
||||
|
||||
const totalHours = halfHours + workHours;
|
||||
if (workHours > 0) {
|
||||
hoursElement.textContent = totalHours.toFixed(2) + ' h (' + halfHours.toFixed(2) + ' h Urlaub + ' + workHours.toFixed(2) + ' h)';
|
||||
hoursElement.textContent = formatHoursMin(totalHours) + ' (' + formatHoursMin(halfHours) + ' Urlaub + ' + formatHoursMin(workHours) + ')';
|
||||
} else {
|
||||
hoursElement.textContent = halfHours.toFixed(2) + ' h (Urlaub)';
|
||||
hoursElement.textContent = formatHoursMin(halfHours) + ' (Urlaub)';
|
||||
}
|
||||
currentEntries[date].total_hours = totalHours;
|
||||
} else {
|
||||
@@ -1303,15 +1303,15 @@ async function saveEntry(input) {
|
||||
const d = new Date(date);
|
||||
const isWeekendHoliday = isHoliday && (d.getDay() === 6 || d.getDay() === 0);
|
||||
if (isSick) {
|
||||
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Krank)';
|
||||
hoursElement.textContent = formatHoursMin(fullDayHours) + ' (Krank)';
|
||||
} else if (isWeekendHoliday) {
|
||||
hoursElement.textContent = (hours ? hours.toFixed(2) : '0') + ' h (Feiertag)';
|
||||
hoursElement.textContent = formatHoursMin(hours || 0) + ' (Feiertag)';
|
||||
} else if (isHoliday && !hours) {
|
||||
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Feiertag)';
|
||||
hoursElement.textContent = formatHoursMin(fullDayHours) + ' (Feiertag)';
|
||||
} else if (isHoliday && hours) {
|
||||
hoursElement.textContent = fullDayHours.toFixed(2) + ' + ' + hours.toFixed(2) + ' h (Überst.)';
|
||||
hoursElement.textContent = formatHoursMin(fullDayHours) + ' + ' + formatHoursMin(hours) + ' (Überst.)';
|
||||
} else {
|
||||
hoursElement.textContent = hours.toFixed(2) + ' h';
|
||||
hoursElement.textContent = formatHoursMin(hours);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1446,10 +1446,10 @@ async function saveEntry(input) {
|
||||
const isHalfDayVacation = vacationType === 'half';
|
||||
const fullDayHours = getFullDayHours();
|
||||
|
||||
let hoursText = result.total_hours.toFixed(2) + ' h';
|
||||
let hoursText = formatHoursMin(result.total_hours);
|
||||
|
||||
if (isFullDayVacation) {
|
||||
hoursText = fullDayHours.toFixed(2) + ' h (Urlaub)';
|
||||
hoursText = formatHoursMin(fullDayHours) + ' (Urlaub)';
|
||||
} else if (isHalfDayVacation) {
|
||||
// Bei halbem Tag Urlaub: result.total_hours enthält nur die gearbeiteten Stunden
|
||||
// Die Urlaubsstunden müssen addiert werden
|
||||
@@ -1458,25 +1458,25 @@ async function saveEntry(input) {
|
||||
const totalHours = halfHours + workHours; // Gesamt = Urlaub + gearbeitet
|
||||
|
||||
if (workHours > 0.01) {
|
||||
hoursText = totalHours.toFixed(2) + ' h (' + halfHours.toFixed(2) + ' h Urlaub + ' + workHours.toFixed(2) + ' h)';
|
||||
hoursText = formatHoursMin(totalHours) + ' (' + formatHoursMin(halfHours) + ' Urlaub + ' + formatHoursMin(workHours) + ')';
|
||||
} else {
|
||||
hoursText = halfHours.toFixed(2) + ' h (Urlaub)';
|
||||
hoursText = formatHoursMin(halfHours) + ' (Urlaub)';
|
||||
}
|
||||
|
||||
// Aktualisiere currentEntries mit den Gesamtstunden
|
||||
currentEntries[date].total_hours = totalHours;
|
||||
} else if (isSick) {
|
||||
hoursText = fullDayHours.toFixed(2) + ' h (Krank)';
|
||||
hoursText = formatHoursMin(fullDayHours) + ' (Krank)';
|
||||
} else if (isHoliday) {
|
||||
const d = new Date(date);
|
||||
const isWeekendHoliday = (d.getDay() === 6 || d.getDay() === 0);
|
||||
if (isWeekendHoliday) {
|
||||
hoursText = (result.total_hours || 0).toFixed(2) + ' h (Feiertag)';
|
||||
hoursText = formatHoursMin(result.total_hours || 0) + ' (Feiertag)';
|
||||
} else if (result.total_hours <= fullDayHours) {
|
||||
hoursText = fullDayHours.toFixed(2) + ' h (Feiertag)';
|
||||
hoursText = formatHoursMin(fullDayHours) + ' (Feiertag)';
|
||||
} else {
|
||||
const overtime = result.total_hours - fullDayHours;
|
||||
hoursText = fullDayHours.toFixed(2) + ' + ' + overtime.toFixed(2) + ' h (Überst.)';
|
||||
hoursText = formatHoursMin(fullDayHours) + ' + ' + formatHoursMin(overtime) + ' (Überst.)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1492,7 +1492,7 @@ async function saveEntry(input) {
|
||||
Object.values(currentEntries).forEach(e => {
|
||||
totalHours += e.total_hours || 0;
|
||||
});
|
||||
document.getElementById('totalHours').textContent = totalHours.toFixed(2) + ' h';
|
||||
document.getElementById('totalHours').textContent = formatHoursMin(totalHours);
|
||||
|
||||
// Überstunden-Anzeige aktualisieren (bei jeder Änderung)
|
||||
updateOvertimeDisplay();
|
||||
@@ -1591,7 +1591,7 @@ function checkWeekComplete() {
|
||||
if (overtimeValue > fullDayHours) {
|
||||
if (!startTime || !endTime || startTime === '' || endTime === '') {
|
||||
allWeekdaysFilled = false;
|
||||
missingFields.push(formatDateDE(dateStr) + ' (bei Überstunden > ' + fullDayHours.toFixed(2) + 'h müssen Start/Ende vorhanden sein)');
|
||||
missingFields.push(formatDateDE(dateStr) + ' (bei Überstunden > ' + formatHoursMin(fullDayHours) + ' müssen Start/Ende vorhanden sein)');
|
||||
continue; // Weiter zum nächsten Tag
|
||||
}
|
||||
}
|
||||
@@ -2077,22 +2077,22 @@ function toggleSickStatus(dateStr) {
|
||||
|
||||
if (newStatus) {
|
||||
// Krank: Zeige fullDayHours mit "(Krank)" Label
|
||||
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Krank)';
|
||||
hoursElement.textContent = formatHoursMin(fullDayHours) + ' (Krank)';
|
||||
currentEntries[dateStr].total_hours = fullDayHours;
|
||||
} else {
|
||||
// Zurück zu normaler Anzeige basierend auf anderen Status
|
||||
const d = new Date(dateStr);
|
||||
const isWeekendHoliday = isHoliday && (d.getDay() === 6 || d.getDay() === 0);
|
||||
if (isFullDayVacation) {
|
||||
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Urlaub)';
|
||||
hoursElement.textContent = formatHoursMin(fullDayHours) + ' (Urlaub)';
|
||||
} else if (isWeekendHoliday) {
|
||||
hoursElement.textContent = (hours ? hours.toFixed(2) : '0') + ' h (Feiertag)';
|
||||
hoursElement.textContent = formatHoursMin(hours || 0) + ' (Feiertag)';
|
||||
} else if (isHoliday && !hours) {
|
||||
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Feiertag)';
|
||||
hoursElement.textContent = formatHoursMin(fullDayHours) + ' (Feiertag)';
|
||||
} else if (isHoliday && hours) {
|
||||
hoursElement.textContent = fullDayHours.toFixed(2) + ' + ' + hours.toFixed(2) + ' h (Überst.)';
|
||||
hoursElement.textContent = formatHoursMin(fullDayHours) + ' + ' + formatHoursMin(hours) + ' (Überst.)';
|
||||
} else {
|
||||
hoursElement.textContent = hours.toFixed(2) + ' h';
|
||||
hoursElement.textContent = formatHoursMin(hours);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
public/js/format-hours.js
Normal file
19
public/js/format-hours.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// Gleiche Logik wie helpers/utils.js formatHoursMin – für Browser (Dashboard, EJS-Seiten).
|
||||
// Wird global als window.formatHoursMin bereitgestellt.
|
||||
(function () {
|
||||
function formatHoursMin(decimalHours) {
|
||||
if (decimalHours == null || !Number.isFinite(Number(decimalHours))) return '0 h 0 min';
|
||||
var n = Number(decimalHours);
|
||||
var sign = n < 0 ? -1 : 1;
|
||||
var absVal = Math.abs(n);
|
||||
var h = Math.floor(absVal);
|
||||
var min = Math.round((absVal - h) * 60);
|
||||
if (min >= 60) {
|
||||
h += 1;
|
||||
min = 0;
|
||||
}
|
||||
var prefix = sign < 0 ? '-' : '';
|
||||
return prefix + h + ' h ' + min + ' min';
|
||||
}
|
||||
window.formatHoursMin = formatHoursMin;
|
||||
})();
|
||||
@@ -3,7 +3,7 @@
|
||||
const PDFDocument = require('pdfkit');
|
||||
const QRCode = require('qrcode');
|
||||
const { db } = require('../database');
|
||||
const { formatDate, formatDateTime } = require('../helpers/utils');
|
||||
const { formatDate, formatDateTime, formatHoursMin } = require('../helpers/utils');
|
||||
const { getHolidaysWithNamesForDateRange } = require('./feiertage-service');
|
||||
|
||||
// Kalenderwoche berechnen
|
||||
@@ -190,7 +190,7 @@ function generatePDF(timesheetId, req, res) {
|
||||
// Feiertag am Wochenende: keine Tagesarbeitsstunden anzeigen
|
||||
const holidayDay = new Date(row.date + 'T12:00:00').getDay();
|
||||
const isWeekendHoliday = (holidayDay === 0 || holidayDay === 6);
|
||||
const holidayHoursStr = isWeekendHoliday ? '0 h (Feiertag)' : fullDayHours.toFixed(2) + ' h (Feiertag)';
|
||||
const holidayHoursStr = isWeekendHoliday ? '0 h 0 min (Feiertag)' : formatHoursMin(fullDayHours) + ' (Feiertag)';
|
||||
const rowData = [formatDate(row.date), '-', '-', '-', holidayHoursStr];
|
||||
rowData.forEach((data, i) => {
|
||||
doc.text(data, x, y, { width: colWidths[i], align: 'left' });
|
||||
@@ -215,7 +215,7 @@ function generatePDF(timesheetId, req, res) {
|
||||
entry.start_time || '-',
|
||||
entry.end_time || '-',
|
||||
entry.break_minutes ? `${entry.break_minutes} min` : '-',
|
||||
entry.total_hours ? entry.total_hours.toFixed(2) + ' h' : '-'
|
||||
entry.total_hours ? formatHoursMin(entry.total_hours) : '-'
|
||||
];
|
||||
|
||||
rowData.forEach((data, i) => {
|
||||
@@ -250,7 +250,7 @@ function generatePDF(timesheetId, req, res) {
|
||||
if (activity.projectNumber) {
|
||||
activityText += ` (Projekt: ${activity.projectNumber})`;
|
||||
}
|
||||
activityText += ` - ${activity.hours.toFixed(2)} h`;
|
||||
activityText += ` - ${formatHoursMin(activity.hours)}`;
|
||||
doc.fontSize(9).font('Helvetica');
|
||||
doc.text(activityText, 70, doc.y, { width: 360 });
|
||||
doc.moveDown(0.2);
|
||||
@@ -264,7 +264,7 @@ function generatePDF(timesheetId, req, res) {
|
||||
overtimeInfo.push('Feiertag: ' + (holidayNames.get(entry.date) || 'Feiertag'));
|
||||
}
|
||||
if (entry.overtime_taken_hours && parseFloat(entry.overtime_taken_hours) > 0) {
|
||||
overtimeInfo.push(`Überstunden genommen: ${parseFloat(entry.overtime_taken_hours).toFixed(2)} h`);
|
||||
overtimeInfo.push(`Überstunden genommen: ${formatHoursMin(parseFloat(entry.overtime_taken_hours))}`);
|
||||
}
|
||||
if (entry.vacation_type) {
|
||||
const vacationText = entry.vacation_type === 'full' ? 'Ganzer Tag' : 'Halber Tag';
|
||||
@@ -308,7 +308,7 @@ function generatePDF(timesheetId, req, res) {
|
||||
doc.font('Helvetica-Bold');
|
||||
// Gesamtstunden = Arbeitsstunden + Urlaubsstunden + Feiertagsstunden (8h pro Feiertag)
|
||||
const totalHoursWithVacation = totalHours + vacationHours + holidayHours;
|
||||
doc.text(`Gesamtstunden: ${totalHoursWithVacation.toFixed(2)} h`, 50, doc.y);
|
||||
doc.text(`Gesamtstunden: ${formatHoursMin(totalHoursWithVacation)}`, 50, doc.y);
|
||||
|
||||
// Überstunden berechnen und anzeigen
|
||||
const wochenstunden = timesheet.wochenstunden || 0;
|
||||
@@ -318,11 +318,11 @@ function generatePDF(timesheetId, req, res) {
|
||||
doc.moveDown(0.3);
|
||||
doc.font('Helvetica-Bold');
|
||||
if (overtimeHours > 0) {
|
||||
doc.text(`Überstunden: +${overtimeHours.toFixed(2)} h`, 50, doc.y);
|
||||
doc.text(`Überstunden: +${formatHoursMin(overtimeHours)}`, 50, doc.y);
|
||||
} else if (overtimeHours < 0) {
|
||||
doc.text(`Überstunden: ${overtimeHours.toFixed(2)} h`, 50, doc.y);
|
||||
doc.text(`Überstunden: ${formatHoursMin(overtimeHours)}`, 50, doc.y);
|
||||
} else {
|
||||
doc.text(`Überstunden: 0.00 h`, 50, doc.y);
|
||||
doc.text(`Überstunden: ${formatHoursMin(0)}`, 50, doc.y);
|
||||
}
|
||||
|
||||
doc.end();
|
||||
@@ -462,7 +462,7 @@ function generatePDFToBuffer(timesheetId, req) {
|
||||
// Feiertag am Wochenende: keine Tagesarbeitsstunden anzeigen
|
||||
const holidayDay = new Date(row.date + 'T12:00:00').getDay();
|
||||
const isWeekendHoliday = (holidayDay === 0 || holidayDay === 6);
|
||||
const holidayHoursStr = isWeekendHoliday ? '0 h (Feiertag)' : fullDayHoursBuf.toFixed(2) + ' h (Feiertag)';
|
||||
const holidayHoursStr = isWeekendHoliday ? '0 h 0 min (Feiertag)' : formatHoursMin(fullDayHoursBuf) + ' (Feiertag)';
|
||||
const rowDataBuf = [formatDate(row.date), '-', '-', '-', holidayHoursStr];
|
||||
rowDataBuf.forEach((data, i) => {
|
||||
doc.text(data, x, y, { width: colWidths[i], align: 'left' });
|
||||
@@ -487,7 +487,7 @@ function generatePDFToBuffer(timesheetId, req) {
|
||||
entry.start_time || '-',
|
||||
entry.end_time || '-',
|
||||
entry.break_minutes ? `${entry.break_minutes} min` : '-',
|
||||
entry.total_hours ? entry.total_hours.toFixed(2) + ' h' : '-'
|
||||
entry.total_hours ? formatHoursMin(entry.total_hours) : '-'
|
||||
];
|
||||
|
||||
rowData.forEach((data, i) => {
|
||||
@@ -519,7 +519,7 @@ function generatePDFToBuffer(timesheetId, req) {
|
||||
if (activity.projectNumber) {
|
||||
activityText += ` (Projekt: ${activity.projectNumber})`;
|
||||
}
|
||||
activityText += ` - ${activity.hours.toFixed(2)} h`;
|
||||
activityText += ` - ${formatHoursMin(activity.hours)}`;
|
||||
doc.fontSize(9).font('Helvetica');
|
||||
doc.text(activityText, 70, doc.y, { width: 360 });
|
||||
doc.moveDown(0.2);
|
||||
@@ -532,7 +532,7 @@ function generatePDFToBuffer(timesheetId, req) {
|
||||
overtimeInfo.push('Feiertag: ' + (holidayNames.get(entry.date) || 'Feiertag'));
|
||||
}
|
||||
if (entry.overtime_taken_hours && parseFloat(entry.overtime_taken_hours) > 0) {
|
||||
overtimeInfo.push(`Überstunden genommen: ${parseFloat(entry.overtime_taken_hours).toFixed(2)} h`);
|
||||
overtimeInfo.push(`Überstunden genommen: ${formatHoursMin(parseFloat(entry.overtime_taken_hours))}`);
|
||||
}
|
||||
if (entry.vacation_type) {
|
||||
const vacationText = entry.vacation_type === 'full' ? 'Ganzer Tag' : 'Halber Tag';
|
||||
@@ -573,7 +573,7 @@ function generatePDFToBuffer(timesheetId, req) {
|
||||
doc.moveDown(0.5);
|
||||
doc.font('Helvetica-Bold');
|
||||
const totalHoursWithVacation = totalHours + vacationHours + holidayHours;
|
||||
doc.text(`Gesamtstunden: ${totalHoursWithVacation.toFixed(2)} h`, 50, doc.y);
|
||||
doc.text(`Gesamtstunden: ${formatHoursMin(totalHoursWithVacation)}`, 50, doc.y);
|
||||
|
||||
const wochenstunden = timesheet.wochenstunden || 0;
|
||||
const overtimeHours = totalHoursWithVacation - wochenstunden;
|
||||
@@ -581,11 +581,11 @@ function generatePDFToBuffer(timesheetId, req) {
|
||||
doc.moveDown(0.3);
|
||||
doc.font('Helvetica-Bold');
|
||||
if (overtimeHours > 0) {
|
||||
doc.text(`Überstunden: +${overtimeHours.toFixed(2)} h`, 50, doc.y);
|
||||
doc.text(`Überstunden: +${formatHoursMin(overtimeHours)}`, 50, doc.y);
|
||||
} else if (overtimeHours < 0) {
|
||||
doc.text(`Überstunden: ${overtimeHours.toFixed(2)} h`, 50, doc.y);
|
||||
doc.text(`Überstunden: ${formatHoursMin(overtimeHours)}`, 50, doc.y);
|
||||
} else {
|
||||
doc.text(`Überstunden: 0.00 h`, 50, doc.y);
|
||||
doc.text(`Überstunden: ${formatHoursMin(0)}`, 50, doc.y);
|
||||
}
|
||||
|
||||
doc.end();
|
||||
|
||||
@@ -52,11 +52,11 @@
|
||||
<div class="summary">
|
||||
<div class="summary-item">
|
||||
<strong>Gesamtstunden diese Woche:</strong>
|
||||
<span id="totalHours">0.00 h</span>
|
||||
<span id="totalHours">0 h 0 min</span>
|
||||
</div>
|
||||
<div class="summary-item" id="overtimeSummaryItem" style="display: none;">
|
||||
<strong>Überstunden diese Woche:</strong>
|
||||
<span id="overtimeHours">0.00 h</span>
|
||||
<span id="overtimeHours">0 h 0 min</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/format-hours.js"></script>
|
||||
<script src="/js/dashboard.js"></script>
|
||||
<script>
|
||||
// Wochenende-Sektion ein-/ausklappen
|
||||
|
||||
@@ -187,6 +187,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script src="/js/format-hours.js"></script>
|
||||
<script>
|
||||
// Rollenwechsel
|
||||
const roleSwitcher = document.getElementById('roleSwitcher');
|
||||
@@ -231,15 +232,7 @@
|
||||
return new Date(s.replace(' ', 'T') + 'Z');
|
||||
}
|
||||
|
||||
function formatHours(value) {
|
||||
const n = Number(value);
|
||||
if (!Number.isFinite(n)) return '';
|
||||
const sign = n > 0 ? '+' : '';
|
||||
let s = sign + n.toFixed(2);
|
||||
s = s.replace(/\.00$/, '');
|
||||
s = s.replace(/(\.\d)0$/, '$1');
|
||||
return s;
|
||||
}
|
||||
// formatHoursMin aus format-hours.js (window.formatHoursMin)
|
||||
|
||||
let correctionsExpanded = false;
|
||||
function toggleCorrectionsSection() {
|
||||
@@ -286,18 +279,18 @@
|
||||
// Zusammenfassung anzeigen
|
||||
const totalOvertimeEl = document.getElementById('totalOvertime');
|
||||
totalOvertimeEl.textContent =
|
||||
(totalOvertime >= 0 ? '+' : '') + totalOvertime.toFixed(2) + ' h';
|
||||
(totalOvertime >= 0 ? '+' : '') + formatHoursMin(totalOvertime);
|
||||
totalOvertimeEl.className =
|
||||
'summary-value ' + (totalOvertime >= 0 ? 'overtime-positive' : 'overtime-negative');
|
||||
|
||||
const totalOvertimeTakenEl = document.getElementById('totalOvertimeTaken');
|
||||
totalOvertimeTakenEl.textContent =
|
||||
totalOvertimeTaken.toFixed(2) + ' h';
|
||||
formatHoursMin(totalOvertimeTaken);
|
||||
totalOvertimeTakenEl.className = 'summary-value overtime-positive';
|
||||
|
||||
const remainingOvertimeEl = document.getElementById('remainingOvertime');
|
||||
remainingOvertimeEl.textContent =
|
||||
(remainingOvertime >= 0 ? '+' : '') + remainingOvertime.toFixed(2) + ' h';
|
||||
(remainingOvertime >= 0 ? '+' : '') + formatHoursMin(remainingOvertime);
|
||||
remainingOvertimeEl.className =
|
||||
'summary-value ' + (remainingOvertime >= 0 ? 'overtime-positive' : 'overtime-negative');
|
||||
|
||||
@@ -305,7 +298,7 @@
|
||||
const offsetItem = document.getElementById('offsetItem');
|
||||
const offsetValue = document.getElementById('overtimeOffset');
|
||||
if (overtimeOffset !== 0) {
|
||||
offsetValue.textContent = (overtimeOffset >= 0 ? '+' : '') + overtimeOffset.toFixed(2) + ' h';
|
||||
offsetValue.textContent = (overtimeOffset >= 0 ? '+' : '') + formatHoursMin(overtimeOffset);
|
||||
offsetValue.className = 'summary-value ' + (overtimeOffset >= 0 ? 'overtime-positive' : 'overtime-negative');
|
||||
offsetItem.style.display = 'flex';
|
||||
} else {
|
||||
@@ -327,12 +320,12 @@
|
||||
corrections.forEach(c => {
|
||||
const dt = parseSqliteDatetime(c.corrected_at);
|
||||
const dateText = dt ? dt.toLocaleDateString('de-DE') : '';
|
||||
const hoursText = formatHours(c.correction_hours);
|
||||
const hoursText = formatHoursMin(c.correction_hours);
|
||||
const reason = (c && c.reason != null) ? String(c.reason).trim() : '';
|
||||
const li = document.createElement('li');
|
||||
li.textContent = reason
|
||||
? `Korrektur am ${dateText} ${hoursText} h – ${reason}`
|
||||
: `Korrektur am ${dateText} ${hoursText} h`;
|
||||
? `Korrektur am ${dateText} ${hoursText} – ${reason}`
|
||||
: `Korrektur am ${dateText} ${hoursText}`;
|
||||
correctionsListEl.appendChild(li);
|
||||
});
|
||||
|
||||
@@ -361,10 +354,10 @@
|
||||
row.innerHTML = `
|
||||
<td><strong>${week.year} KW${calendarWeekStr}</strong></td>
|
||||
<td>${dateRange}</td>
|
||||
<td>${week.total_hours.toFixed(2)} h</td>
|
||||
<td>${week.soll_stunden.toFixed(2)} h</td>
|
||||
<td class="${overtimeClass}">${overtimeSign}${week.overtime_hours.toFixed(2)} h</td>
|
||||
<td>${week.overtime_taken.toFixed(2)} h</td>
|
||||
<td>${formatHoursMin(week.total_hours)}</td>
|
||||
<td>${formatHoursMin(week.soll_stunden)}</td>
|
||||
<td class="${overtimeClass}">${overtimeSign}${formatHoursMin(week.overtime_hours)}</td>
|
||||
<td>${formatHoursMin(week.overtime_taken)}</td>
|
||||
<td>${week.vacation_days > 0 ? week.vacation_days.toFixed(1) : '-'}</td>
|
||||
`;
|
||||
tableBodyEl.appendChild(row);
|
||||
|
||||
@@ -318,6 +318,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/format-hours.js"></script>
|
||||
<script>
|
||||
async function loadStatsForDiv(statsDiv) {
|
||||
const userId = statsDiv.dataset.userId;
|
||||
@@ -341,13 +342,13 @@
|
||||
const weekOvertimeColor = data.weekOvertimeHours < 0 ? '#dc3545' : (data.weekOvertimeHours > 0 ? '#28a745' : '#666');
|
||||
const sign = data.weekOvertimeHours >= 0 ? '+' : '';
|
||||
statsHTML += `<div class="stats-inline" style="display: inline-block; margin-right: 20px;">
|
||||
<strong>Überstunden:</strong> <span style="color: ${weekOvertimeColor};">${sign}${data.weekOvertimeHours.toFixed(2)} h</span>
|
||||
<strong>Überstunden:</strong> <span style="color: ${weekOvertimeColor};">${sign}${formatHoursMin(data.weekOvertimeHours)}</span>
|
||||
</div>`;
|
||||
}
|
||||
if (data.overtimeOffsetHours !== undefined && data.overtimeOffsetHours !== 0) {
|
||||
statsHTML += `<div class="stats-inline" style="display: inline-block; margin-right: 20px;">
|
||||
<strong>Offset:</strong> <span>${Number(data.overtimeOffsetHours).toFixed(2)} h</span>
|
||||
${data.remainingOvertimeWithOffset !== undefined ? `<span style="color: #28a745;">(verbleibend inkl. Offset: ${Number(data.remainingOvertimeWithOffset).toFixed(2)} h)</span>` : ''}
|
||||
<strong>Offset:</strong> <span>${formatHoursMin(Number(data.overtimeOffsetHours))}</span>
|
||||
${data.remainingOvertimeWithOffset !== undefined ? `<span style="color: #28a745;">(verbleibend inkl. Offset: ${formatHoursMin(Number(data.remainingOvertimeWithOffset))})</span>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
if (data.totalVacationDays !== undefined || data.vacationDays !== undefined) {
|
||||
@@ -513,15 +514,7 @@
|
||||
return new Date(s.replace(' ', 'T') + 'Z');
|
||||
}
|
||||
|
||||
function formatHours(value) {
|
||||
const n = Number(value);
|
||||
if (!Number.isFinite(n)) return '';
|
||||
const sign = n > 0 ? '+' : '';
|
||||
let s = sign + n.toFixed(2);
|
||||
s = s.replace(/\.00$/, '');
|
||||
s = s.replace(/(\.\d)0$/, '$1');
|
||||
return s;
|
||||
}
|
||||
// formatHoursMin aus format-hours.js (window.formatHoursMin)
|
||||
|
||||
function showOvertimeCorrectionReasonModal(opts) {
|
||||
const title = opts && opts.title ? String(opts.title) : 'Grund für die Korrektur';
|
||||
@@ -652,12 +645,12 @@
|
||||
corrections.forEach(c => {
|
||||
const dt = parseSqliteDatetime(c.corrected_at);
|
||||
const dateText = dt ? dt.toLocaleDateString('de-DE') : '';
|
||||
const hoursText = formatHours(c.correction_hours);
|
||||
const hoursText = formatHoursMin(c.correction_hours);
|
||||
const reason = (c && c.reason != null) ? String(c.reason).trim() : '';
|
||||
const li = document.createElement('li');
|
||||
li.textContent = reason
|
||||
? `Korrektur am ${dateText} ${hoursText} h – ${reason}`
|
||||
: `Korrektur am ${dateText} ${hoursText} h`;
|
||||
? `Korrektur am ${dateText} ${hoursText} – ${reason}`
|
||||
: `Korrektur am ${dateText} ${hoursText}`;
|
||||
if (listEl) listEl.appendChild(li);
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -720,7 +713,7 @@
|
||||
// Modal: Grund ist Pflicht
|
||||
showOvertimeCorrectionReasonModal({
|
||||
title: 'Grund für die Überstunden-Korrektur',
|
||||
prompt: `Korrektur: ${value > 0 ? '+' : ''}${value} h`,
|
||||
prompt: `Korrektur: ${value >= 0 ? '+' : ''}${formatHoursMin(value)}`,
|
||||
onCancel: () => {
|
||||
delete this.dataset.modalOpen;
|
||||
this.disabled = false;
|
||||
|
||||
Reference in New Issue
Block a user