// js/ui.js // --- IMPORTS --- // Imports functions for server calls, utility formatting, and attaching event listeners. import { apiCall } from './api.js'; import * as utils from './utils.js'; import { attachAuthFormListener, attachAdminDashboardListeners, attachEmployeeDashboardListeners, } from './main.js'; // --- DOM ELEMENT SELECTORS --- 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'); const welcomeMessage = document.getElementById('welcome-message'); const modalContainer = document.getElementById('modal-container'); // --- MODULE-LEVEL STATE --- // These are specific to the UI and manage running timers. let employeeTimerInterval = null; let adminTimerIntervals = []; // This state is populated by renderAdminDashboard and used by its helper functions like renderEditModal let allTimeEntries = []; let allUsers = []; // --- VIEW MANAGEMENT --- export function showView(viewName) { clearInterval(employeeTimerInterval); adminTimerIntervals.forEach(clearInterval); adminTimerIntervals = []; Object.keys(mainViews).forEach(v => mainViews[v].classList.toggle('hidden', v !== viewName)); } // --- MASTER UI UPDATE FUNCTION --- export function updateUI() { try { const storedUser = localStorage.getItem('user'); const authToken = localStorage.getItem('authToken'); const user = storedUser ? JSON.parse(storedUser) : null; if (authToken && user) { // ... (rest of the if block is fine) } else { // ... (rest of the else block is fine) } } catch (error) { // This is the corrected catch block console.error("Corrupted session data. Clearing and reloading.", error); localStorage.clear(); window.location.reload(); // Reload the page to a clean state } } // --- RENDER FUNCTIONS --- export function renderAuthView() { showView('auth'); mainViews.auth.innerHTML = `

Employee Login

`; attachAuthFormListener(); } export async function renderEmployeeDashboard() { showView('employee'); clearInterval(employeeTimerInterval); const [statusRes, timeOffRes, notesRes] = await Promise.all([apiCall('/status'), apiCall('/user/time-off-requests'), apiCall('/user/notes')]); if (!statusRes.success || !timeOffRes.success || !notesRes.success) return; const entries = statusRes.data; const requests = timeOffRes.data; const notes = notesRes.data; const last = entries[0]; const punchedIn = last?.status === 'in'; 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:'} ${utils.formatDateTime(punchedIn ? last.punch_in_time : last?.punch_out_time)}

Notes from Admin

My Account

My Total Hours (This Pay Period)

${utils.formatDecimal(totalMilliseconds)}

Time Off Requests

${requests.map(r => ``).join('') || ''}
DatesReasonStatus
${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)}${r.reason || ''}${r.status}
No upcoming or pending requests.

My Time Log

${entries.map(e => ``).join('') || ''}
InOutDuration (Hours)
${utils.formatDateTime(e.punch_in_time)}${utils.formatDateTime(e.punch_out_time)}${e.status === 'in' ? 'Running...' : utils.formatDecimal(new Date(e.punch_out_time) - new Date(e.punch_in_time))}
No entries.
`; attachEmployeeDashboardListeners(); // Attach all listeners for this view if (punchedIn) { 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 = utils.formatDuration(elapsed); if (totalHoursCell) totalHoursCell.textContent = utils.formatDecimal(totalMilliseconds + elapsed); }, 1000); } } export async function renderAdminDashboard() { showView('admin'); 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; // Update module-level state allTimeEntries = logsRes.data; allUsers = usersRes.data; const pendingRequests = requestsRes.data; const user = JSON.parse(localStorage.getItem('user')); 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'); const employeesOnly = allUsers.filter(u => u.role === 'employee'); mainViews.admin.innerHTML = `

Admin Dashboard

Employee Notes

Currently Punched In

Pending Time Off Requests

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

Hours by Employee

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

Detailed Logs

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

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 ? `` : ''}`}
`; attachAdminDashboardListeners(); // Attach all listeners for this view 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 = utils.formatDuration(Date.now() - punchInTime.getTime()); }, 1000); adminTimerIntervals.push(intervalId); } }); } export function renderArchiveView() { apiCall('/admin/archives').then(res => { if (!res.success) return; showView('archive'); mainViews.archive.innerHTML = `

Archived Logs

${res.data.map(e => ``).join('') || ''}
EmployeeInOutDuration (Hrs)Archived On
${e.username||'N/A'}${utils.formatDateTime(e.punch_in_time)}${utils.formatDateTime(e.punch_out_time)}${utils.formatDecimal(new Date(e.punch_out_time) - new Date(e.punch_in_time))}${utils.formatDateTime(e.archived_at)}
No archived entries found.
`; document.getElementById('back-to-dash-btn').addEventListener('click', renderAdminDashboard); }); } export function renderTimeOffHistoryView() { apiCall('/admin/time-off-requests/history').then(res => { if (!res.success) return; showView('timeOffHistory'); mainViews.timeOffHistory.innerHTML = `

Time Off History

${res.data.map(r => ``).join('') || ''}
EmployeeDatesReasonStatus
${r.username}${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)}${r.reason||''}${r.status}
No history.
`; document.getElementById('back-to-dash-btn').addEventListener('click', renderAdminDashboard); }); } // --- MODAL RENDER FUNCTIONS --- function renderModal(title, formHTML, submitHandler) { 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 = ''; }); } export function renderEditModal(id, submitHandler) { const entry = allTimeEntries.find(e => e.id == id); if (!entry) { utils.showMessage('Could not find entry to edit.', 'error'); return; } const formHTML = `
`; renderModal(`Edit Entry for ${entry.username}`, formHTML, submitHandler); } export function renderChangePasswordModal(submitHandler) { const formHTML = ``; renderModal('Change My Password', formHTML, submitHandler); } export function renderResetPasswordModal(username, submitHandler) { const formHTML = ``; renderModal(`Reset Password for ${username}`, formHTML, submitHandler); } export function renderRequestHistoryModal(requests) { const modalBody = `...`; // Same as your original function modalContainer.innerHTML = `...`; // Same as your original function document.querySelector('.cancel-modal-btn').addEventListener('click', () => modalContainer.innerHTML = ''); document.querySelector('.modal-overlay').addEventListener('click', (e) => { if (e.target === e.currentTarget) modalContainer.innerHTML = ''; }); } // --- UI HELPER FUNCTIONS --- export async function handleViewNotesClick() { const userId = document.getElementById('note-user-select').value; const container = document.getElementById('employee-notes-container'); if (!userId) { return utils.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.

'; } }