feat: enhance timer functionality with manual time entry and additional metrics display
All checks were successful
Build and Push Docker Image / build (push) Successful in 25s

This commit is contained in:
Felix Schlusche
2025-10-30 17:02:03 +01:00
parent 282aaac8ae
commit 17906c76f2
2 changed files with 185 additions and 26 deletions

View File

@@ -373,13 +373,32 @@
<!-- Start/Stop Timer Section --> <!-- Start/Stop Timer Section -->
<div class="mb-6 p-6 glass-card rounded-xl"> <div class="mb-6 p-6 glass-card rounded-xl">
<div class="flex flex-wrap items-center justify-between gap-4"> <div class="flex flex-wrap items-center justify-between gap-4">
<div> <div class="flex-1">
<div class="text-sm text-gray-400 mb-2 font-medium">Heutige Arbeitszeit</div> <div class="text-sm text-gray-400 mb-2 font-medium">Heutige Arbeitszeit</div>
<div id="timerDisplay" class="text-5xl font-bold text-white">00:00:00</div> <div id="timerDisplay" class="text-5xl font-bold text-white">00:00:00</div>
<button id="timerStatus" class="text-sm text-blue-400 hover:text-blue-300 mt-2 underline cursor-pointer transition-all" title="Startzeit manuell eingeben">
Nicht gestartet <!-- Manual time entry link (shown when timer is not running) -->
<button id="manualTimeLink" class="text-sm text-blue-400 hover:text-blue-300 mt-2 underline cursor-pointer transition-all" title="Startzeit manuell eingeben">
Startzeit manuell eingeben
</button> </button>
<input type="text" id="manualStartTimeInput" class="hidden"> <input type="text" id="manualStartTimeInput" class="hidden">
<!-- Additional Timer Metrics -->
<div id="timerMetrics" class="hidden mt-4 space-y-1 text-sm">
<button id="timerStatus" class="flex items-center gap-2 text-gray-300 hover:text-gray-100 cursor-pointer transition-all text-left w-full" title="Startzeit manuell eingeben">
<i data-lucide="clock" class="w-4 h-4 text-yellow-400"></i>
<span id="timerStatusText">Nicht gestartet</span>
</button>
<div class="flex items-center gap-2 text-gray-300">
<i data-lucide="target" class="w-4 h-4 text-green-400"></i>
<span>Soll erreicht: <span id="targetReachedTime" class="font-semibold text-white">--:--</span> (8h)</span>
</div>
<div class="flex items-center gap-2 text-gray-300">
<i data-lucide="timer" class="w-4 h-4 text-blue-400"></i>
<span>Zeit bis Soll: <span id="timeUntilTarget" class="font-semibold text-white">--:--</span></span>
</div>
</div>
</div> </div>
<div class="flex gap-3"> <div class="flex gap-3">
<button id="btnStartWork" <button id="btnStartWork"
@@ -638,6 +657,7 @@
<th class="px-4 py-4 text-left text-xs font-semibold text-gray-300 uppercase tracking-wider">Ende</th> <th class="px-4 py-4 text-left text-xs font-semibold text-gray-300 uppercase tracking-wider">Ende</th>
<th class="px-4 py-4 text-left text-xs font-semibold text-gray-300 uppercase tracking-wider">Pause</th> <th class="px-4 py-4 text-left text-xs font-semibold text-gray-300 uppercase tracking-wider">Pause</th>
<th class="px-4 py-4 text-left text-xs font-semibold text-gray-300 uppercase tracking-wider">Netto</th> <th class="px-4 py-4 text-left text-xs font-semibold text-gray-300 uppercase tracking-wider">Netto</th>
<th class="px-4 py-4 text-left text-xs font-semibold text-gray-300 uppercase tracking-wider">Saldo</th>
<th class="px-4 py-4 text-center text-xs font-semibold text-gray-300 uppercase tracking-wider">Ort</th> <th class="px-4 py-4 text-center text-xs font-semibold text-gray-300 uppercase tracking-wider">Ort</th>
<th class="px-4 py-4 text-center text-xs font-semibold text-gray-300 uppercase tracking-wider">Actions</th> <th class="px-4 py-4 text-center text-xs font-semibold text-gray-300 uppercase tracking-wider">Actions</th>
</tr> </tr>

View File

@@ -84,6 +84,29 @@ function handleNextMonth() {
// TIMER FUNCTIONS // TIMER FUNCTIONS
// ============================================ // ============================================
/**
* Update timer status text and show/hide metrics
*/
function setTimerStatus(text, showMetrics = false) {
const statusTextEl = document.getElementById('timerStatusText');
const metricsEl = document.getElementById('timerMetrics');
const manualTimeLink = document.getElementById('manualTimeLink');
if (statusTextEl) {
statusTextEl.textContent = text;
}
if (metricsEl) {
if (showMetrics) {
metricsEl.classList.remove('hidden');
if (manualTimeLink) manualTimeLink.classList.add('hidden');
} else {
metricsEl.classList.add('hidden');
if (manualTimeLink) manualTimeLink.classList.remove('hidden');
}
}
}
/** /**
* Check if timer is running (on page load) * Check if timer is running (on page load)
*/ */
@@ -114,9 +137,7 @@ async function checkRunningTimer() {
stopBtn.disabled = false; stopBtn.disabled = false;
stopBtn.classList.remove('opacity-50', 'cursor-not-allowed'); stopBtn.classList.remove('opacity-50', 'cursor-not-allowed');
stopBtn.classList.add('hover:bg-red-700'); stopBtn.classList.add('hover:bg-red-700');
document.getElementById('timerStatus').textContent = 'Läuft seit ' + todayEntry.startTime; setTimerStatus('Läuft seit ' + todayEntry.startTime, true);
document.getElementById('timerStatus').classList.remove('cursor-pointer', 'underline', 'hover:text-blue-300');
document.getElementById('timerStatus').classList.add('cursor-default');
// Calculate elapsed time and check for active pauses // Calculate elapsed time and check for active pauses
const elapsed = Date.now() - timerStartTime; const elapsed = Date.now() - timerStartTime;
@@ -135,7 +156,7 @@ async function checkRunningTimer() {
timerPausedDuration = 0; timerPausedDuration = 0;
const remainingPause = (sixHoursSeconds + thirtyMinutes) - elapsedSeconds; const remainingPause = (sixHoursSeconds + thirtyMinutes) - elapsedSeconds;
pauseEndTime = Date.now() + (remainingPause * 1000); pauseEndTime = Date.now() + (remainingPause * 1000);
document.getElementById('timerStatus').textContent = `Läuft seit ${todayEntry.startTime} - Pause (${Math.ceil(remainingPause / 60)} Min)`; setTimerStatus(`Läuft seit ${todayEntry.startTime} - Pause (${Math.ceil(remainingPause / 60)} Min)`, true);
// Schedule end of pause // Schedule end of pause
pauseTimeout = setTimeout(() => { pauseTimeout = setTimeout(() => {
@@ -143,7 +164,7 @@ async function checkRunningTimer() {
isPaused = false; isPaused = false;
pauseStartElapsed = 0; pauseStartElapsed = 0;
pauseEndTime = 0; pauseEndTime = 0;
document.getElementById('timerStatus').textContent = 'Läuft seit ' + todayEntry.startTime; setTimerStatus('Läuft seit ' + todayEntry.startTime, true);
}, remainingPause * 1000); }, remainingPause * 1000);
} }
// Check if in 9-hour pause (9h30m to 9h45m real time = 9h to 9h work time) // Check if in 9-hour pause (9h30m to 9h45m real time = 9h to 9h work time)
@@ -153,7 +174,7 @@ async function checkRunningTimer() {
timerPausedDuration = thirtyMinutes; timerPausedDuration = thirtyMinutes;
const remainingPause = (nineHoursSeconds + thirtyMinutes + fifteenMinutes) - elapsedSeconds; const remainingPause = (nineHoursSeconds + thirtyMinutes + fifteenMinutes) - elapsedSeconds;
pauseEndTime = Date.now() + (remainingPause * 1000); pauseEndTime = Date.now() + (remainingPause * 1000);
document.getElementById('timerStatus').textContent = `Läuft seit ${todayEntry.startTime} - Pause (${Math.ceil(remainingPause / 60)} Min)`; setTimerStatus(`Läuft seit ${todayEntry.startTime} - Pause (${Math.ceil(remainingPause / 60)} Min)`, true);
// Schedule end of pause // Schedule end of pause
pauseTimeout = setTimeout(() => { pauseTimeout = setTimeout(() => {
@@ -161,7 +182,7 @@ async function checkRunningTimer() {
isPaused = false; isPaused = false;
pauseStartElapsed = 0; pauseStartElapsed = 0;
pauseEndTime = 0; pauseEndTime = 0;
document.getElementById('timerStatus').textContent = 'Läuft seit ' + todayEntry.startTime; setTimerStatus('Läuft seit ' + todayEntry.startTime, true);
}, remainingPause * 1000); }, remainingPause * 1000);
} }
// Not in pause, but may have completed pauses // Not in pause, but may have completed pauses
@@ -214,9 +235,7 @@ async function startWork() {
stopBtn.disabled = false; stopBtn.disabled = false;
stopBtn.classList.remove('opacity-50', 'cursor-not-allowed'); stopBtn.classList.remove('opacity-50', 'cursor-not-allowed');
stopBtn.classList.add('hover:bg-red-700'); stopBtn.classList.add('hover:bg-red-700');
document.getElementById('timerStatus').textContent = 'Läuft seit ' + startTime; setTimerStatus('Läuft seit ' + startTime, true);
document.getElementById('timerStatus').classList.remove('cursor-pointer', 'underline', 'hover:text-blue-300');
document.getElementById('timerStatus').classList.add('cursor-default');
// Start timer interval // Start timer interval
timerInterval = setInterval(updateTimer, 1000); timerInterval = setInterval(updateTimer, 1000);
@@ -269,9 +288,7 @@ async function stopWork() {
stopBtn.classList.add('opacity-50', 'cursor-not-allowed'); stopBtn.classList.add('opacity-50', 'cursor-not-allowed');
stopBtn.classList.remove('hover:bg-red-700'); stopBtn.classList.remove('hover:bg-red-700');
document.getElementById('timerDisplay').textContent = '00:00:00'; document.getElementById('timerDisplay').textContent = '00:00:00';
document.getElementById('timerStatus').textContent = 'Nicht gestartet'; setTimerStatus('Nicht gestartet', false);
document.getElementById('timerStatus').classList.add('cursor-pointer', 'underline', 'hover:text-blue-300');
document.getElementById('timerStatus').classList.remove('cursor-default');
// Reload monthly view // Reload monthly view
loadMonthlyView(); loadMonthlyView();
@@ -293,7 +310,7 @@ function updateTimer() {
// Update pause countdown live // Update pause countdown live
const remainingSeconds = Math.max(0, Math.ceil((pauseEndTime - now) / 1000)); const remainingSeconds = Math.max(0, Math.ceil((pauseEndTime - now) / 1000));
const remainingMinutes = Math.ceil(remainingSeconds / 60); const remainingMinutes = Math.ceil(remainingSeconds / 60);
document.getElementById('timerStatus').textContent = `Läuft seit ${timerStartTimeString} - Pause (${remainingMinutes} Min)`; setTimerStatus(`Läuft seit ${timerStartTimeString} - Pause (${remainingMinutes} Min)`, true);
} else { } else {
elapsed = Math.floor((now - timerStartTime) / 1000) - timerPausedDuration; elapsed = Math.floor((now - timerStartTime) / 1000) - timerPausedDuration;
@@ -308,6 +325,9 @@ function updateTimer() {
document.getElementById('timerDisplay').textContent = formatDuration(elapsed); document.getElementById('timerDisplay').textContent = formatDuration(elapsed);
// Calculate and display additional timer metrics
updateTimerMetrics(elapsed);
// Update live net hours in the table for current day // Update live net hours in the table for current day
const netHoursCell = document.getElementById('current-day-net-hours'); const netHoursCell = document.getElementById('current-day-net-hours');
if (netHoursCell) { if (netHoursCell) {
@@ -315,6 +335,21 @@ function updateTimer() {
netHoursCell.textContent = netHours.toFixed(2); netHoursCell.textContent = netHours.toFixed(2);
} }
// Update live balance in the table for current day
const balanceCell = document.getElementById('current-day-balance');
if (balanceCell) {
const netHours = elapsed / 3600; // Convert seconds to hours
const deviation = netHours - 8.0;
const baseBalance = parseFloat(balanceCell.dataset.baseBalance || 0);
const currentBalance = baseBalance + deviation;
const balanceColor = currentBalance >= 0 ? 'text-green-400' : 'text-red-400';
const balanceSign = currentBalance >= 0 ? '+' : '';
balanceCell.className = `px-4 py-3 whitespace-nowrap text-sm font-semibold ${balanceColor}`;
balanceCell.textContent = `${balanceSign}${currentBalance.toFixed(2)}h`;
}
// Update live pause minutes in the table for current day // Update live pause minutes in the table for current day
const pauseCell = document.getElementById('current-day-pause'); const pauseCell = document.getElementById('current-day-pause');
if (pauseCell) { if (pauseCell) {
@@ -342,6 +377,59 @@ function updateTimer() {
} }
} }
/**
* Calculate and update additional timer metrics
*/
function updateTimerMetrics(netElapsedSeconds) {
const targetReachedTimeSpan = document.getElementById('targetReachedTime');
const timeUntilTargetSpan = document.getElementById('timeUntilTarget');
if (!timerStartTime) {
return;
}
// Target: 8 hours (28800 seconds)
const targetSeconds = 8 * 60 * 60;
// Calculate total pause time: 30 min after 6h + 15 min after 9h
const pauseDuration30Min = 30 * 60; // 30 minutes in seconds
const pauseDuration15Min = 15 * 60; // 15 minutes in seconds
// Time needed including pauses
// After 6h work -> 30 min pause
// After 9h work -> 15 min pause
// Total gross time = 8h work + 30min pause + 15min pause = 8h 45min
const totalGrossTimeNeeded = targetSeconds + pauseDuration30Min + pauseDuration15Min;
// Calculate when target will be reached (clock time)
const targetReachedTimestamp = new Date(timerStartTime + totalGrossTimeNeeded * 1000);
const targetHours = String(targetReachedTimestamp.getHours()).padStart(2, '0');
const targetMinutes = String(targetReachedTimestamp.getMinutes()).padStart(2, '0');
targetReachedTimeSpan.textContent = `${targetHours}:${targetMinutes}`;
// Calculate countdown to target (remaining net work time)
const remainingNetSeconds = Math.max(0, targetSeconds - netElapsedSeconds);
if (remainingNetSeconds === 0) {
timeUntilTargetSpan.textContent = '00:00:00';
timeUntilTargetSpan.classList.add('text-green-400');
} else {
// Format as HH:MM:SS
const hours = Math.floor(remainingNetSeconds / 3600);
const minutes = Math.floor((remainingNetSeconds % 3600) / 60);
const seconds = remainingNetSeconds % 60;
timeUntilTargetSpan.textContent =
`${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
timeUntilTargetSpan.classList.remove('text-green-400');
}
// Reinitialize Lucide icons for the new metrics
if (typeof lucide !== 'undefined' && lucide.createIcons) {
lucide.createIcons();
}
}
/** /**
* Schedule automatic pauses at 6h and 9h with offset for existing elapsed time * Schedule automatic pauses at 6h and 9h with offset for existing elapsed time
*/ */
@@ -384,7 +472,7 @@ function pauseTimer(durationSeconds) {
timerPausedDuration += durationSeconds; timerPausedDuration += durationSeconds;
isPaused = false; isPaused = false;
pauseEndTime = 0; pauseEndTime = 0;
document.getElementById('timerStatus').textContent = 'Läuft seit ' + timerStartTimeString; setTimerStatus('Läuft seit ' + timerStartTimeString, true);
}, durationSeconds * 1000); }, durationSeconds * 1000);
} }
@@ -497,6 +585,9 @@ function renderEntries(entries) {
emptyState.classList.add('hidden'); emptyState.classList.add('hidden');
// Running balance accumulator
let runningBalance = 0;
entries.forEach(entry => { entries.forEach(entry => {
const row = document.createElement('tr'); const row = document.createElement('tr');
const dateObj = new Date(entry.date + 'T00:00:00'); const dateObj = new Date(entry.date + 'T00:00:00');
@@ -529,6 +620,22 @@ function renderEntries(entries) {
</td> </td>
` : '<td class="hidden"></td>'; ` : '<td class="hidden"></td>';
// Calculate balance: for work and flextime entries (excluding vacation)
const entryType = entry.entryType || 'work';
let balanceCell = '';
if (entryType !== 'vacation') {
// For all workdays (including flextime): Actual - Target (8h)
// Flextime has netHours = 0, so deviation will be -8h
const deviation = entry.netHours - 8.0;
runningBalance += deviation;
const balanceColor = runningBalance >= 0 ? 'text-green-400' : 'text-red-400';
const balanceSign = runningBalance >= 0 ? '+' : '';
balanceCell = `<td class="px-6 py-4 whitespace-nowrap text-sm font-semibold ${balanceColor}">${balanceSign}${runningBalance.toFixed(2)}h</td>`;
} else {
balanceCell = '<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">-</td>';
}
row.innerHTML = checkboxCell + ` row.innerHTML = checkboxCell + `
<td class="px-2 py-4 whitespace-nowrap text-sm font-medium ${weekend ? 'text-blue-400' : 'text-gray-100'}">${dayOfWeek}</td> <td class="px-2 py-4 whitespace-nowrap text-sm font-medium ${weekend ? 'text-blue-400' : 'text-gray-100'}">${dayOfWeek}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-100">${formatDateDisplay(entry.date)}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-100">${formatDateDisplay(entry.date)}</td>
@@ -545,6 +652,7 @@ function renderEntries(entries) {
${entry.pauseMinutes} ${entry.pauseMinutes}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-semibold text-gray-100">${entry.netHours.toFixed(2)}</td> <td class="px-6 py-4 whitespace-nowrap text-sm font-semibold text-gray-100">${entry.netHours.toFixed(2)}</td>
${balanceCell}
<td class="px-6 py-4 whitespace-nowrap text-sm text-center text-gray-100"> <td class="px-6 py-4 whitespace-nowrap text-sm text-center text-gray-100">
<span class="text-lg">${locationIcon}</span> ${locationText} <span class="text-lg">${locationIcon}</span> ${locationText}
</td> </td>
@@ -618,6 +726,9 @@ function renderMonthlyView(entries) {
entriesMap[entry.date] = entry; entriesMap[entry.date] = entry;
}); });
// Running balance accumulator
let runningBalance = 0;
// Render all days from 1st to lastDay // Render all days from 1st to lastDay
for (let day = 1; day <= lastDay; day++) { for (let day = 1; day <= lastDay; day++) {
const dateObj = new Date(displayYear, displayMonth, day); const dateObj = new Date(displayYear, displayMonth, day);
@@ -707,11 +818,33 @@ function renderMonthlyView(entries) {
</td> </td>
` : '<td class="hidden"></td>'; ` : '<td class="hidden"></td>';
// Calculate balance: only for past days (excluding vacation)
const isPastDay = dateObj < today || (dateObj.getFullYear() === todayYear && dateObj.getMonth() === todayMonth && day <= todayDay);
let balanceCell = '';
if (isPastDay && entryType !== 'vacation') {
// For all workdays (including flextime): Actual - Target (8h)
// Flextime has netHours = 0, so deviation will be -8h
const deviation = entry.netHours - 8.0;
runningBalance += deviation;
const balanceColor = runningBalance >= 0 ? 'text-green-400' : 'text-red-400';
const balanceSign = runningBalance >= 0 ? '+' : '';
// Add ID for current day to enable live updates
const balanceId = isToday && entryType === 'work' ? 'id="current-day-balance"' : '';
const balanceDataAttr = isToday && entryType === 'work' ? `data-base-balance="${runningBalance - deviation}"` : '';
balanceCell = `<td class="px-4 py-3 whitespace-nowrap text-sm font-semibold ${balanceColor}" ${balanceId} ${balanceDataAttr}>${balanceSign}${runningBalance.toFixed(2)}h</td>`;
} else {
balanceCell = '<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">-</td>';
}
row.innerHTML = checkboxCell + ` row.innerHTML = checkboxCell + `
<td class="px-2 py-3 whitespace-nowrap text-sm font-medium ${weekend ? 'text-blue-400' : 'text-gray-100'}">${dayOfWeek}</td> <td class="px-2 py-3 whitespace-nowrap text-sm font-medium ${weekend ? 'text-blue-400' : 'text-gray-100'}">${dayOfWeek}</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-100">${formatDateDisplay(entry.date)}</td> <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-100">${formatDateDisplay(entry.date)}</td>
${displayTimes} ${displayTimes}
<td class="px-4 py-3 whitespace-nowrap text-sm font-semibold text-gray-100" id="${isToday ? 'current-day-net-hours' : ''}">${entry.netHours.toFixed(2)}</td> <td class="px-4 py-3 whitespace-nowrap text-sm font-semibold text-gray-100" id="${isToday ? 'current-day-net-hours' : ''}">${entry.netHours.toFixed(2)}</td>
${balanceCell}
<td class="px-4 py-3 whitespace-nowrap text-sm text-center text-gray-100"> <td class="px-4 py-3 whitespace-nowrap text-sm text-center text-gray-100">
<span class="text-lg">${displayIcon}</span> ${displayText} <span class="text-lg">${displayIcon}</span> ${displayText}
</td> </td>
@@ -754,7 +887,7 @@ function renderMonthlyView(entries) {
data-date="${dateISO}" ${selectedEntries.has(dateISO) ? 'checked' : ''}> data-date="${dateISO}" ${selectedEntries.has(dateISO) ? 'checked' : ''}>
</td> </td>
` : '<td class="hidden"></td>'; ` : '<td class="hidden"></td>';
const colspan = bulkEditMode ? '5' : '5'; const colspan = bulkEditMode ? '6' : '6';
row.innerHTML = checkboxCell + ` row.innerHTML = checkboxCell + `
<td class="px-2 py-3 whitespace-nowrap text-sm font-medium ${weekend ? 'text-blue-400' : 'text-gray-100'}">${dayOfWeek}</td> <td class="px-2 py-3 whitespace-nowrap text-sm font-medium ${weekend ? 'text-blue-400' : 'text-gray-100'}">${dayOfWeek}</td>
@@ -3577,9 +3710,7 @@ async function handleManualStartTime(timeStr) {
stopBtn.disabled = false; stopBtn.disabled = false;
stopBtn.classList.remove('opacity-50', 'cursor-not-allowed'); stopBtn.classList.remove('opacity-50', 'cursor-not-allowed');
stopBtn.classList.add('hover:bg-red-700'); stopBtn.classList.add('hover:bg-red-700');
document.getElementById('timerStatus').textContent = 'Läuft seit ' + timeStr; setTimerStatus('Läuft seit ' + timeStr, true);
document.getElementById('timerStatus').classList.remove('cursor-pointer', 'underline', 'hover:text-blue-300');
document.getElementById('timerStatus').classList.add('cursor-default');
// Calculate elapsed time and check for active pauses // Calculate elapsed time and check for active pauses
const elapsed = now.getTime() - startDate.getTime(); const elapsed = now.getTime() - startDate.getTime();
@@ -3598,7 +3729,7 @@ async function handleManualStartTime(timeStr) {
timerPausedDuration = 0; timerPausedDuration = 0;
const remainingPause = (sixHoursSeconds + thirtyMinutes) - elapsedSeconds; const remainingPause = (sixHoursSeconds + thirtyMinutes) - elapsedSeconds;
pauseEndTime = Date.now() + (remainingPause * 1000); pauseEndTime = Date.now() + (remainingPause * 1000);
document.getElementById('timerStatus').textContent = `Läuft seit ${timeStr} - Pause (${Math.ceil(remainingPause / 60)} Min)`; setTimerStatus(`Läuft seit ${timeStr} - Pause (${Math.ceil(remainingPause / 60)} Min)`, true);
// Schedule end of pause // Schedule end of pause
pauseTimeout = setTimeout(() => { pauseTimeout = setTimeout(() => {
@@ -3606,7 +3737,7 @@ async function handleManualStartTime(timeStr) {
isPaused = false; isPaused = false;
pauseStartElapsed = 0; pauseStartElapsed = 0;
pauseEndTime = 0; pauseEndTime = 0;
document.getElementById('timerStatus').textContent = 'Läuft seit ' + timeStr; setTimerStatus('Läuft seit ' + timeStr, true);
}, remainingPause * 1000); }, remainingPause * 1000);
} }
// Check if in 9-hour pause (9h30m to 9h45m real time = 9h to 9h work time) // Check if in 9-hour pause (9h30m to 9h45m real time = 9h to 9h work time)
@@ -3616,7 +3747,7 @@ async function handleManualStartTime(timeStr) {
timerPausedDuration = thirtyMinutes; timerPausedDuration = thirtyMinutes;
const remainingPause = (nineHoursSeconds + thirtyMinutes + fifteenMinutes) - elapsedSeconds; const remainingPause = (nineHoursSeconds + thirtyMinutes + fifteenMinutes) - elapsedSeconds;
pauseEndTime = Date.now() + (remainingPause * 1000); pauseEndTime = Date.now() + (remainingPause * 1000);
document.getElementById('timerStatus').textContent = `Läuft seit ${timeStr} - Pause (${Math.ceil(remainingPause / 60)} Min)`; setTimerStatus(`Läuft seit ${timeStr} - Pause (${Math.ceil(remainingPause / 60)} Min)`, true);
// Schedule end of pause // Schedule end of pause
pauseTimeout = setTimeout(() => { pauseTimeout = setTimeout(() => {
@@ -3624,7 +3755,7 @@ async function handleManualStartTime(timeStr) {
isPaused = false; isPaused = false;
pauseStartElapsed = 0; pauseStartElapsed = 0;
pauseEndTime = 0; pauseEndTime = 0;
document.getElementById('timerStatus').textContent = 'Läuft seit ' + timeStr; setTimerStatus('Läuft seit ' + timeStr, true);
}, remainingPause * 1000); }, remainingPause * 1000);
} }
// Not in pause, but may have completed pauses // Not in pause, but may have completed pauses
@@ -3719,6 +3850,14 @@ function initializeEventListeners() {
} }
}); });
// Manual time link - opens time picker when timer is not running
document.getElementById('manualTimeLink').addEventListener('click', () => {
// Show custom time picker modal
document.getElementById('manualTimePickerModal').classList.remove('hidden');
// Set default time to 09:00
manualStartTimePicker.setDate('09:00', false);
});
// Manual time picker - Confirm button // Manual time picker - Confirm button
document.getElementById('btnConfirmManualTime').addEventListener('click', async () => { document.getElementById('btnConfirmManualTime').addEventListener('click', async () => {
const timeInput = document.getElementById('manualTimeInput'); const timeInput = document.getElementById('manualTimeInput');