From e6f47550d408713744b143102424dc2ca1c704a1 Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 2 Aug 2025 09:55:30 -0400 Subject: [PATCH] add delete noted --- public/index.html | 87 ++++++++++++++++++++++++++++++++++++----------- server.js | 30 ++++++++++++++++ 2 files changed, 97 insertions(+), 20 deletions(-) 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 @@

Admin Dashboard

-

Leave a Note for Employee

+

Employee Notes

- - + +
+ + +
+

Currently Punched In

@@ -264,11 +266,10 @@

User & Payroll Management

Create User

Add Manual Entry

Manage Users

${allUsers.map(u => ``).join('')}
UsernameRoleActions
${u.username}${u.role}
${u.isPrimary ? `Primary Admin` : `${u.username !== user.username ? `` : ''}`}
`; punchedInEntries.forEach(entry => { const durationCell = document.getElementById(`admin-duration-${entry.id}`); if (durationCell) { const punchInTime = new Date(entry.punch_in_time); const intervalId = setInterval(() => { durationCell.textContent = formatDuration(Date.now() - punchInTime.getTime()); }, 1000); adminTimerIntervals.push(intervalId); } }); - document.getElementById('archive-btn').addEventListener('click', handleArchive); document.getElementById('view-archives-btn').addEventListener('click', renderArchiveView); document.getElementById('view-time-off-history-btn').addEventListener('click', renderTimeOffHistoryView); document.getElementById('create-user-form').addEventListener('submit', handleCreateUser); document.getElementById('add-punch-form').addEventListener('submit', handleAddPunch); - document.getElementById('add-note-form').addEventListener('submit', handleAddNote); // Add listener for new form + document.getElementById('archive-btn').addEventListener('click', handleArchive); document.getElementById('view-archives-btn').addEventListener('click', renderArchiveView); document.getElementById('view-time-off-history-btn').addEventListener('click', renderTimeOffHistoryView); document.getElementById('create-user-form').addEventListener('submit', handleCreateUser); document.getElementById('add-punch-form').addEventListener('submit', handleAddPunch); document.getElementById('add-note-form').addEventListener('submit', handleAddNote); + document.getElementById('view-notes-btn').addEventListener('click', handleViewNotesClick); // Add listener for new button document.getElementById('admin-dashboard').addEventListener('click', handleAdminDashboardClick); -} - +} function renderArchiveView() { apiCall('/admin/archives').then(res => { if (!res.success) return; @@ -313,22 +314,68 @@ renderModal(`Reset Password for ${username}`, formHTML, handleResetPassword); } + async function handleViewNotesClick() { + const userId = document.getElementById('note-user-select').value; + const container = document.getElementById('employee-notes-container'); + if (!userId) { + return showMessage('Please select an employee to view their notes.', 'error'); + } + + container.innerHTML = 'Loading notes...'; + const res = await apiCall(`/admin/notes/${userId}`); + + if (res.success) { + if (res.data.length > 0) { + container.innerHTML = ` +

Showing Notes for ${document.getElementById('note-user-select').options[document.getElementById('note-user-select').selectedIndex].text}

+ `; + } else { + container.innerHTML = '

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();