Files
timetracker/public/index.html

443 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zeiterfassung</title>
<!-- Tailwind CSS via CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Flatpickr CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<!-- Custom Flatpickr Mobile Theme for Tumbler Style -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/dark.css">
<style>
/* Custom styles for better tumbler/wheel experience */
.flatpickr-time input {
font-size: 1.2rem;
padding: 0.5rem;
}
/* Mobile-friendly time picker styles */
@media (min-width: 768px) {
.flatpickr-time {
display: flex;
gap: 0.5rem;
align-items: center;
}
}
/* Editable cell styles */
.editable-cell {
cursor: pointer;
transition: background-color 0.2s;
}
.editable-cell:hover {
background-color: #374151;
}
.editable-cell.editing {
padding: 0;
}
.cell-input {
width: 100%;
padding: 0.5rem;
border: 2px solid #3b82f6;
border-radius: 0.25rem;
font-size: 0.875rem;
background-color: #1f2937;
color: #f3f4f6;
}
/* Toast Notification Styles */
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 10px;
max-width: 400px;
}
.toast {
padding: 16px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
gap: 12px;
animation: slideIn 0.3s ease-out;
font-size: 14px;
font-weight: 500;
}
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
.toast.hiding {
animation: slideOut 0.3s ease-in forwards;
}
.toast-success {
background-color: #10b981;
color: white;
}
.toast-error {
background-color: #ef4444;
color: white;
}
.toast-info {
background-color: #3b82f6;
color: white;
}
.toast-icon {
font-size: 20px;
flex-shrink: 0;
}
</style>
</head>
<body class="bg-gray-900 min-h-screen">
<!-- Toast Container -->
<div id="toastContainer" class="toast-container"></div>
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<div class="bg-gray-800 rounded-lg shadow-md p-6 mb-6 border border-gray-700">
<h1 class="text-3xl font-bold text-gray-100 mb-4">⏱️ Zeiterfassung</h1>
<!-- Start/Stop Timer Section -->
<div class="mb-6 p-4 bg-gradient-to-r from-gray-700 to-gray-800 rounded-lg border border-gray-600">
<div class="flex flex-wrap items-center justify-between gap-4">
<div>
<div class="text-sm text-gray-400 mb-1">Heutige Arbeitszeit</div>
<div id="timerDisplay" class="text-4xl font-bold text-gray-100">00:00:00</div>
<div id="timerStatus" class="text-sm text-gray-400 mt-1">Nicht gestartet</div>
</div>
<div class="flex gap-3">
<button id="btnStartWork"
class="px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors text-2xl shadow-md" title="Start">
▶️
</button>
<button id="btnStopWork" disabled
class="px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors text-2xl shadow-md disabled:opacity-50 disabled:cursor-not-allowed" title="Stop">
⏹️
</button>
</div>
</div>
</div>
<!-- Date Range Filter -->
<div class="flex flex-wrap gap-4 items-end">
<div class="flex-1 min-w-[200px]">
<label for="filterFrom" class="block text-sm font-medium text-gray-300 mb-1">Von</label>
<input type="text" id="filterFrom"
class="w-full px-4 py-2 border border-gray-600 bg-gray-700 text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="DD.MM.YYYY">
</div>
<div class="flex-1 min-w-[200px]">
<label for="filterTo" class="block text-sm font-medium text-gray-300 mb-1">Bis</label>
<input type="text" id="filterTo"
class="w-full px-4 py-2 border border-gray-600 bg-gray-700 text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="DD.MM.YYYY">
</div>
<div class="flex gap-3">
<button id="btnFilter"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-xl" title="Filtern">
🔍
</button>
<button id="btnClearFilter"
class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors text-xl" title="Filter zurücksetzen">
</button>
<button id="btnExport"
class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors text-xl" title="Export (alle)">
📥
</button>
<button id="btnExportDeviations"
class="px-4 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 transition-colors text-xl" title="Export (nur Abweichungen)">
⚠️
</button>
</div>
</div>
</div>
<!-- Statistics -->
<div class="mb-6 bg-gray-800 rounded-lg shadow-md p-6 border border-gray-700">
<h2 class="text-xl font-bold text-gray-100 mb-4">📊 Statistiken</h2>
<!-- Current Month Stats -->
<div class="mb-4">
<h3 class="text-sm font-semibold text-gray-300 mb-2">Aktueller Monat</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="bg-gray-700 rounded-lg p-4 border border-gray-600">
<div class="text-sm text-gray-400 mb-1">Soll</div>
<div id="statTargetHours" class="text-2xl font-bold text-gray-100">0h</div>
</div>
<div class="bg-gray-700 rounded-lg p-4 border border-gray-600">
<div class="text-sm text-gray-400 mb-1">Ist</div>
<div id="statActualHours" class="text-2xl font-bold text-gray-100">0h</div>
</div>
<div class="bg-gray-700 rounded-lg p-4 border border-gray-600">
<div class="text-sm text-gray-400 mb-1">Saldo (Monat)</div>
<div id="statBalance" class="text-2xl font-bold text-gray-100">0h</div>
</div>
<div class="bg-gray-700 rounded-lg p-4 border border-gray-600">
<div class="text-sm text-gray-400 mb-1">Arbeitstage</div>
<div id="statWorkdays" class="text-2xl font-bold text-gray-100">0</div>
</div>
</div>
</div>
<!-- Total Balance -->
<div class="bg-gradient-to-r from-gray-700 to-gray-600 rounded-lg p-4 border border-gray-600">
<div class="flex justify-between items-center">
<div>
<div class="text-sm text-gray-300 mb-1">Gesamt-Saldo (inkl. Vormonat)</div>
<div id="statTotalBalance" class="text-3xl font-bold text-gray-100">0h</div>
</div>
<div class="text-right">
<div class="text-xs text-gray-400">Übertrag Vormonat</div>
<div id="statPreviousBalance" class="text-lg font-semibold text-gray-300">0h</div>
</div>
</div>
</div>
</div>
<!-- Month Navigation -->
<div class="mb-6 bg-gray-800 rounded-lg shadow-md p-4 border border-gray-700">
<div class="flex items-center justify-between">
<div class="flex gap-3">
<button id="btnAddEntry"
class="px-4 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors text-xl" title="Neuer Eintrag">
</button>
<button id="btnAutoFill"
class="px-4 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors text-xl" title="Monat ausfüllen (8h)">
🔄
</button>
<button id="btnToggleBulkEdit"
class="px-4 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors text-xl" title="Mehrfachauswahl aktivieren">
☑️
</button>
</div>
<div class="flex items-center gap-4">
<button id="btnPrevMonth"
class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors text-xl">
</button>
<h2 id="currentMonthDisplay" class="text-2xl font-bold text-gray-100 min-w-[200px] text-center">
<!-- Month name will be inserted here -->
</h2>
<button id="btnNextMonth"
class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors text-xl">
</button>
</div>
<div class="w-48"></div> <!-- Spacer for alignment -->
</div>
</div>
<!-- Bulk Edit Actions Bar -->
<div id="bulkEditBar" class="hidden mb-6 bg-amber-900 rounded-lg shadow-md p-4 border border-amber-700">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<span id="selectedCount" class="text-white font-semibold">0 ausgewählt</span>
<button id="btnSelectAll"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-semibold">
Alle auswählen
</button>
<button id="btnDeselectAll"
class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors text-sm font-semibold">
Auswahl aufheben
</button>
</div>
<div class="flex gap-3">
<button id="btnBulkSetOffice"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-semibold" title="Alle auf Präsenz setzen">
🏢 Präsenz
</button>
<button id="btnBulkSetHome"
class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors text-sm font-semibold" title="Alle auf Home Office setzen">
🏠 Home Office
</button>
<button id="btnBulkDelete"
class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors text-sm font-semibold" title="Ausgewählte löschen">
🗑️ Löschen
</button>
</div>
</div>
</div>
<!-- Entries Table -->
<div class="bg-gray-800 rounded-lg shadow-md overflow-hidden border border-gray-700">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-700 border-b border-gray-600">
<tr>
<th id="checkboxHeader" class="hidden px-2 py-3 text-center text-xs font-medium text-gray-400 uppercase tracking-wider">
<input type="checkbox" id="masterCheckbox" class="w-5 h-5 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500" title="Alle auswählen/abwählen">
</th>
<th class="px-2 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Tag</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Datum</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Start</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Ende</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Pause (Min)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Netto (Std)</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-400 uppercase tracking-wider">Ort</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-400 uppercase tracking-wider">Action</th>
</tr>
</thead>
<tbody id="entriesTableBody" class="bg-gray-800 divide-y divide-gray-700">
<!-- Entries will be inserted here dynamically -->
</tbody>
</table>
</div>
<!-- Empty State -->
<div id="emptyState" class="hidden p-12 text-center">
<p class="text-gray-400 text-lg">Keine Einträge vorhanden.</p>
<p class="text-gray-500 mt-2">Klicken Sie auf "Neuer Eintrag", um zu beginnen.</p>
</div>
</div>
</div>
<!-- Modal for Add/Edit Entry -->
<div id="entryModal" class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center p-4 z-50">
<div class="bg-gray-800 border border-gray-700 rounded-lg shadow-xl max-w-md w-full p-6">
<h2 id="modalTitle" class="text-2xl font-bold text-gray-100 mb-4">Neuer Eintrag</h2>
<form id="entryForm">
<!-- Date -->
<div class="mb-4">
<label for="modalDate" class="block text-sm font-medium text-gray-300 mb-1">Datum</label>
<input type="text" id="modalDate" required
class="w-full px-4 py-2 border border-gray-600 bg-gray-700 text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="DD.MM.YYYY">
</div>
<!-- Start Time -->
<div class="mb-4">
<label for="modalStartTime" class="block text-sm font-medium text-gray-300 mb-1">Startzeit</label>
<input type="text" id="modalStartTime" required
class="w-full px-4 py-2 border border-gray-600 bg-gray-700 text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="HH:MM">
</div>
<!-- End Time -->
<div class="mb-4">
<label for="modalEndTime" class="block text-sm font-medium text-gray-300 mb-1">Endzeit</label>
<input type="text" id="modalEndTime" required
class="w-full px-4 py-2 border border-gray-600 bg-gray-700 text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="HH:MM">
<!-- Quick Time Buttons -->
<div class="mt-2 flex gap-2 flex-wrap">
<span class="text-xs text-gray-400 w-full mb-1">Schnellauswahl (für 9:00 Start):</span>
<button type="button" class="quick-time-btn px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-gray-200 rounded border border-gray-600" data-hours="6">
6h
</button>
<button type="button" class="quick-time-btn px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-gray-200 rounded border border-gray-600" data-hours="7">
7h
</button>
<button type="button" class="quick-time-btn px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-gray-200 rounded border border-gray-600" data-hours="8">
8h
</button>
<button type="button" class="quick-time-btn px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-gray-200 rounded border border-gray-600" data-hours="9">
9h
</button>
<button type="button" class="quick-time-btn px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-gray-200 rounded border border-gray-600" data-hours="10">
10h
</button>
</div>
</div>
<!-- Pause -->
<div class="mb-4">
<label for="modalPause" class="block text-sm font-medium text-gray-300 mb-1">
Pause (Minuten)
<span class="text-xs text-gray-400">- Optional, sonst automatisch berechnet</span>
</label>
<input type="number" id="modalPause" min="0" step="1"
class="w-full px-4 py-2 border border-gray-600 bg-gray-700 text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Leer lassen für automatische Berechnung">
</div>
<!-- Location -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-300 mb-2">
Arbeitsort
</label>
<div class="flex gap-3">
<button type="button" id="btnLocationOffice"
class="flex-1 px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-semibold flex items-center justify-center gap-2">
🏢 Präsenz
</button>
<button type="button" id="btnLocationHome"
class="flex-1 px-4 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors font-semibold flex items-center justify-center gap-2">
🏠 Home Office
</button>
</div>
<input type="hidden" id="modalLocation" value="office">
</div>
<!-- Buttons -->
<div class="flex gap-3 mt-6">
<button type="submit"
class="flex-1 px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-semibold">
Speichern
</button>
<button type="button" id="btnCancelModal"
class="flex-1 px-6 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400 transition-colors font-semibold">
Abbrechen
</button>
</div>
</form>
</div>
</div>
<!-- Flatpickr JS -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/de.js"></script>
<!-- App Logic -->
<script src="app.js"></script>
</body>
</html>