// js/ui.js // --- IMPORTS --- 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 --- let employeeTimerInterval = null; let adminTimerIntervals = []; 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() { // This function was empty, it has been corrected. try { const storedUser = localStorage.getItem('user'); const authToken = localStorage.getItem('authToken'); const user = storedUser ? JSON.parse(storedUser) : null; if (authToken && user) { navUserControls.classList.remove('hidden'); welcomeMessage.textContent = `Welcome, ${user.username}`; if (user.role === 'admin') { renderAdminDashboard(); } else { renderEmployeeDashboard(); } } else { navUserControls.classList.add('hidden'); renderAuthView(); } } catch (error) { console.error("Corrupted session data. Clearing and reloading.", error); localStorage.clear(); window.location.reload(); } } // --- 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(); 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; 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

Currently Punched In

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

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.

Employee Notes

`; attachAdminDashboardListeners(); 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 = `
${requests.map(r => ``).join('') || ''}
DatesReasonStatus
${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)}${r.reason || ''}${r.status}
No history found.
`; modalContainer.innerHTML = ``; 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.

'; } } // This function ONLY updates the pending requests table. export function updatePendingRequestsList(requests) { const tableBody = document.querySelector('#tab-content-overview table tbody'); if (!tableBody) return; // Exit if the table isn't on the page if (requests.length === 0) { tableBody.innerHTML = 'No pending requests.'; return; } // Rebuild only the rows of the table tableBody.innerHTML = requests.map(r => ` ${r.username} ${utils.formatDate(r.start_date)} - ${utils.formatDate(r.end_date)} ${r.reason || ''}
`).join(''); }