Initial commit.

This commit is contained in:
2025-06-27 15:14:57 +02:00
committed by Jens Luedicke
commit a8d1f33874
13 changed files with 1007 additions and 0 deletions

314
static/css/style.css Normal file
View File

@@ -0,0 +1,314 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
}
header {
background-color: #4CAF50;
padding: 1rem;
}
nav ul {
display: flex;
list-style: none;
justify-content: center;
}
nav ul li {
margin: 0 1rem;
}
nav ul li a {
color: white;
text-decoration: none;
}
main {
max-width: 1200px;
margin: 2rem auto;
padding: 0 1rem;
}
.hero {
text-align: center;
padding: 2rem 0;
background-color: #f9f9f9;
border-radius: 5px;
margin-bottom: 2rem;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin: 2rem 0;
}
.feature {
background-color: #f9f9f9;
padding: 1.5rem;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.feature h3 {
color: #4CAF50;
margin-top: 0;
}
.about, .contact, .thank-you {
max-width: 800px;
margin: 0 auto;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 3px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 0.5rem 1rem;
cursor: pointer;
border-radius: 3px;
}
.btn {
display: inline-block;
background-color: #4CAF50;
color: white;
padding: 0.5rem 1rem;
text-decoration: none;
border-radius: 3px;
margin-top: 1rem;
}
footer {
text-align: center;
padding: 1rem;
background-color: #f4f4f4;
margin-top: 2rem;
}
/* Time tracking specific styles */
.timetrack-container {
max-width: 800px;
margin: 0 auto 3rem auto;
}
.timer-section {
background-color: #f5f5f5;
padding: 2rem;
border-radius: 5px;
margin-bottom: 2rem;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
#timer {
font-size: 3rem;
font-weight: bold;
margin: 1rem 0;
font-family: monospace;
color: #333;
}
.arrive-btn {
background-color: #4CAF50;
font-size: 1.2rem;
padding: 0.8rem 2rem;
transition: background-color 0.3s;
}
.arrive-btn:hover {
background-color: #45a049;
}
.leave-btn {
background-color: #f44336;
font-size: 1.2rem;
padding: 0.8rem 2rem;
transition: background-color 0.3s;
}
.leave-btn:hover {
background-color: #d32f2f;
}
.time-history {
width: 100%;
border-collapse: collapse;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.time-history th, .time-history td {
padding: 0.8rem;
text-align: left;
border-bottom: 1px solid #ddd;
}
.time-history th {
background-color: #f2f2f2;
font-weight: bold;
}
.time-history tr:hover {
background-color: #f5f5f5;
}
.button-group {
display: flex;
justify-content: center;
gap: 1rem;
margin-top: 1rem;
}
.pause-btn {
background-color: #ff9800;
font-size: 1.2rem;
padding: 0.8rem 2rem;
transition: background-color 0.3s;
}
.pause-btn:hover {
background-color: #f57c00;
}
.resume-btn {
background-color: #2196F3;
font-size: 1.2rem;
padding: 0.8rem 2rem;
transition: background-color 0.3s;
}
.resume-btn:hover {
background-color: #1976D2;
}
.break-info {
color: #ff9800;
font-weight: bold;
margin: 0.5rem 0;
}
.break-total {
color: #666;
font-size: 0.9rem;
margin: 0.5rem 0;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
background-color: #4CAF50;
color: white;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 1000;
animation: fadeIn 0.3s, fadeOut 0.3s 2.7s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeOut {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-20px); }
}
.config-container {
max-width: 800px;
margin: 0 auto 3rem auto;
padding: 1rem;
background-color: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.form-section {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 1rem;
margin-bottom: 1.5rem;
background-color: #f9f9f9;
}
.form-section h3 {
margin-top: 0;
margin-bottom: 1rem;
color: #333;
font-size: 1.2rem;
}
.config-form .form-group {
margin-bottom: 1.5rem;
}
.config-form label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.config-form small {
display: block;
color: #666;
margin-top: 0.25rem;
}
.btn-primary {
background-color: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.btn-primary:hover {
background-color: #0069d9;
}
.alert {
padding: 0.75rem 1rem;
margin-bottom: 1rem;
border-radius: 4px;
}
.alert-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

135
static/js/script.js Normal file
View File

@@ -0,0 +1,135 @@
document.addEventListener('DOMContentLoaded', function() {
console.log('Flask app loaded successfully!');
// Timer functionality
const timer = document.getElementById('timer');
const arriveBtn = document.getElementById('arrive-btn');
const leaveBtn = document.getElementById('leave-btn');
const pauseBtn = document.getElementById('pause-btn');
let isPaused = false;
let timerInterval;
// Start timer if we're on a page with an active timer
if (timer) {
const startTime = parseInt(timer.dataset.start);
const totalBreakDuration = parseInt(timer.dataset.breaks || 0);
isPaused = timer.dataset.paused === 'true';
// Update the pause button text based on current state
if (pauseBtn) {
updatePauseButtonText();
}
// Update timer every second
function updateTimer() {
if (isPaused) return;
const now = Math.floor(Date.now() / 1000);
const diff = now - startTime - totalBreakDuration;
const hours = Math.floor(diff / 3600);
const minutes = Math.floor((diff % 3600) / 60);
const seconds = diff % 60;
timer.textContent = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
// Initial update
updateTimer();
// Set interval for updates
timerInterval = setInterval(updateTimer, 1000);
}
function updatePauseButtonText() {
if (pauseBtn) {
if (isPaused) {
pauseBtn.textContent = 'Resume Work';
pauseBtn.classList.add('resume-btn');
pauseBtn.classList.remove('pause-btn');
} else {
pauseBtn.textContent = 'Pause';
pauseBtn.classList.add('pause-btn');
pauseBtn.classList.remove('resume-btn');
}
}
}
// Handle arrive button click
if (arriveBtn) {
arriveBtn.addEventListener('click', function() {
fetch('/api/arrive', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
// Reload the page to show the active timer
window.location.reload();
})
.catch(error => {
console.error('Error:', error);
alert('Failed to record arrival time. Please try again.');
});
});
}
// Handle pause/resume button click
if (pauseBtn) {
pauseBtn.addEventListener('click', function() {
const entryId = pauseBtn.dataset.id;
fetch(`/api/toggle-pause/${entryId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
isPaused = data.is_paused;
updatePauseButtonText();
// Show a notification
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = data.message;
document.body.appendChild(notification);
// Remove notification after 3 seconds
setTimeout(() => {
notification.remove();
}, 3000);
})
.catch(error => {
console.error('Error:', error);
alert('Failed to pause/resume. Please try again.');
});
});
}
// Handle leave button click
if (leaveBtn) {
leaveBtn.addEventListener('click', function() {
const entryId = leaveBtn.dataset.id;
fetch(`/api/leave/${entryId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
// Reload the page to update the UI
window.location.reload();
})
.catch(error => {
console.error('Error:', error);
alert('Failed to record departure time. Please try again.');
});
});
}
});