From 2a7f0b5762730fe4265738ad392fc48cc4628d9c Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 10 Aug 2025 09:51:16 -0400 Subject: [PATCH] edit time off requests --- public/js/main.js | 72 +++++++++++++++++++++++++++++++++++++++++---- public/js/ui.js | 75 +++++++++++++++++++++++++++++++++++++---------- server.js | 39 ++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 20 deletions(-) diff --git a/public/js/main.js b/public/js/main.js index beb280d..bc0df10 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -14,7 +14,8 @@ import { handleViewNotesClick, renderArchiveView, renderTimeOffHistoryView, - updatePendingRequestsList + updatePendingRequestsList, + renderEditTimeOffModal } from './ui.js'; // --- STATE MANAGEMENT --- @@ -214,17 +215,78 @@ function handleAdminDashboardClick(e) { if (target.classList.contains('delete-note-btn')) { if (confirm('Delete this note?')) { apiCall(`/admin/notes/${noteId}`, 'DELETE').then(res => { if (res.success) { showMessage('Note deleted.', 'success'); handleViewNotesClick(); }});}} } +// --- NEW: Handler for editing a time-off request --- +async function handleEditTimeOffSubmit(e) { + e.preventDefault(); + const id = e.target.elements['edit-request-id'].value; + const startDate = e.target.elements['edit-start-date'].value; + const endDate = e.target.elements['edit-end-date'].value; + const reason = e.target.elements['edit-reason'].value; + + if (new Date(endDate) < new Date(startDate)) { + return showMessage('End date cannot be before start date.', 'error'); + } + + const res = await apiCall(`/user/time-off-requests/${id}`, 'PUT', { startDate, endDate, reason }); + if (res.success) { + showMessage(res.data.message, 'success'); + document.getElementById('modal-container').innerHTML = ''; + renderEmployeeDashboard(); // Refresh the dashboard to show changes + } +} + // --- LISTENER ATTACHMENT FUNCTIONS --- export function attachAuthFormListener() { const form = document.getElementById('auth-form'); if (form) form.addEventListener('submit', handleAuthSubmit); } +// --- UPDATED: Employee dashboard listeners now use event delegation --- export function attachEmployeeDashboardListeners() { - document.getElementById('punch-btn')?.addEventListener('click', handlePunch); - document.getElementById('change-password-btn')?.addEventListener('click', () => renderChangePasswordModal(handleChangePassword)); - document.getElementById('time-off-form')?.addEventListener('submit', handleTimeOffRequest); - document.getElementById('view-request-history-btn')?.addEventListener('click', handleViewRequestHistoryClick); + const dashboard = document.getElementById('employee-dashboard'); + if (!dashboard) return; + + // Use one listener for the entire dashboard + dashboard.addEventListener('click', async (e) => { + const target = e.target; + + if (target.id === 'punch-btn') { + handlePunch(); + } + if (target.id === 'change-password-btn') { + renderChangePasswordModal(handleChangePassword); + } + if (target.id === 'view-request-history-btn') { + handleViewRequestHistoryClick(); + } + if (target.classList.contains('delete-request-btn')) { + const id = target.dataset.id; + if (confirm('Are you sure you want to delete this time off request?')) { + const res = await apiCall(`/user/time-off-requests/${id}`, 'DELETE'); + if (res.success) { + showMessage(res.data.message, 'success'); + renderEmployeeDashboard(); + } + } + } + if (target.classList.contains('edit-request-btn')) { + const id = target.dataset.id; + // We need to get the full request data to pre-fill the form + const res = await apiCall('/user/time-off-requests/history'); + if (res.success) { + const requestToEdit = res.data.find(r => r.id == id); + if (requestToEdit) { + renderEditTimeOffModal(requestToEdit, handleEditTimeOffSubmit); + } + } + } + }); + + // Handle form submissions separately + const timeOffForm = document.getElementById('time-off-form'); + if (timeOffForm) { + timeOffForm.addEventListener('submit', handleTimeOffRequest); + } } export function attachAdminDashboardListeners() { diff --git a/public/js/ui.js b/public/js/ui.js index be49ae4..85a2014 100644 --- a/public/js/ui.js +++ b/public/js/ui.js @@ -38,7 +38,6 @@ export function showView(viewName) { // --- MASTER UI UPDATE FUNCTION --- export function updateUI() { - // This function was empty, it has been corrected. try { const storedUser = localStorage.getItem('user'); const authToken = localStorage.getItem('authToken'); @@ -126,7 +125,35 @@ export async function renderEmployeeDashboard() {
-
${requests.map(r => ``).join('') || ''}
DatesReasonStatus
${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)}${r.reason || ''}${r.status}
No upcoming or pending requests.
+
+ + + + + + + + + + + ${requests.map(r => ` + + + + + + + `).join('') || ''} + +
DatesReasonStatusActions
${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)}${r.reason || ''}${r.status} + ${r.status === 'pending' ? ` +
+ + +
+ ` : ''} +
No upcoming or pending requests.
+

My Time Log

@@ -201,6 +228,13 @@ export async function renderAdminDashboard() {
`; + if (lastAdminTab && lastAdminTab !== 'overview') { + document.querySelector('.tab-btn[data-tab="overview"]').classList.remove('active-tab'); + document.getElementById('tab-content-overview').classList.add('hidden'); + document.querySelector(`.tab-btn[data-tab="${lastAdminTab}"]`).classList.add('active-tab'); + document.getElementById(`tab-content-${lastAdminTab}`).classList.remove('hidden'); + } + attachAdminDashboardListeners(); punchedInEntries.forEach(entry => { const durationCell = document.getElementById(`admin-duration-${entry.id}`); @@ -212,16 +246,6 @@ export async function renderAdminDashboard() { adminTimerIntervals.push(intervalId); } }); - - if (lastAdminTab && lastAdminTab !== 'overview') { - // Remove active state from the default tab and panel - document.querySelector('.tab-btn[data-tab="overview"]').classList.remove('active-tab'); - document.getElementById('tab-content-overview').classList.add('hidden'); - - // Add active state to the last viewed tab and panel - document.querySelector(`.tab-btn[data-tab="${lastAdminTab}"]`).classList.add('active-tab'); - document.getElementById(`tab-content-${lastAdminTab}`).classList.remove('hidden'); - } } export function renderArchiveView() { @@ -276,6 +300,28 @@ export function renderRequestHistoryModal(requests) { document.querySelector('.modal-overlay').addEventListener('click', (e) => { if (e.target === e.currentTarget) modalContainer.innerHTML = ''; }); } +export function renderEditTimeOffModal(request, submitHandler) { + const startDate = new Date(request.start_date).toISOString().split('T')[0]; + const endDate = new Date(request.end_date).toISOString().split('T')[0]; + + const formHTML = ` + +
+ + +
+
+ + +
+
+ + +
+ `; + renderModal('Edit Time Off Request', formHTML, submitHandler); +} + // --- UI HELPER FUNCTIONS --- export async function handleViewNotesClick() { @@ -309,17 +355,16 @@ export async function handleViewNotesClick() { container.innerHTML = '

Could not load notes.

'; } } -// This function ONLY updates the pending requests table. + export function updatePendingRequestsList(requests) { const tableBody = document.querySelector('#tab-content-overview table tbody'); - if (!tableBody) return; // Exit if the table isn't on the page + if (!tableBody) return; if (requests.length === 0) { tableBody.innerHTML = 'No pending requests.'; return; } - // Rebuild only the rows of the table tableBody.innerHTML = requests.map(r => ` ${r.username} diff --git a/server.js b/server.js index 17fb41c..ac49e7b 100644 --- a/server.js +++ b/server.js @@ -397,6 +397,45 @@ function setupRoutes() { } }); + // NEW: Endpoint to UPDATE a specific time-off request +app.put('/api/user/time-off-requests/:id', authenticateToken, async (req, res) => { + try { + const { id } = req.params; + const { startDate, endDate, reason } = req.body; + // Ensure users can only edit their own pending requests + const result = await db.run( + `UPDATE time_off_requests + SET start_date = ?, end_date = ?, reason = ? + WHERE id = ? AND user_id = ? AND status = 'pending'`, + [startDate, endDate, reason, id, req.user.id] + ); + if (result.changes === 0) { + return res.status(404).json({ message: "Pending request not found or you don't have permission to edit it." }); + } + res.json({ message: 'Time off request updated successfully.' }); + } catch (err) { + res.status(500).json({ message: 'Error updating time off request.' }); + } +}); + +// NEW: Endpoint to DELETE a specific time-off request +app.delete('/api/user/time-off-requests/:id', authenticateToken, async (req, res) => { + try { + const { id } = req.params; + // Ensure users can only delete their own pending requests + const result = await db.run( + `DELETE FROM time_off_requests + WHERE id = ? AND user_id = ? AND status = 'pending'`, + [id, req.user.id] + ); + if (result.changes === 0) { + return res.status(404).json({ message: "Pending request not found or you don't have permission to delete it." }); + } + res.json({ message: 'Time off request deleted successfully.' }); + } catch (err) { + res.status(500).json({ message: 'Error deleting time off request.' }); + } +}); app.get('/api/admin/notes/:userId', authenticateToken, requireRole('admin'), async (req, res) => { try { const { userId } = req.params;