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
All checks were successful
Build and Push Docker Image / build (push) Successful in 25s
This commit is contained in:
@@ -373,13 +373,32 @@
|
||||
<!-- Start/Stop Timer Section -->
|
||||
<div class="mb-6 p-6 glass-card rounded-xl">
|
||||
<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 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>
|
||||
|
||||
<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 class="flex gap-3">
|
||||
<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">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">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">Actions</th>
|
||||
</tr>
|
||||
|
||||
@@ -84,6 +84,29 @@ function handleNextMonth() {
|
||||
// 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)
|
||||
*/
|
||||
@@ -114,9 +137,7 @@ async function checkRunningTimer() {
|
||||
stopBtn.disabled = false;
|
||||
stopBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||
stopBtn.classList.add('hover:bg-red-700');
|
||||
document.getElementById('timerStatus').textContent = 'Läuft seit ' + todayEntry.startTime;
|
||||
document.getElementById('timerStatus').classList.remove('cursor-pointer', 'underline', 'hover:text-blue-300');
|
||||
document.getElementById('timerStatus').classList.add('cursor-default');
|
||||
setTimerStatus('Läuft seit ' + todayEntry.startTime, true);
|
||||
|
||||
// Calculate elapsed time and check for active pauses
|
||||
const elapsed = Date.now() - timerStartTime;
|
||||
@@ -135,7 +156,7 @@ async function checkRunningTimer() {
|
||||
timerPausedDuration = 0;
|
||||
const remainingPause = (sixHoursSeconds + thirtyMinutes) - elapsedSeconds;
|
||||
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
|
||||
pauseTimeout = setTimeout(() => {
|
||||
@@ -143,7 +164,7 @@ async function checkRunningTimer() {
|
||||
isPaused = false;
|
||||
pauseStartElapsed = 0;
|
||||
pauseEndTime = 0;
|
||||
document.getElementById('timerStatus').textContent = 'Läuft seit ' + todayEntry.startTime;
|
||||
setTimerStatus('Läuft seit ' + todayEntry.startTime, true);
|
||||
}, remainingPause * 1000);
|
||||
}
|
||||
// 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;
|
||||
const remainingPause = (nineHoursSeconds + thirtyMinutes + fifteenMinutes) - elapsedSeconds;
|
||||
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
|
||||
pauseTimeout = setTimeout(() => {
|
||||
@@ -161,7 +182,7 @@ async function checkRunningTimer() {
|
||||
isPaused = false;
|
||||
pauseStartElapsed = 0;
|
||||
pauseEndTime = 0;
|
||||
document.getElementById('timerStatus').textContent = 'Läuft seit ' + todayEntry.startTime;
|
||||
setTimerStatus('Läuft seit ' + todayEntry.startTime, true);
|
||||
}, remainingPause * 1000);
|
||||
}
|
||||
// Not in pause, but may have completed pauses
|
||||
@@ -214,9 +235,7 @@ async function startWork() {
|
||||
stopBtn.disabled = false;
|
||||
stopBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||
stopBtn.classList.add('hover:bg-red-700');
|
||||
document.getElementById('timerStatus').textContent = 'Läuft seit ' + startTime;
|
||||
document.getElementById('timerStatus').classList.remove('cursor-pointer', 'underline', 'hover:text-blue-300');
|
||||
document.getElementById('timerStatus').classList.add('cursor-default');
|
||||
setTimerStatus('Läuft seit ' + startTime, true);
|
||||
|
||||
// Start timer interval
|
||||
timerInterval = setInterval(updateTimer, 1000);
|
||||
@@ -269,9 +288,7 @@ async function stopWork() {
|
||||
stopBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
||||
stopBtn.classList.remove('hover:bg-red-700');
|
||||
document.getElementById('timerDisplay').textContent = '00:00:00';
|
||||
document.getElementById('timerStatus').textContent = 'Nicht gestartet';
|
||||
document.getElementById('timerStatus').classList.add('cursor-pointer', 'underline', 'hover:text-blue-300');
|
||||
document.getElementById('timerStatus').classList.remove('cursor-default');
|
||||
setTimerStatus('Nicht gestartet', false);
|
||||
|
||||
// Reload monthly view
|
||||
loadMonthlyView();
|
||||
@@ -293,7 +310,7 @@ function updateTimer() {
|
||||
// Update pause countdown live
|
||||
const remainingSeconds = Math.max(0, Math.ceil((pauseEndTime - now) / 1000));
|
||||
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 {
|
||||
elapsed = Math.floor((now - timerStartTime) / 1000) - timerPausedDuration;
|
||||
|
||||
@@ -308,6 +325,9 @@ function updateTimer() {
|
||||
|
||||
document.getElementById('timerDisplay').textContent = formatDuration(elapsed);
|
||||
|
||||
// Calculate and display additional timer metrics
|
||||
updateTimerMetrics(elapsed);
|
||||
|
||||
// Update live net hours in the table for current day
|
||||
const netHoursCell = document.getElementById('current-day-net-hours');
|
||||
if (netHoursCell) {
|
||||
@@ -315,6 +335,21 @@ function updateTimer() {
|
||||
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
|
||||
const pauseCell = document.getElementById('current-day-pause');
|
||||
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
|
||||
*/
|
||||
@@ -384,7 +472,7 @@ function pauseTimer(durationSeconds) {
|
||||
timerPausedDuration += durationSeconds;
|
||||
isPaused = false;
|
||||
pauseEndTime = 0;
|
||||
document.getElementById('timerStatus').textContent = 'Läuft seit ' + timerStartTimeString;
|
||||
setTimerStatus('Läuft seit ' + timerStartTimeString, true);
|
||||
}, durationSeconds * 1000);
|
||||
}
|
||||
|
||||
@@ -497,6 +585,9 @@ function renderEntries(entries) {
|
||||
|
||||
emptyState.classList.add('hidden');
|
||||
|
||||
// Running balance accumulator
|
||||
let runningBalance = 0;
|
||||
|
||||
entries.forEach(entry => {
|
||||
const row = document.createElement('tr');
|
||||
const dateObj = new Date(entry.date + 'T00:00:00');
|
||||
@@ -529,6 +620,22 @@ function renderEntries(entries) {
|
||||
</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 + `
|
||||
<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>
|
||||
@@ -545,6 +652,7 @@ function renderEntries(entries) {
|
||||
${entry.pauseMinutes}
|
||||
</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">
|
||||
<span class="text-lg">${locationIcon}</span> ${locationText}
|
||||
</td>
|
||||
@@ -618,6 +726,9 @@ function renderMonthlyView(entries) {
|
||||
entriesMap[entry.date] = entry;
|
||||
});
|
||||
|
||||
// Running balance accumulator
|
||||
let runningBalance = 0;
|
||||
|
||||
// Render all days from 1st to lastDay
|
||||
for (let day = 1; day <= lastDay; day++) {
|
||||
const dateObj = new Date(displayYear, displayMonth, day);
|
||||
@@ -707,11 +818,33 @@ function renderMonthlyView(entries) {
|
||||
</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 + `
|
||||
<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>
|
||||
${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>
|
||||
${balanceCell}
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-center text-gray-100">
|
||||
<span class="text-lg">${displayIcon}</span> ${displayText}
|
||||
</td>
|
||||
@@ -754,7 +887,7 @@ function renderMonthlyView(entries) {
|
||||
data-date="${dateISO}" ${selectedEntries.has(dateISO) ? 'checked' : ''}>
|
||||
</td>
|
||||
` : '<td class="hidden"></td>';
|
||||
const colspan = bulkEditMode ? '5' : '5';
|
||||
const colspan = bulkEditMode ? '6' : '6';
|
||||
|
||||
row.innerHTML = checkboxCell + `
|
||||
<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.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||
stopBtn.classList.add('hover:bg-red-700');
|
||||
document.getElementById('timerStatus').textContent = 'Läuft seit ' + timeStr;
|
||||
document.getElementById('timerStatus').classList.remove('cursor-pointer', 'underline', 'hover:text-blue-300');
|
||||
document.getElementById('timerStatus').classList.add('cursor-default');
|
||||
setTimerStatus('Läuft seit ' + timeStr, true);
|
||||
|
||||
// Calculate elapsed time and check for active pauses
|
||||
const elapsed = now.getTime() - startDate.getTime();
|
||||
@@ -3598,7 +3729,7 @@ async function handleManualStartTime(timeStr) {
|
||||
timerPausedDuration = 0;
|
||||
const remainingPause = (sixHoursSeconds + thirtyMinutes) - elapsedSeconds;
|
||||
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
|
||||
pauseTimeout = setTimeout(() => {
|
||||
@@ -3606,7 +3737,7 @@ async function handleManualStartTime(timeStr) {
|
||||
isPaused = false;
|
||||
pauseStartElapsed = 0;
|
||||
pauseEndTime = 0;
|
||||
document.getElementById('timerStatus').textContent = 'Läuft seit ' + timeStr;
|
||||
setTimerStatus('Läuft seit ' + timeStr, true);
|
||||
}, remainingPause * 1000);
|
||||
}
|
||||
// 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;
|
||||
const remainingPause = (nineHoursSeconds + thirtyMinutes + fifteenMinutes) - elapsedSeconds;
|
||||
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
|
||||
pauseTimeout = setTimeout(() => {
|
||||
@@ -3624,7 +3755,7 @@ async function handleManualStartTime(timeStr) {
|
||||
isPaused = false;
|
||||
pauseStartElapsed = 0;
|
||||
pauseEndTime = 0;
|
||||
document.getElementById('timerStatus').textContent = 'Läuft seit ' + timeStr;
|
||||
setTimerStatus('Läuft seit ' + timeStr, true);
|
||||
}, remainingPause * 1000);
|
||||
}
|
||||
// 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
|
||||
document.getElementById('btnConfirmManualTime').addEventListener('click', async () => {
|
||||
const timeInput = document.getElementById('manualTimeInput');
|
||||
|
||||
Reference in New Issue
Block a user