add admin time off edit fuctionaliy

This commit is contained in:
chris 2025-08-10 10:00:01 -04:00
parent 2a7f0b5762
commit 772f034914
3 changed files with 105 additions and 8 deletions

View File

@ -206,6 +206,40 @@ function handleAdminDashboardClick(e) {
return; return;
} }
if (target.classList.contains('set-pending-btn')) {
if (confirm('Are you sure you want to move this request back to pending?')) {
apiCall('/admin/update-time-off-status', 'POST', { requestId: id, status: 'pending' })
.then(res => {
if (res.success) {
showMessage('Request status set to pending.', 'success');
renderTimeOffHistoryView();
}
});
}
return;
}
if (target.classList.contains('admin-delete-request-btn')) {
if (confirm('Are you sure you want to permanently delete this request? This cannot be undone.')) {
apiCall(`/admin/time-off-requests/${id}`, 'DELETE')
.then(res => {
if (res.success) {
showMessage('Request deleted.', 'success');
if (document.getElementById('tab-content-overview')) {
apiCall('/admin/time-off-requests/pending').then(requestsRes => {
if (requestsRes.success) {
updatePendingRequestsList(requestsRes.data);
}
});
} else {
renderTimeOffHistoryView();
}
}
});
}
return;
}
if (target.classList.contains('edit-btn')) renderEditModal(id, handleEditSubmit); if (target.classList.contains('edit-btn')) renderEditModal(id, handleEditSubmit);
if (target.classList.contains('delete-btn') && confirm('Delete this time entry?')) apiCall(`/admin/logs/${id}`, 'DELETE').then(res => res.success && renderAdminDashboard()); if (target.classList.contains('delete-btn') && confirm('Delete this time entry?')) apiCall(`/admin/logs/${id}`, 'DELETE').then(res => res.success && renderAdminDashboard());
if (target.classList.contains('force-clock-out-btn') && confirm(`Force clock out ${username}?`)) apiCall('/admin/force-clock-out', 'POST', { userId: userid }).then(res => res.success && renderAdminDashboard()); if (target.classList.contains('force-clock-out-btn') && confirm(`Force clock out ${username}?`)) apiCall('/admin/force-clock-out', 'POST', { userId: userid }).then(res => res.success && renderAdminDashboard());
@ -215,7 +249,6 @@ 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(); }});}} 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) { async function handleEditTimeOffSubmit(e) {
e.preventDefault(); e.preventDefault();
const id = e.target.elements['edit-request-id'].value; const id = e.target.elements['edit-request-id'].value;
@ -231,7 +264,7 @@ async function handleEditTimeOffSubmit(e) {
if (res.success) { if (res.success) {
showMessage(res.data.message, 'success'); showMessage(res.data.message, 'success');
document.getElementById('modal-container').innerHTML = ''; document.getElementById('modal-container').innerHTML = '';
renderEmployeeDashboard(); // Refresh the dashboard to show changes renderEmployeeDashboard();
} }
} }
@ -241,12 +274,10 @@ export function attachAuthFormListener() {
if (form) form.addEventListener('submit', handleAuthSubmit); if (form) form.addEventListener('submit', handleAuthSubmit);
} }
// --- UPDATED: Employee dashboard listeners now use event delegation ---
export function attachEmployeeDashboardListeners() { export function attachEmployeeDashboardListeners() {
const dashboard = document.getElementById('employee-dashboard'); const dashboard = document.getElementById('employee-dashboard');
if (!dashboard) return; if (!dashboard) return;
// Use one listener for the entire dashboard
dashboard.addEventListener('click', async (e) => { dashboard.addEventListener('click', async (e) => {
const target = e.target; const target = e.target;
@ -271,7 +302,6 @@ export function attachEmployeeDashboardListeners() {
} }
if (target.classList.contains('edit-request-btn')) { if (target.classList.contains('edit-request-btn')) {
const id = target.dataset.id; 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'); const res = await apiCall('/user/time-off-requests/history');
if (res.success) { if (res.success) {
const requestToEdit = res.data.find(r => r.id == id); const requestToEdit = res.data.find(r => r.id == id);
@ -282,7 +312,6 @@ export function attachEmployeeDashboardListeners() {
} }
}); });
// Handle form submissions separately
const timeOffForm = document.getElementById('time-off-form'); const timeOffForm = document.getElementById('time-off-form');
if (timeOffForm) { if (timeOffForm) {
timeOffForm.addEventListener('submit', handleTimeOffRequest); timeOffForm.addEventListener('submit', handleTimeOffRequest);

View File

@ -201,7 +201,30 @@ export async function renderAdminDashboard() {
<div id="admin-tabs-content" class="bg-white rounded-b-lg shadow-md p-6"> <div id="admin-tabs-content" class="bg-white rounded-b-lg shadow-md p-6">
<div id="tab-content-overview" class="space-y-8"> <div id="tab-content-overview" class="space-y-8">
<div><h3 class="text-xl font-bold text-gray-700 mb-2">Currently Punched In</h3><ul class="border rounded-lg divide-y">${punchedInEntries.map(e => `<li class="flex flex-col items-start space-y-2 p-3 sm:flex-row sm:items-center sm:justify-between sm:space-y-0"><span class="font-medium text-gray-800">${e.username}</span><div class="flex items-center space-x-4"><span class="text-sm text-gray-500">Since: ${utils.formatDateTime(e.punch_in_time)}</span><button class="force-clock-out-btn px-3 py-1 text-xs bg-red-500 text-white rounded whitespace-nowrap" data-userid="${e.user_id}" data-username="${e.username}">Force Clock Out</button></div></li>`).join('') || '<li class="p-4 text-center text-gray-500">None</li>'}</ul></div> <div><h3 class="text-xl font-bold text-gray-700 mb-2">Currently Punched In</h3><ul class="border rounded-lg divide-y">${punchedInEntries.map(e => `<li class="flex flex-col items-start space-y-2 p-3 sm:flex-row sm:items-center sm:justify-between sm:space-y-0"><span class="font-medium text-gray-800">${e.username}</span><div class="flex items-center space-x-4"><span class="text-sm text-gray-500">Since: ${utils.formatDateTime(e.punch_in_time)}</span><button class="force-clock-out-btn px-3 py-1 text-xs bg-red-500 text-white rounded whitespace-nowrap" data-userid="${e.user_id}" data-username="${e.username}">Force Clock Out</button></div></li>`).join('') || '<li class="p-4 text-center text-gray-500">None</li>'}</ul></div>
<div><div class="flex justify-between items-center mb-4"><h3 class="text-xl font-bold text-gray-700">Pending Time Off Requests</h3><button id="view-time-off-history-btn" class="px-4 py-2 text-sm bg-gray-200 rounded-lg hover:bg-gray-300">View History</button></div><div class="overflow-x-auto border rounded-lg"><table class="min-w-full text-sm text-left"><thead class="bg-gray-50"><tr><th class="p-2">Employee</th><th class="p-2">Dates</th><th class="p-2">Reason</th><th class="p-2">Actions</th></tr></thead><tbody>${pendingRequests.map(r => `<tr class="border-t"><td class="p-2">${r.username}</td><td class="p-2 whitespace-nowrap">${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)}</td><td class="p-2">${r.reason||''}</td><td class="p-2"><div class="flex flex-col sm:flex-row gap-2"><button class="approve-request-btn font-medium text-green-600 hover:underline" data-id="${r.id}">Approve</button><button class="deny-request-btn font-medium text-red-600 hover:underline" data-id="${r.id}">Deny</button></div></td></tr>`).join('') || '<tr><td colspan="4" class="text-center p-4">No pending requests.</td></tr>'}</tbody></table></div></div> <div>
<div class="flex justify-between items-center mb-4"><h3 class="text-xl font-bold text-gray-700">Pending Time Off Requests</h3><button id="view-time-off-history-btn" class="px-4 py-2 text-sm bg-gray-200 rounded-lg hover:bg-gray-300">View History</button></div>
<div class="overflow-x-auto border rounded-lg">
<table class="min-w-full text-sm text-left">
<thead class="bg-gray-50"><tr><th class="p-2">Employee</th><th class="p-2">Dates</th><th class="p-2">Reason</th><th class="p-2">Actions</th></tr></thead>
<tbody>
${pendingRequests.map(r => `
<tr class="border-t">
<td class="p-2">${r.username}</td>
<td class="p-2 whitespace-nowrap">${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)}</td>
<td class="p-2">${r.reason||''}</td>
<td class="p-2">
<div class="flex flex-col sm:flex-row gap-2">
<button class="approve-request-btn font-medium text-green-600 hover:underline" data-id="${r.id}">Approve</button>
<button class="deny-request-btn font-medium text-red-600 hover:underline" data-id="${r.id}">Deny</button>
<button class="admin-delete-request-btn font-medium text-gray-500 hover:underline" data-id="${r.id}">Delete</button>
</div>
</td>
</tr>
`).join('') || '<tr><td colspan="4" class="text-center p-4">No pending requests.</td></tr>'}
</tbody>
</table>
</div>
</div>
<div> <div>
<h3 class="text-xl font-bold text-gray-700 mb-4">Employee Notes</h3> <h3 class="text-xl font-bold text-gray-700 mb-4">Employee Notes</h3>
<form id="add-note-form" class="space-y-3 bg-gray-50 p-4 rounded-lg"> <form id="add-note-form" class="space-y-3 bg-gray-50 p-4 rounded-lg">
@ -263,7 +286,38 @@ export function renderTimeOffHistoryView() {
if (!res.success) return; if (!res.success) return;
showView('timeOffHistory'); showView('timeOffHistory');
mainViews.timeOffHistory.innerHTML = ` mainViews.timeOffHistory.innerHTML = `
<div class="max-w-6xl mx-auto bg-white rounded-xl shadow-md p-6"><div class="flex justify-between items-center mb-4"><h2 class="text-2xl font-bold">Time Off History</h2><button id="back-to-dash-btn" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">Back to Dashboard</button></div><div class="overflow-x-auto border rounded-lg"><table class="min-w-full text-sm text-left"><thead class="bg-gray-50"><tr><th class="p-2">Employee</th><th class="p-2">Dates</th><th class="p-2">Reason</th><th class="p-2">Status</th></tr></thead><tbody>${res.data.map(r => `<tr class="border-t"><td class="p-2">${r.username}</td><td class="p-2 whitespace-nowrap">${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)}</td><td class="p-2">${r.reason||''}</td><td class="p-2 font-medium capitalize text-${r.status === 'approved' ? 'green' : 'red'}-600">${r.status}</td></tr>`).join('') || '<tr><td colspan="4" class="text-center p-4">No history.</td></tr>'}</tbody></table></div></div>`; <div class="max-w-6xl mx-auto bg-white rounded-xl shadow-md p-6">
<div class="flex justify-between items-center mb-4"><h2 class="text-2xl font-bold">Time Off History</h2><button id="back-to-dash-btn" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">Back to Dashboard</button></div>
<div class="overflow-x-auto border rounded-lg">
<table class="min-w-full text-sm text-left">
<thead class="bg-gray-50">
<tr>
<th class="p-2">Employee</th>
<th class="p-2">Dates</th>
<th class="p-2">Reason</th>
<th class="p-2">Status</th>
<th class="p-2">Actions</th>
</tr>
</thead>
<tbody>
${res.data.map(r => `
<tr class="border-t">
<td class="p-2">${r.username}</td>
<td class="p-2 whitespace-nowrap">${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)}</td>
<td class="p-2">${r.reason||''}</td>
<td class="p-2 font-medium capitalize text-${r.status === 'approved' ? 'green' : 'red'}-600">${r.status}</td>
<td class="p-2">
<div class="flex flex-col sm:flex-row gap-2">
<button class="set-pending-btn font-medium text-blue-600 hover:underline" data-id="${r.id}">Set to Pending</button>
<button class="admin-delete-request-btn font-medium text-red-600 hover:underline" data-id="${r.id}">Delete</button>
</div>
</td>
</tr>
`).join('') || '<tr><td colspan="5" class="text-center p-4">No history.</td></tr>'}
</tbody>
</table>
</div>
</div>`;
document.getElementById('back-to-dash-btn').addEventListener('click', renderAdminDashboard); document.getElementById('back-to-dash-btn').addEventListener('click', renderAdminDashboard);
}); });
} }
@ -374,6 +428,7 @@ export function updatePendingRequestsList(requests) {
<div class="flex flex-col sm:flex-row gap-2"> <div class="flex flex-col sm:flex-row gap-2">
<button class="approve-request-btn font-medium text-green-600 hover:underline" data-id="${r.id}">Approve</button> <button class="approve-request-btn font-medium text-green-600 hover:underline" data-id="${r.id}">Approve</button>
<button class="deny-request-btn font-medium text-red-600 hover:underline" data-id="${r.id}">Deny</button> <button class="deny-request-btn font-medium text-red-600 hover:underline" data-id="${r.id}">Deny</button>
<button class="admin-delete-request-btn font-medium text-gray-500 hover:underline" data-id="${r.id}">Delete</button>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -385,6 +385,19 @@ function setupRoutes() {
} }
}); });
app.delete('/api/admin/time-off-requests/:id', authenticateToken, requireRole('admin'), async (req, res) => {
try {
const { id } = req.params;
const result = await db.run('DELETE FROM time_off_requests WHERE id = ?', [id]);
if (result.changes === 0) {
return res.status(404).json({ message: "Request not found." });
}
res.json({ message: 'Time off request permanently deleted.' });
} catch (err) {
res.status(500).json({ message: 'Error deleting time off request.' });
}
});
app.post('/api/admin/notes', authenticateToken, requireRole('admin'), async (req, res) => { app.post('/api/admin/notes', authenticateToken, requireRole('admin'), async (req, res) => {
try { try {
const { userId, noteText } = req.body; const { userId, noteText } = req.body;