feat: add target hours selector and update timer calculations based on user input
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:18:38 +01:00
parent 17906c76f2
commit 763d7d141b
3 changed files with 43 additions and 10 deletions

View File

@@ -382,6 +382,23 @@
Startzeit manuell eingeben Startzeit manuell eingeben
</button> </button>
<!-- Target hours selector -->
<div class="mt-3 flex items-center gap-2">
<label for="targetHoursSelect" class="text-sm text-gray-400">Soll-Stunden:</label>
<select id="targetHoursSelect" class="px-3 py-1 bg-gray-700 text-gray-100 border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
<option value="1">1h</option>
<option value="2">2h</option>
<option value="3">3h</option>
<option value="4">4h</option>
<option value="5">5h</option>
<option value="6">6h</option>
<option value="7">7h</option>
<option value="8" selected>8h</option>
<option value="9">9h</option>
<option value="10">10h</option>
</select>
</div>
<input type="text" id="manualStartTimeInput" class="hidden"> <input type="text" id="manualStartTimeInput" class="hidden">
<!-- Additional Timer Metrics --> <!-- Additional Timer Metrics -->
@@ -392,7 +409,7 @@
</button> </button>
<div class="flex items-center gap-2 text-gray-300"> <div class="flex items-center gap-2 text-gray-300">
<i data-lucide="target" class="w-4 h-4 text-green-400"></i> <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> <span>Soll erreicht: <span id="targetReachedTime" class="font-semibold text-white">--:--</span></span>
</div> </div>
<div class="flex items-center gap-2 text-gray-300"> <div class="flex items-center gap-2 text-gray-300">
<i data-lucide="timer" class="w-4 h-4 text-blue-400"></i> <i data-lucide="timer" class="w-4 h-4 text-blue-400"></i>

View File

@@ -339,7 +339,7 @@ function updateTimer() {
const balanceCell = document.getElementById('current-day-balance'); const balanceCell = document.getElementById('current-day-balance');
if (balanceCell) { if (balanceCell) {
const netHours = elapsed / 3600; // Convert seconds to hours const netHours = elapsed / 3600; // Convert seconds to hours
const deviation = netHours - 8.0; const deviation = netHours - targetHours;
const baseBalance = parseFloat(balanceCell.dataset.baseBalance || 0); const baseBalance = parseFloat(balanceCell.dataset.baseBalance || 0);
const currentBalance = baseBalance + deviation; const currentBalance = baseBalance + deviation;
@@ -388,8 +388,8 @@ function updateTimerMetrics(netElapsedSeconds) {
return; return;
} }
// Target: 8 hours (28800 seconds) // Target hours from user selection (default 8)
const targetSeconds = 8 * 60 * 60; const targetSeconds = targetHours * 60 * 60;
// Calculate total pause time: 30 min after 6h + 15 min after 9h // Calculate total pause time: 30 min after 6h + 15 min after 9h
const pauseDuration30Min = 30 * 60; // 30 minutes in seconds const pauseDuration30Min = 30 * 60; // 30 minutes in seconds
@@ -398,14 +398,14 @@ function updateTimerMetrics(netElapsedSeconds) {
// Time needed including pauses // Time needed including pauses
// After 6h work -> 30 min pause // After 6h work -> 30 min pause
// After 9h work -> 15 min pause // After 9h work -> 15 min pause
// Total gross time = 8h work + 30min pause + 15min pause = 8h 45min // Total gross time = target work hours + 30min pause + 15min pause
const totalGrossTimeNeeded = targetSeconds + pauseDuration30Min + pauseDuration15Min; const totalGrossTimeNeeded = targetSeconds + pauseDuration30Min + pauseDuration15Min;
// Calculate when target will be reached (clock time) // Calculate when target will be reached (clock time)
const targetReachedTimestamp = new Date(timerStartTime + totalGrossTimeNeeded * 1000); const targetReachedTimestamp = new Date(timerStartTime + totalGrossTimeNeeded * 1000);
const targetHours = String(targetReachedTimestamp.getHours()).padStart(2, '0'); const targetHoursTime = String(targetReachedTimestamp.getHours()).padStart(2, '0');
const targetMinutes = String(targetReachedTimestamp.getMinutes()).padStart(2, '0'); const targetMinutesDisplay = String(targetReachedTimestamp.getMinutes()).padStart(2, '0');
targetReachedTimeSpan.textContent = `${targetHours}:${targetMinutes}`; targetReachedTimeSpan.textContent = `${targetHoursTime}:${targetMinutesDisplay}`;
// Calculate countdown to target (remaining net work time) // Calculate countdown to target (remaining net work time)
const remainingNetSeconds = Math.max(0, targetSeconds - netElapsedSeconds); const remainingNetSeconds = Math.max(0, targetSeconds - netElapsedSeconds);
@@ -823,16 +823,21 @@ function renderMonthlyView(entries) {
let balanceCell = ''; let balanceCell = '';
if (isPastDay && entryType !== 'vacation') { if (isPastDay && entryType !== 'vacation') {
// For all workdays (including flextime): Actual - Target (8h) // For all workdays (including flextime): Actual - Target (8h default)
// Flextime has netHours = 0, so deviation will be -8h // Flextime has netHours = 0, so deviation will be -8h
// For current day: store balance before today, then use custom target hours in live update
const balanceBeforeToday = runningBalance;
const deviation = entry.netHours - 8.0; const deviation = entry.netHours - 8.0;
runningBalance += deviation; runningBalance += deviation;
const balanceColor = runningBalance >= 0 ? 'text-green-400' : 'text-red-400'; const balanceColor = runningBalance >= 0 ? 'text-green-400' : 'text-red-400';
const balanceSign = runningBalance >= 0 ? '+' : ''; const balanceSign = runningBalance >= 0 ? '+' : '';
// Add ID for current day to enable live updates // Add ID for current day to enable live updates
// Store balance before today, so live update can add current day's deviation with custom target
const balanceId = isToday && entryType === 'work' ? 'id="current-day-balance"' : ''; const balanceId = isToday && entryType === 'work' ? 'id="current-day-balance"' : '';
const balanceDataAttr = isToday && entryType === 'work' ? `data-base-balance="${runningBalance - deviation}"` : ''; const balanceDataAttr = isToday && entryType === 'work' ? `data-base-balance="${balanceBeforeToday}"` : '';
balanceCell = `<td class="px-4 py-3 whitespace-nowrap text-sm font-semibold ${balanceColor}" ${balanceId} ${balanceDataAttr}>${balanceSign}${runningBalance.toFixed(2)}h</td>`; balanceCell = `<td class="px-4 py-3 whitespace-nowrap text-sm font-semibold ${balanceColor}" ${balanceId} ${balanceDataAttr}>${balanceSign}${runningBalance.toFixed(2)}h</td>`;
} else { } else {
@@ -3840,6 +3845,16 @@ function initializeEventListeners() {
document.getElementById('btnStartWork').addEventListener('click', startWork); document.getElementById('btnStartWork').addEventListener('click', startWork);
document.getElementById('btnStopWork').addEventListener('click', stopWork); document.getElementById('btnStopWork').addEventListener('click', stopWork);
// Target hours selector
document.getElementById('targetHoursSelect').addEventListener('change', (e) => {
targetHours = parseInt(e.target.value);
// Update metrics if timer is running
if (timerStartTime) {
const elapsed = Math.floor((Date.now() - timerStartTime) / 1000) - timerPausedDuration;
updateTimerMetrics(elapsed);
}
});
// Timer status - manual start time entry // Timer status - manual start time entry
document.getElementById('timerStatus').addEventListener('click', () => { document.getElementById('timerStatus').addEventListener('click', () => {
if (!timerStartTime) { if (!timerStartTime) {

View File

@@ -17,6 +17,7 @@ let timerPausedDuration = 0; // Total paused time in seconds
let isPaused = false; let isPaused = false;
let pauseTimeout = null; let pauseTimeout = null;
let currentEntryId = null; // ID of today's entry being timed let currentEntryId = null; // ID of today's entry being timed
let targetHours = 8; // Target work hours per day (1-10)
// Current month display state // Current month display state
let displayYear = new Date().getFullYear(); let displayYear = new Date().getFullYear();