This commit is contained in:
2026-03-10 14:39:11 +01:00
parent bf578a8d87
commit d4a544164b
7 changed files with 217 additions and 51 deletions

View File

@@ -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() {
<td colspan="6" class="activities-cell">
<div class="activities-form">
<div class="activities-header"><strong>Tätigkeiten:</strong></div>
${activities.map((activity, idx) => `
${activities.map((activity, idx) => {
const timeParts = decimalHoursToParts(activity.hours);
return `
<div class="activity-row">
<div class="activity-desc">
<input type="text"
@@ -626,22 +674,36 @@ function renderWeek() {
class="activity-project-input">
</div>
<div class="activity-hours">
<input type="number"
<input type="text"
data-date="${dateStr}"
data-field="activity${idx + 1}_hours"
value="${activity.hours > 0 ? activity.hours.toFixed(2) : ''}"
min="0"
step="0.25"
placeholder="0.00"
data-field="activity${idx + 1}_hours_hh"
value="${timeParts.hh}"
inputmode="numeric"
pattern="[0-9]*"
placeholder="hh"
${timeFieldsDisabled} ${disabled}
onblur="saveEntry(this)"
oninput="updateOvertimeDisplay();"
onchange="updateOvertimeDisplay();"
class="activity-hours-input">
class="activity-hours-input activity-hours-hh-input">
<span class="activity-hours-label">h</span>
<input type="text"
data-date="${dateStr}"
data-field="activity${idx + 1}_hours_mm"
value="${timeParts.mm}"
inputmode="numeric"
pattern="[0-9]*"
placeholder="mm"
${timeFieldsDisabled} ${disabled}
onblur="saveEntry(this)"
oninput="updateOvertimeDisplay();"
onchange="updateOvertimeDisplay();"
class="activity-hours-input activity-hours-mm-input">
<span class="activity-hours-label">min</span>
</div>
</div>
`).join('')}
`;
}).join('')}
</div>
<div class="overtime-vacation-controls" style="margin-top: 15px; display: flex; gap: 15px; align-items: center;">
<div class="overtime-control">
@@ -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) {