Files
TimeTrack/static/js/script.js
Jens Luedicke be111a4bed Implement comprehensive project time logging feature
Add complete project management system with role-based access control:

**Core Features:**
- Project creation and management for Admins/Supervisors
- Time tracking with optional project selection and notes
- Project-based filtering and reporting in history
- Enhanced export functionality with project data
- Team-specific project assignments

**Database Changes:**
- New Project model with full relationships
- Enhanced TimeEntry model with project_id and notes
- Updated migration scripts with rollback support
- Sample project creation for testing

**User Interface:**
- Project management templates (create, edit, list)
- Enhanced time tracking with project dropdown
- Project filtering in history page
- Updated navigation for role-based access
- Modern styling with hover effects and responsive design

**API Enhancements:**
- Project validation and access control
- Updated arrive endpoint with project support
- Enhanced export functions with project data
- Role-based route protection

**Migration Support:**
- Comprehensive migration scripts (migrate_projects.py)
- Updated main migration script (migrate_db.py)
- Detailed migration documentation
- Rollback functionality for safe deployment

**Role-Based Access:**
- Admins: Full project CRUD operations
- Supervisors: Project creation and management
- Team Leaders: View team hours with projects
- Team Members: Select projects when tracking time

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-29 17:18:10 +02:00

238 lines
7.9 KiB
JavaScript

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() {
// Get project and notes from the form
const projectSelect = document.getElementById('project-select');
const notesTextarea = document.getElementById('work-notes');
const projectId = projectSelect ? projectSelect.value : null;
const notes = notesTextarea ? notesTextarea.value.trim() : null;
const requestData = {};
if (projectId) {
requestData.project_id = projectId;
}
if (notes) {
requestData.notes = notes;
}
fetch('/api/arrive', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData)
})
.then(response => {
if (!response.ok) {
return response.json().then(data => {
throw new Error(data.error || 'Failed to record arrival time');
});
}
return 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: ' + error.message);
});
});
}
// 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.');
});
});
}
// Add dropdown menu functionality
const dropdownToggles = document.querySelectorAll('.dropdown-toggle');
dropdownToggles.forEach(toggle => {
toggle.addEventListener('click', function(e) {
e.preventDefault();
const parent = this.parentElement;
const menu = parent.querySelector('.dropdown-menu');
// Toggle the display of the dropdown menu
if (menu.style.display === 'block') {
menu.style.display = 'none';
} else {
// Close all other open dropdowns first
document.querySelectorAll('.dropdown-menu').forEach(m => {
if (m !== menu) m.style.display = 'none';
});
menu.style.display = 'block';
}
});
});
// Close dropdowns when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown-menu').forEach(menu => {
menu.style.display = 'none';
});
}
});
});
// Add event listener for resume work buttons
document.addEventListener('click', function(e) {
if (e.target && e.target.classList.contains('resume-work-btn')) {
const entryId = e.target.getAttribute('data-id');
resumeWork(entryId);
}
});
// Function to resume work
function resumeWork(entryId) {
fetch(`/api/resume/${entryId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => {
if (!response.ok) {
return response.json().then(data => {
throw new Error(data.message || 'Failed to resume work');
});
}
return response.json();
})
.then(data => {
if (data.success) {
// 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);
// Reload the page to show the active session
window.location.reload();
} else {
alert(data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert(error.message || 'An error occurred while trying to resume work.');
});
}