From 4299738f37cc38c88728407e569448cc7077629e Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 2 Aug 2025 08:40:42 -0400 Subject: [PATCH] fix? --- public/index.html | 411 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 322 insertions(+), 89 deletions(-) diff --git a/public/index.html b/public/index.html index 74dbfdb..d90874f 100644 --- a/public/index.html +++ b/public/index.html @@ -52,19 +52,17 @@ const mainViews = { auth: document.getElementById('auth-view'), employee: document.getElementById('employee-dashboard'), admin: document.getElementById('admin-dashboard'), archive: document.getElementById('admin-archive-view'), timeOffHistory: document.getElementById('admin-time-off-history-view') }; const navUserControls = document.getElementById('nav-user-controls'), welcomeMessage = document.getElementById('welcome-message'), signOutBtn = document.getElementById('sign-out-btn'); const messageBox = document.getElementById('message-box'), loadingSpinner = document.getElementById('loading-spinner'), modalContainer = document.getElementById('modal-container'); - - // **FIXED**: Declared variables safely without parsing immediately. This prevents the script from crashing. let authToken, user, allTimeEntries = [], allUsers = [], employeeTimerInterval = null; - + // --- Helper Functions --- - const showLoading = (show) => loadingSpinner.innerHTML = show ? `
` : ''; + const showLoading = (show) => { loadingSpinner.innerHTML = show ? `
` : ''; }; const showMessage = (message, type = 'success') => { messageBox.innerHTML = ``; messageBox.classList.remove('hidden'); }; - const formatDecimal = (ms) => ms ? (ms / 3600000).toFixed(2) : 'N/A'; + const formatDecimal = (ms) => ms ? (ms / 3600000).toFixed(2) : '0.00'; const formatDateTime = (s) => s ? new Date(s).toLocaleString() : 'N/A'; const formatDate = (s) => s ? new Date(s).toLocaleDateString() : 'N/A'; const toLocalISO = (d) => { if (!d) return ''; const date = new Date(d); return new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 16); }; - const formatDuration = (ms) => { if (!ms || ms < 0) return '00:00:00'; const s = Math.floor(ms / 1000); const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`; }; - + const formatDuration = (ms) => { if (!ms || ms < 0) return '00:00:00'; const totalSeconds = Math.floor(ms / 1000); const h = Math.floor(totalSeconds / 3600); const m = Math.floor((totalSeconds % 3600) / 60); const s = totalSeconds % 60; return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; }; + // --- API Calls --- const API_BASE_URL = '/api'; async function apiCall(endpoint, method = 'GET', body = null) { @@ -73,35 +71,61 @@ try { showLoading(true); const response = await fetch(`${API_BASE_URL}${endpoint}`, { method, headers, body: body ? JSON.stringify(body) : null }); - if (response.status === 401) { handleSignOut('Your session has expired. Please log in again.'); return { success: false }; } - const text = await response.text(); - if (!response.ok) { - const errorData = text ? JSON.parse(text) : {}; - throw new Error(errorData.message || `HTTP error ${response.status}`); + + if (response.status === 401) { + handleSignOut('Your session has expired. Please log in again.'); + return { success: false }; } - return { success: true, data: text ? JSON.parse(text) : null }; - } catch (error) { - showMessage(error.message, 'error'); + + const text = await response.text(); + let data; + + try { + data = text ? JSON.parse(text) : null; + } catch (error) { + if (!response.ok) { + throw new Error(text || `Request failed with status ${response.status}`); + } + return { success: true, data: text }; + } + + if (!response.ok) { + throw new Error(data.message || `An unknown error occurred.`); + } + + return { success: true, data }; + + } catch (error) { + showMessage(error.message, 'error'); return { success: false }; - } finally { - showLoading(false); + } finally { + showLoading(false); } } - + // --- View Management --- - const showView = (viewName) => { clearInterval(employeeTimerInterval); Object.keys(mainViews).forEach(v => mainViews[v].classList.toggle('hidden', v !== viewName)); } - + const showView = (viewName) => { + clearInterval(employeeTimerInterval); + Object.keys(mainViews).forEach(v => mainViews[v].classList.toggle('hidden', v !== viewName)); + } + // --- UI Rendering --- function updateUI() { try { const storedUser = localStorage.getItem('user'); authToken = localStorage.getItem('authToken'); user = storedUser ? JSON.parse(storedUser) : null; - + if (authToken && user) { navUserControls.classList.remove('hidden'); welcomeMessage.textContent = `Welcome, ${user.username}`; - user.role === 'admin' ? (showView('admin'), renderAdminDashboard()) : (showView('employee'), renderEmployeeDashboard()); + if (user.role === 'admin') { + showView('admin'); + renderAdminDashboard(); + } else { + showView('employee'); + renderEmployeeDashboard(); + } } else { navUserControls.classList.add('hidden'); showView('auth'); @@ -112,41 +136,63 @@ handleSignOut("There was an error loading your session."); } } - + function renderAuthView() { - mainViews.auth.innerHTML = `

Employee Login

`; + mainViews.auth.innerHTML = ` +
+

Employee Login

+
+ + + +
+
`; document.getElementById('auth-form').addEventListener('submit', handleAuthSubmit); } - + async function renderEmployeeDashboard() { clearInterval(employeeTimerInterval); const [statusRes, timeOffRes] = await Promise.all([apiCall('/status'), apiCall('/user/time-off-requests')]); if (!statusRes.success || !timeOffRes.success) return; - + const entries = statusRes.data; const requests = timeOffRes.data; const last = entries[0]; const punchedIn = last?.status === 'in'; - let totalMilliseconds = entries.reduce((acc, e) => e.status === 'out' ? acc + (new Date(e.punch_out_time) - new Date(e.punch_in_time)) : acc, 0); - + let totalMilliseconds = entries.reduce((acc, e) => { + return e.status === 'out' && e.punch_out_time ? acc + (new Date(e.punch_out_time) - new Date(e.punch_in_time)) : acc; + }, 0); + mainViews.employee.innerHTML = `
-

Current Status

${punchedIn ? 'Punched In' : 'Punched Out'}

${punchedIn ? 'Since:' : 'Last Punch:'} ${formatDateTime(punchedIn ? last.punch_in_time : last?.punch_out_time)}

-
+
+

Current Status

+

${punchedIn ? 'Punched In' : 'Punched Out'}

+

${punchedIn ? 'Since:' : 'Last Punch:'} ${formatDateTime(punchedIn ? last.punch_in_time : last?.punch_out_time)}

+
+
+ +
-

My Account

-

My Total Hours

${formatDecimal(totalMilliseconds)}

+
+

My Account

+ +
+
+

My Total Hours (This Pay Period)

+

${formatDecimal(totalMilliseconds)}

+

Time Off Requests

- +
${requests.map(r => ``).join('') || ''}
DatesReasonStatus
${formatDate(r.start_date)} - ${formatDate(r.end_date)}${r.reason || ''}${r.status}
No requests.
@@ -159,36 +205,81 @@ document.getElementById('punch-btn').addEventListener('click', handlePunch); document.getElementById('change-password-btn').addEventListener('click', renderChangePasswordModal); document.getElementById('time-off-form').addEventListener('submit', handleTimeOffRequest); - + if (punchedIn) { - const durationCell = document.getElementById(`duration-${last.id}`), totalHoursCell = document.getElementById('employee-total-hours'), punchInTime = new Date(last.punch_in_time); - employeeTimerInterval = setInterval(() => { const elapsed = new Date() - punchInTime; durationCell.textContent = formatDuration(elapsed); totalHoursCell.textContent = formatDecimal(totalMilliseconds + elapsed); }, 1000); + const durationCell = document.getElementById(`duration-${last.id}`); + const totalHoursCell = document.getElementById('employee-total-hours'); + const punchInTime = new Date(last.punch_in_time); + employeeTimerInterval = setInterval(() => { + const elapsed = Date.now() - punchInTime.getTime(); + if (durationCell) durationCell.textContent = formatDuration(elapsed); + if (totalHoursCell) totalHoursCell.textContent = formatDecimal(totalMilliseconds + elapsed); + }, 1000); } } - + 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.status === 'out' ? new Date(entry.punch_out_time) - new Date(entry.punch_in_time) : new Date() - new Date(entry.punch_in_time)); acc[entry.username] = (acc[entry.username] || 0) + dur; return acc; }, {}); + + 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'); - + mainViews.admin.innerHTML = `
-

Admin Dashboard

-

Currently Punched In

    ${punchedInEntries.map(e => ` +
    +
    +

    Admin Dashboard

    +
    + + +
    +
    +
    +
    +

    Currently Punched In

    +
      ${punchedInEntries.map(e => `
    • ${e.username}
      Since: ${formatDateTime(e.punch_in_time)} - +
      -
    • - `).join('') || '
    • None
    • '}
    -

    Pending Time Off Requests

    ${pendingRequests.map(r => ``).join('') || ''}
    EmployeeDatesReasonActions
    ${r.username}${formatDate(r.start_date)} - ${formatDate(r.end_date)}${r.reason||''}
    No pending requests.
    -

    Hours by Employee

    ${Object.keys(employeeTotals).map(u => ``).join('')}
    EmployeeTotal Hours
    ${u}${formatDecimal(employeeTotals[u])}
    -

    Detailed Logs

    ${allTimeEntries.map(e => ``).join('')}
    EmployeeInOutDurationActions
    ${e.username||'N/A'}${formatDateTime(e.punch_in_time)}${formatDateTime(e.punch_out_time)}${formatDecimal(new Date(e.punch_out_time) - new Date(e.punch_in_time))}
    -

    User & Payroll Management

    Create User

    Add Manual Punch

    Manage Users

    ${allUsers.map(u => ``).join('')}
    UsernameRoleActions
    ${u.username}${u.role}${u.isPrimary ? `Primary Admin` : `${u.username !== user.username ? `` : ''}`}
    + `).join('') || '
  • None
  • '} +
+
+
+
+

Pending Time Off Requests

+ +
+
${pendingRequests.map(r => ``).join('') || ''}
EmployeeDatesReasonActions
${r.username}${formatDate(r.start_date)} - ${formatDate(r.end_date)}${r.reason||''}
No pending requests.
+
+
+

Hours by Employee

+
${Object.entries(employeeTotals).map(([username, totalMs]) => ``).join('') || ''}
EmployeeTotal Hours
${username}${formatDecimal(totalMs)}
No data.
+
+
+

Detailed Logs

+
${allTimeEntries.map(e => ``).join('')}
EmployeeInOutDurationActions
${e.username||'N/A'}${formatDateTime(e.punch_in_time)}${formatDateTime(e.punch_out_time)}${e.punch_out_time ? formatDecimal(new Date(e.punch_out_time) - new Date(e.punch_in_time)) : 'Running'}
+
+
+

User & Payroll Management

+
+

Create User

+

Add Manual Punch

+
+

Manage Users

${allUsers.map(u => ``).join('')}
UsernameRoleActions
${u.username}${u.role}${u.isPrimary ? `Primary Admin` : `${u.username !== user.username ? `` : ''}`}
+
`; document.getElementById('archive-btn').addEventListener('click', handleArchive); document.getElementById('view-archives-btn').addEventListener('click', renderArchiveView); @@ -197,12 +288,23 @@ document.getElementById('add-punch-form').addEventListener('submit', handleAddPunch); document.getElementById('admin-dashboard').addEventListener('click', handleAdminDashboardClick); } - + function renderArchiveView() { apiCall('/admin/archives').then(res => { if (!res.success) return; showView('archive'); - mainViews.archive.innerHTML = `

Archived Logs

${res.data.map(e => ``).join('') || ''}
EmployeeInOutDurationArchived On
${e.username||'N/A'}${formatDateTime(e.punch_in_time)}${formatDateTime(e.punch_out_time)}${formatDecimal(new Date(e.punch_out_time) - new Date(e.punch_in_time))}${formatDateTime(e.archived_at)}
No archived entries found.
`; + mainViews.archive.innerHTML = ` +
+
+

Archived Logs

+ +
+
+ + ${res.data.map(e => ``).join('') || ''} +
EmployeeInOutDuration (Hrs)Archived On
${e.username||'N/A'}${formatDateTime(e.punch_in_time)}${formatDateTime(e.punch_out_time)}${formatDecimal(new Date(e.punch_out_time) - new Date(e.punch_in_time))}${formatDateTime(e.archived_at)}
No archived entries found.
+
+
`; document.getElementById('back-to-dash-btn').addEventListener('click', () => { showView('admin'); renderAdminDashboard(); }); }); } @@ -213,75 +315,206 @@ showView('timeOffHistory'); mainViews.timeOffHistory.innerHTML = `
-

Time Off History

-
${res.data.map(r => ``).join('') || ''}
EmployeeDatesReasonStatus
${r.username}${formatDate(r.start_date)} - ${formatDate(r.end_date)}${r.reason||''}${r.status}
No history.
+
+

Time Off History

+ +
+
+ + ${res.data.map(r => ``).join('') || ''} +
EmployeeDatesReasonStatus
${r.username}${formatDate(r.start_date)} - ${formatDate(r.end_date)}${r.reason||''}${r.status}
No history.
+
`; document.getElementById('back-to-dash-btn').addEventListener('click', () => { showView('admin'); renderAdminDashboard(); }); }); } function renderModal(title, formHTML, submitHandler) { - modalContainer.innerHTML = ``; + modalContainer.innerHTML = ` + `; document.getElementById('modal-form').addEventListener('submit', submitHandler); document.querySelector('.cancel-modal-btn').addEventListener('click', () => modalContainer.innerHTML = ''); + document.querySelector('.modal-overlay').addEventListener('click', (e) => { if (e.target === e.currentTarget) modalContainer.innerHTML = ''; }); } - + function renderEditModal(id) { const entry = allTimeEntries.find(e => e.id == id); - const formHTML = `
`; - renderModal('Edit Time Entry', formHTML, handleEditSubmit); + if (!entry) { showMessage('Could not find entry to edit.', 'error'); return; } + const formHTML = ` + +
+
`; + renderModal(`Edit Entry for ${entry.username}`, formHTML, handleEditSubmit); } function renderChangePasswordModal() { - const formHTML = ``; + const formHTML = ` + + `; renderModal('Change My Password', formHTML, handleChangePassword); } - + function renderResetPasswordModal(username) { - const formHTML = ``; + const formHTML = ` + + `; renderModal(`Reset Password for ${username}`, formHTML, handleResetPassword); } - + // --- Event Handlers --- - async function handleAuthSubmit(e) { e.preventDefault(); const username = e.target.elements.username.value, 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, 'error'); }; + 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('Delete entry?')) apiCall(`/admin/logs/${id}`, 'DELETE').then(res => res.success && renderAdminDashboard()); - if (target.classList.contains('force-clock-out-btn') && confirm('Force clock out?')) apiCall('/admin/force-clock-out', 'POST', { userId: userid }).then(res => res.success && renderAdminDashboard()); + 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()); } + 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()); } } - - async function handleEditSubmit(e) { e.preventDefault(); const id = e.target.elements['edit-id'].value, punch_in_time = new Date(e.target.elements['edit-in'].value).toISOString(), punch_out_time = e.target.elements['edit-out'].value ? new Date(e.target.elements['edit-out'].value).toISOString() : null; const res = await apiCall(`/admin/logs/${id}`, 'PUT', { punch_in_time, punch_out_time }); if (res.success) { modalContainer.innerHTML = ''; renderAdminDashboard(); } } - const handleArchive = () => confirm('Archive all completed entries?') && apiCall('/admin/archive', 'POST').then(res => res.success && renderAdminDashboard()); - async function handleCreateUser(e) { e.preventDefault(); const username = e.target.elements['new-username'].value, password = e.target.elements['new-password'].value, 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(); } } - async function handleChangePassword(e) { e.preventDefault(); const currentPassword = e.target.elements['modal-current-pw'].value, newPassword = e.target.elements['modal-new-pw'].value; const res = await apiCall('/user/change-password', 'POST', { currentPassword, newPassword }); if (res.success) { showMessage(res.data.message, 'success'); modalContainer.innerHTML = ''; } } - async function handleResetPassword(e) { e.preventDefault(); const username = e.target.elements['reset-username'].value, newPassword = e.target.elements['reset-new-pw'].value; const res = await apiCall('/admin/reset-password', 'POST', { username, newPassword }); if (res.success) { showMessage(res.data.message, 'success'); modalContainer.innerHTML = ''; } } - async function handleAddPunch(e) { e.preventDefault(); const selected = e.target.elements['add-punch-user']; const userId = selected.value; const username = selected.options[selected.selectedIndex].dataset.username; const punchInTime = new Date(e.target.elements['add-punch-in'].value).toISOString(); const punchOutTime = new Date(e.target.elements['add-punch-out'].value).toISOString(); const res = await apiCall('/admin/add-punch', 'POST', { userId, username, punchInTime, punchOutTime }); if (res.success) { showMessage(res.data.message, 'success'); e.target.reset(); renderAdminDashboard(); } } - const handleViewArchivesBtn = renderArchiveView; - async function handleTimeOffRequest(e) { e.preventDefault(); const startDate = e.target.elements['start-date'].value, endDate = e.target.elements['end-date'].value, reason = e.target.elements['reason'].value; const res = await apiCall('/user/request-time-off', 'POST', { startDate, endDate, reason }); if (res.success) { showMessage(res.data.message, 'success'); e.target.reset(); renderEmployeeDashboard(); } } - - // --- Initializer --- - signOutBtn.addEventListener('click', () => handleSignOut()); - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { - console.log("Tab is visible, refreshing UI."); - updateUI(); - } -}); - updateUI(); - + 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(); + } + } + + async function handleChangePassword(e) { + e.preventDefault(); + const currentPassword = e.target.elements['modal-current-pw'].value; + const newPassword = e.target.elements['modal-new-pw'].value; + const res = await apiCall('/user/change-password', 'POST', { currentPassword, newPassword }); + if (res.success) { + showMessage(res.data.message, 'success'); + modalContainer.innerHTML = ''; + } + } + + async function handleResetPassword(e) { + e.preventDefault(); + const username = e.target.elements['reset-username'].value; + const newPassword = e.target.elements['reset-new-pw'].value; + const res = await apiCall('/admin/reset-password', 'POST', { username, newPassword }); + if (res.success) { + showMessage(res.data.message, 'success'); + modalContainer.innerHTML = ''; + } + } + + async function handleAddPunch(e) { + e.preventDefault(); + const selected = e.target.elements['add-punch-user']; + const userId = selected.value; + const username = selected.options[selected.selectedIndex].dataset.username; + const punchInTime = new Date(e.target.elements['add-punch-in'].value).toISOString(); + const punchOutTime = new Date(e.target.elements['add-punch-out'].value).toISOString(); + const res = await apiCall('/admin/add-punch', 'POST', { userId, username, punchInTime, punchOutTime }); + if (res.success) { + showMessage(res.data.message, 'success'); + e.target.reset(); + renderAdminDashboard(); + } + } + + async function handleTimeOffRequest(e) { + e.preventDefault(); + const startDate = e.target.elements['start-date'].value; + const endDate = e.target.elements['end-date'].value; + const reason = e.target.elements['reason'].value; + if (new Date(endDate) < new Date(startDate)) { + return showMessage('End date cannot be before start date.', 'error'); + } + const res = await apiCall('/user/request-time-off', 'POST', { startDate, endDate, reason }); + if (res.success) { + showMessage(res.data.message, 'success'); + e.target.reset(); + renderEmployeeDashboard(); + } + } + + // --- Initializer --- + signOutBtn.addEventListener('click', () => handleSignOut('You have been signed out.')); + + // Adds a listener to refresh data when the tab becomes visible again + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible' && user) { + console.log("Tab is visible, refreshing UI."); + updateUI(); + } + }); + + // Initial load + updateUI(); + \ No newline at end of file