From c7379f5af303e85f51739549518acf2636071294 Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 9 Aug 2025 20:37:52 -0400 Subject: [PATCH] test tabbed interface --- public/js/main.js | 45 ++++++++++++++++++++----- public/js/ui.js | 74 +++++++++++++++++++++++++++++------------- public/style/style.css | 16 +++++++++ 3 files changed, 104 insertions(+), 31 deletions(-) diff --git a/public/js/main.js b/public/js/main.js index 659efb7..5caf147 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -204,20 +204,19 @@ export function attachEmployeeDashboardListeners() { document.getElementById('view-request-history-btn').addEventListener('click', handleViewRequestHistoryClick); } +// In js/main.js + export function attachAdminDashboardListeners() { - // Event delegation for all buttons + // This master listener handles most buttons in the admin view via event delegation document.getElementById('admin-dashboard').addEventListener('click', handleAdminDashboardClick); - // Specific form handlers + // Listeners for specific forms that need to prevent default submission behavior document.getElementById('create-user-form').addEventListener('submit', handleCreateUser); document.getElementById('add-punch-form').addEventListener('submit', handleAddPunch); document.getElementById('add-note-form').addEventListener('submit', handleAddNote); - - // Other top-level buttons - 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('view-notes-btn').addEventListener('click', handleViewNotesClick); + + // Call the function to make the new tabs work + setupTabbedInterface(); } // --- APP INITIALIZER --- @@ -241,6 +240,36 @@ function initializeApp() { } } +// This function handles the logic for switching between tabs +function setupTabbedInterface() { + const tabsContainer = document.getElementById('admin-tabs-nav'); + const contentContainer = document.getElementById('admin-tabs-content'); + + // Exit if the tab elements aren't on the page + if (!tabsContainer || !contentContainer) return; + + // Use event delegation on the tab navigation container + tabsContainer.addEventListener('click', (e) => { + const clickedTab = e.target.closest('.tab-btn'); + // Ignore clicks that aren't on a tab button + if (!clickedTab) return; + + const tabTarget = clickedTab.dataset.tab; + + // Update the active state on tab buttons + tabsContainer.querySelectorAll('.tab-btn').forEach(btn => { + btn.classList.remove('active-tab'); + }); + clickedTab.classList.add('active-tab'); + + // Show the correct content panel and hide the others + contentContainer.querySelectorAll('[id^="tab-content-"]').forEach(panel => { + panel.classList.add('hidden'); + }); + document.getElementById(`tab-content-${tabTarget}`).classList.remove('hidden'); + }); +} + // --- START THE APP --- // Attach global listeners that are always present. document.getElementById('sign-out-btn').addEventListener('click', () => handleSignOut('You have been signed out.')); diff --git a/public/js/ui.js b/public/js/ui.js index 0ca95a3..e03526e 100644 --- a/public/js/ui.js +++ b/public/js/ui.js @@ -147,42 +147,70 @@ export async function renderEmployeeDashboard() { } } +// In js/ui.js + 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 + // --- Existing data processing logic (no changes here) --- 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

    ${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.
-

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 + // --- New Tabbed HTML Structure --- + 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

+
+ + +
+
+
+
+
+ + + + +
+
+ `; + + // The rest of the function remains the same + attachAdminDashboardListeners(); punchedInEntries.forEach(entry => { const durationCell = document.getElementById(`admin-duration-${entry.id}`); if (durationCell) { diff --git a/public/style/style.css b/public/style/style.css index e3c59df..9a3f9f5 100644 --- a/public/style/style.css +++ b/public/style/style.css @@ -28,4 +28,20 @@ body { border-radius: 0.5rem; width: 90%; max-width: 500px; +} + +/* Tab Styles */ +.tab-btn { + /* Smooth transition for color and border changes */ + transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease; + /* A transparent bottom border on all tabs to prevent layout shift */ + border-bottom: 3px solid transparent; +} + +.active-tab { + /* The blue bottom border for the active tab */ + border-color: #2563EB; /* Tailwind's blue-600 */ + /* A darker text color for the active tab to make it stand out */ + color: #1E40AF; /* Tailwind's blue-800 */ + font-weight: 500; } \ No newline at end of file