feat: update better-sqlite3 dependency and enhance timer metrics with target hours functionality
Some checks failed
Build and Push Docker Image / build (push) Failing after 27s
Some checks failed
Build and Push Docker Image / build (push) Failing after 27s
This commit is contained in:
@@ -372,7 +372,8 @@
|
||||
|
||||
<!-- 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 class="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-6">
|
||||
<!-- Left side: Timer display and controls -->
|
||||
<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>
|
||||
@@ -384,11 +385,8 @@
|
||||
|
||||
<!-- Target hours selector -->
|
||||
<div class="mt-3 flex items-center gap-2">
|
||||
<label for="targetHoursSelect" class="text-sm text-gray-400">Soll-Stunden:</label>
|
||||
<label for="targetHoursSelect" class="text-sm text-gray-400">Geplante Arbeitszeit:</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>
|
||||
@@ -400,24 +398,30 @@
|
||||
</div>
|
||||
|
||||
<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></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>
|
||||
|
||||
<!-- Right side: Timer Metrics (responsive - below on small screens, right on large) -->
|
||||
<div id="timerMetrics" class="hidden w-full lg:w-auto lg:min-w-[280px] 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></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 class="flex items-center gap-2 text-gray-300">
|
||||
<i data-lucide="trending-up" class="w-4 h-4 text-purple-400"></i>
|
||||
<span>Saldo bei Soll: <span id="balanceAtTarget" class="font-semibold text-white">--:--</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex gap-3 w-full lg:w-auto justify-center lg:justify-start">
|
||||
<button id="btnStartWork"
|
||||
class="btn-elevated inline-flex items-center justify-center gap-2 px-8 py-4 bg-gradient-to-r from-green-600 to-green-500 text-white rounded-xl font-semibold text-lg" title="Start">
|
||||
<i data-lucide="play" class="w-6 h-6"></i>
|
||||
|
||||
@@ -226,6 +226,9 @@ async function startWork() {
|
||||
timerPausedDuration = 0;
|
||||
isPaused = false;
|
||||
|
||||
// Save current target hours
|
||||
await setSetting('targetHours', targetHours);
|
||||
|
||||
// Update UI
|
||||
const startBtn = document.getElementById('btnStartWork');
|
||||
const stopBtn = document.getElementById('btnStopWork');
|
||||
@@ -278,6 +281,11 @@ async function stopWork() {
|
||||
timerPausedDuration = 0;
|
||||
isPaused = false;
|
||||
|
||||
// Reset target hours to 8 and clear saved setting
|
||||
targetHours = 8;
|
||||
document.getElementById('targetHoursSelect').value = 8;
|
||||
await setSetting('targetHours', null);
|
||||
|
||||
// Update UI
|
||||
const startBtn = document.getElementById('btnStartWork');
|
||||
const stopBtn = document.getElementById('btnStopWork');
|
||||
@@ -339,7 +347,8 @@ function updateTimer() {
|
||||
const balanceCell = document.getElementById('current-day-balance');
|
||||
if (balanceCell) {
|
||||
const netHours = elapsed / 3600; // Convert seconds to hours
|
||||
const deviation = netHours - targetHours;
|
||||
// Always use 8h standard target for balance calculation (not the dropdown target)
|
||||
const deviation = netHours - 8.0;
|
||||
const baseBalance = parseFloat(balanceCell.dataset.baseBalance || 0);
|
||||
const currentBalance = baseBalance + deviation;
|
||||
|
||||
@@ -383,6 +392,7 @@ function updateTimer() {
|
||||
function updateTimerMetrics(netElapsedSeconds) {
|
||||
const targetReachedTimeSpan = document.getElementById('targetReachedTime');
|
||||
const timeUntilTargetSpan = document.getElementById('timeUntilTarget');
|
||||
const balanceAtTargetSpan = document.getElementById('balanceAtTarget');
|
||||
|
||||
if (!timerStartTime) {
|
||||
return;
|
||||
@@ -424,6 +434,23 @@ function updateTimerMetrics(netElapsedSeconds) {
|
||||
timeUntilTargetSpan.classList.remove('text-green-400');
|
||||
}
|
||||
|
||||
// Calculate balance when target is reached
|
||||
// Get base balance from current day row (balance before today)
|
||||
const balanceCell = document.getElementById('current-day-balance');
|
||||
if (balanceCell && balanceAtTargetSpan) {
|
||||
const baseBalance = parseFloat(balanceCell.dataset.baseBalance || 0);
|
||||
// Deviation when target hours are reached: targetHours - 8h (standard)
|
||||
const targetNetHours = targetHours;
|
||||
const deviation = targetNetHours - 8.0;
|
||||
const balanceAtTarget = baseBalance + deviation;
|
||||
|
||||
const balanceColor = balanceAtTarget >= 0 ? 'text-green-400' : 'text-red-400';
|
||||
const balanceSign = balanceAtTarget >= 0 ? '+' : '';
|
||||
|
||||
balanceAtTargetSpan.className = `font-semibold ${balanceColor}`;
|
||||
balanceAtTargetSpan.textContent = `${balanceSign}${balanceAtTarget.toFixed(2)}h`;
|
||||
}
|
||||
|
||||
// Reinitialize Lucide icons for the new metrics
|
||||
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
||||
lucide.createIcons();
|
||||
@@ -1055,20 +1082,25 @@ async function loadMonthlyView() {
|
||||
updateStatistics(entries);
|
||||
updateBridgeDaysDisplay();
|
||||
|
||||
// Show/hide PDF export button based on whether month is complete
|
||||
const today = new Date();
|
||||
const isCurrentOrFutureMonth = displayYear > today.getFullYear() ||
|
||||
(displayYear === today.getFullYear() && displayMonth >= today.getMonth());
|
||||
|
||||
// Show/hide PDF export button based on whether last day has complete times
|
||||
const pdfButton = document.getElementById('btnExportPDF');
|
||||
const pdfButtonMobile = document.getElementById('btnExportPDFMobile');
|
||||
|
||||
if (isCurrentOrFutureMonth) {
|
||||
pdfButton.style.display = 'none';
|
||||
if (pdfButtonMobile) pdfButtonMobile.style.display = 'none';
|
||||
} else {
|
||||
// Check if last day of month has start and end time (and they're different)
|
||||
const lastDayDate = `${displayYear}-${String(displayMonth + 1).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
|
||||
const lastDayEntry = entries.find(e => e.date === lastDayDate);
|
||||
|
||||
const isLastDayComplete = lastDayEntry &&
|
||||
lastDayEntry.startTime &&
|
||||
lastDayEntry.endTime &&
|
||||
lastDayEntry.startTime !== lastDayEntry.endTime;
|
||||
|
||||
if (isLastDayComplete) {
|
||||
pdfButton.style.display = '';
|
||||
if (pdfButtonMobile) pdfButtonMobile.style.display = '';
|
||||
} else {
|
||||
pdfButton.style.display = 'none';
|
||||
if (pdfButtonMobile) pdfButtonMobile.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2804,6 +2836,13 @@ async function loadSettings() {
|
||||
document.getElementById('companyHolidayNewYear').checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Load target hours (always restore dropdown value)
|
||||
const savedTargetHours = await getSetting('targetHours');
|
||||
if (savedTargetHours) {
|
||||
targetHours = parseInt(savedTargetHours);
|
||||
document.getElementById('targetHoursSelect').value = targetHours;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3706,6 +3745,9 @@ async function handleManualStartTime(timeStr) {
|
||||
timerPausedDuration = 0;
|
||||
isPaused = false;
|
||||
|
||||
// Save current target hours
|
||||
await setSetting('targetHours', targetHours);
|
||||
|
||||
// Update UI
|
||||
const startBtn = document.getElementById('btnStartWork');
|
||||
const stopBtn = document.getElementById('btnStopWork');
|
||||
@@ -3846,10 +3888,12 @@ function initializeEventListeners() {
|
||||
document.getElementById('btnStopWork').addEventListener('click', stopWork);
|
||||
|
||||
// Target hours selector
|
||||
document.getElementById('targetHoursSelect').addEventListener('change', (e) => {
|
||||
document.getElementById('targetHoursSelect').addEventListener('change', async (e) => {
|
||||
targetHours = parseInt(e.target.value);
|
||||
// Update metrics if timer is running
|
||||
|
||||
// Save target hours if timer is running
|
||||
if (timerStartTime) {
|
||||
await setSetting('targetHours', targetHours);
|
||||
const elapsed = Math.floor((Date.now() - timerStartTime) / 1000) - timerPausedDuration;
|
||||
updateTimerMetrics(elapsed);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user