diff --git a/public/index.html b/public/index.html index 032d926..a9af8eb 100644 --- a/public/index.html +++ b/public/index.html @@ -231,14 +231,12 @@ } } - async function renderAdminDashboard() { +async function renderAdminDashboard() { const [logsRes, usersRes, requestsRes] = await Promise.all([apiCall('/admin/logs'), apiCall('/admin/users'), apiCall('/admin/time-off-requests/pending')]); if (!logsRes.success || !usersRes.success || !requestsRes.success) return; allTimeEntries = logsRes.data; allUsers = usersRes.data; const pendingRequests = requestsRes.data; const employeeTotals = allTimeEntries.reduce((acc, entry) => { const dur = entry.punch_out_time ? (new Date(entry.punch_out_time) - new Date(entry.punch_in_time)) : (Date.now() - new Date(entry.punch_in_time)); acc[entry.username] = (acc[entry.username] || 0) + dur; return acc; }, {}); const punchedInEntries = allTimeEntries.filter(e => e.status === 'in'); - - // Filter out admins from the "leave a note" dropdown const employeesOnly = allUsers.filter(u => u.role === 'employee'); mainViews.admin.innerHTML = ` @@ -246,15 +244,19 @@
| Username | Role | Actions |
|---|---|---|
| ${u.username} | ${u.role} | ${u.isPrimary ? `Primary Admin` : `${u.username !== user.username ? `` : ''}`} |
"${note.note_text}"
+- ${note.admin_username} on ${formatDate(note.created_at)}
+No notes found for this employee.
'; + } + } else { + container.innerHTML = 'Could not load notes.
'; + } +} // --- Event Handlers (no changes needed below this line, but included for completeness) --- async function handleAuthSubmit(e) { e.preventDefault(); const username = e.target.elements.username.value; const password = e.target.elements.password.value; const res = await apiCall('/login', 'POST', { username, password }); if (res.success) { authToken = res.data.token; user = res.data.user; localStorage.setItem('authToken', authToken); localStorage.setItem('user', JSON.stringify(user)); updateUI(); } } const handleSignOut = (message) => { localStorage.clear(); authToken = null; user = null; updateUI(); if (message) showMessage(message, 'success'); }; const handlePunch = () => apiCall('/punch', 'POST').then(res => res.success && renderEmployeeDashboard()); function handleAdminDashboardClick(e) { - const target = e.target; - const { id, userid, username, role } = target.dataset; - if (target.classList.contains('edit-btn')) renderEditModal(id); - if (target.classList.contains('delete-btn') && confirm('Are you sure you want to delete this time entry?')) apiCall(`/admin/logs/${id}`, 'DELETE').then(res => res.success && renderAdminDashboard()); - if (target.classList.contains('force-clock-out-btn') && confirm(`Are you sure you want to force clock out ${username}?`)) apiCall('/admin/force-clock-out', 'POST', { userId: userid }).then(res => res.success && renderAdminDashboard()); - if (target.classList.contains('reset-pw-btn')) renderResetPasswordModal(username); - if (target.classList.contains('change-role-btn')) { const newRole = role === 'admin' ? 'employee' : 'admin'; if(confirm(`Change ${username} to ${newRole}?`)) apiCall('/admin/update-role', 'POST', { username, newRole }).then(res => res.success && renderAdminDashboard()); } - if (target.classList.contains('delete-user-btn') && confirm(`PERMANENTLY DELETE user '${username}' and all their data? This cannot be undone.`)) apiCall(`/admin/delete-user/${username}`, 'DELETE').then(res => res.success && renderAdminDashboard()); - if (target.classList.contains('approve-request-btn')) { if (confirm('Set this request to "approved"?')) apiCall('/admin/update-time-off-status', 'POST', { requestId: id, status: 'approved' }).then(res => res.success && renderAdminDashboard()); } - if (target.classList.contains('deny-request-btn')) { if (confirm('Set this request to "denied"?')) apiCall('/admin/update-time-off-status', 'POST', { requestId: id, status: 'denied' }).then(res => res.success && renderAdminDashboard()); } + const target = e.target; + const { id, userid, username, role, noteId } = target.dataset; + + if (target.classList.contains('edit-btn')) renderEditModal(id); + if (target.classList.contains('delete-btn') && confirm('Are you sure you want to delete this time entry?')) apiCall(`/admin/logs/${id}`, 'DELETE').then(res => res.success && renderAdminDashboard()); + if (target.classList.contains('force-clock-out-btn') && confirm(`Are you sure you want to force clock out ${username}?`)) apiCall('/admin/force-clock-out', 'POST', { userId: userid }).then(res => res.success && renderAdminDashboard()); + if (target.classList.contains('reset-pw-btn')) renderResetPasswordModal(username); + if (target.classList.contains('change-role-btn')) { const newRole = role === 'admin' ? 'employee' : 'admin'; if(confirm(`Change ${username} to ${newRole}?`)) apiCall('/admin/update-role', 'POST', { username, newRole }).then(res => res.success && renderAdminDashboard()); } + if (target.classList.contains('delete-user-btn') && confirm(`PERMANENTLY DELETE user '${username}' and all their data? This cannot be undone.`)) apiCall(`/admin/delete-user/${username}`, 'DELETE').then(res => res.success && renderAdminDashboard()); + if (target.classList.contains('approve-request-btn')) { if (confirm('Set this request to "approved"?')) apiCall('/admin/update-time-off-status', 'POST', { requestId: id, status: 'approved' }).then(res => res.success && renderAdminDashboard()); } + if (target.classList.contains('deny-request-btn')) { if (confirm('Set this request to "denied"?')) apiCall('/admin/update-time-off-status', 'POST', { requestId: id, status: 'denied' }).then(res => res.success && renderAdminDashboard()); } + + // ADDED: Logic to delete a note + if (target.classList.contains('delete-note-btn')) { + if (confirm('Are you sure you want to delete this note?')) { + apiCall(`/admin/notes/${noteId}`, 'DELETE').then(res => { + if (res.success) { + showMessage('Note deleted.', 'success'); + handleViewNotesClick(); // Refresh the notes list + } + }); } + } +} + async function handleEditSubmit(e) { e.preventDefault(); const id = e.target.elements['edit-id'].value; const punch_in_time = new Date(e.target.elements['edit-in'].value).toISOString(); const punch_out_value = e.target.elements['edit-out'].value; const punch_out_time = punch_out_value ? new Date(punch_out_value).toISOString() : null; const res = await apiCall(`/admin/logs/${id}`, 'PUT', { punch_in_time, punch_out_time }); if (res.success) { modalContainer.innerHTML = ''; renderAdminDashboard(); showMessage('Entry updated successfully.', 'success'); } } const handleArchive = () => { if (confirm('Are you sure you want to archive all completed time entries? This action cannot be undone.')) { apiCall('/admin/archive', 'POST').then(res => { if(res.success) { showMessage('Records archived successfully.', 'success'); renderAdminDashboard(); } }); } }; async function handleCreateUser(e) { e.preventDefault(); const username = e.target.elements['new-username'].value; const password = e.target.elements['new-password'].value; const role = e.target.elements['new-user-role'].value; const res = await apiCall('/admin/create-user', 'POST', { username, password, role }); if (res.success) { showMessage(res.data.message, 'success'); e.target.reset(); renderAdminDashboard(); } } diff --git a/server.js b/server.js index 059b66e..9f2bdfc 100644 --- a/server.js +++ b/server.js @@ -387,6 +387,36 @@ app.get('/api/user/notes', authenticateToken, async (req, res) => { } }); +// --- Note Management Routes --- + +// Admin gets all notes for a specific employee +app.get('/api/admin/notes/:userId', authenticateToken, requireRole('admin'), async (req, res) => { + try { + const { userId } = req.params; + const notes = await db.all( + "SELECT id, admin_username, note_text, created_at FROM notes WHERE employee_user_id = ? ORDER BY created_at DESC", + [userId] + ); + res.json(notes); + } catch (err) { + res.status(500).json({ message: 'Failed to fetch notes.' }); + } +}); + +// Admin deletes a specific note +app.delete('/api/admin/notes/:noteId', authenticateToken, requireRole('admin'), async (req, res) => { + try { + const { noteId } = req.params; + const result = await db.run('DELETE FROM notes WHERE id = ?', [noteId]); + + if (result.changes === 0) { + return res.status(404).json({ message: "Note not found." }); + } + res.json({ message: 'Note deleted successfully.' }); + } catch (err) { + res.status(500).json({ message: 'Failed to delete note.' }); + } +}); } startServer();