My Time Log
@@ -211,7 +202,6 @@ const showView = (viewName) => {
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}`);
const totalHoursCell = document.getElementById('employee-total-hours');
@@ -225,105 +215,30 @@ const showView = (viewName) => {
}
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');
-
- mainViews.admin.innerHTML = `
-
-
-
-
Admin Dashboard
-
-
-
-
-
-
-
-
Currently Punched In
-
-
-
-
-
Pending Time Off Requests
-
-
-
| Employee | Dates | Reason | Actions |
${pendingRequests.map(r => `| ${r.username} | ${formatDate(r.start_date)} - ${formatDate(r.end_date)} | ${r.reason||''} | |
`).join('') || '| No pending requests. |
'}
-
-
-
Hours by Employee
-
| Employee | Total Hours |
${Object.entries(employeeTotals).map(([username, totalMs]) => `| ${username} | ${formatDecimal(totalMs)} |
`).join('') || '| No data. |
'}
-
-
-
Detailed Logs
-
| Employee | In | Out | Duration | Actions |
${allTimeEntries.map(e => `| ${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)) + ' hrs' : '...'} | |
`).join('')}
-
-
-
User & Payroll Management
-
-
-
-
-
Manage Users
| Username | Role | Actions |
${allUsers.map(u => `| ${u.username} | ${u.role} | ${u.isPrimary ? `Primary Admin` : `${u.username !== user.username ? `` : ''}`} |
`).join('')}
-
-
`;
-
- // ** NEW: Start timers for all currently punched-in users **
- 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); // Store the timer to clear it later
+ 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');
+ mainViews.admin.innerHTML = `
+
+
Admin Dashboard
+
+
Pending Time Off Requests
| Employee | Dates | Reason | Actions |
${pendingRequests.map(r => `| ${r.username} | ${formatDate(r.start_date)} - ${formatDate(r.end_date)} | ${r.reason||''} | |
`).join('') || '| No pending requests. |
'}
+
Hours by Employee
| Employee | Total Hours |
${Object.entries(employeeTotals).map(([username, totalMs]) => `| ${username} | ${formatDecimal(totalMs)} |
`).join('') || '| No data. |
'}
+
Detailed Logs
| Employee | In | Out | Duration | Actions |
${allTimeEntries.map(e => `| ${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)) + ' hrs' : '...'} | ${/* UPDATED */''} |
`).join('')}
+
User & Payroll Management
Manage Users
| Username | Role | Actions |
${allUsers.map(u => `| ${u.username} | ${u.role} | ${/* UPDATED */''}${u.isPrimary ? `Primary Admin` : `${u.username !== user.username ? `` : ''}`} |
`).join('')}
+
`;
+ 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('admin-dashboard').addEventListener('click', handleAdminDashboardClick);
}
- });
-
- 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('admin-dashboard').addEventListener('click', handleAdminDashboardClick);
-}
function renderArchiveView() {
apiCall('/admin/archives').then(res => {
if (!res.success) return;
showView('archive');
mainViews.archive.innerHTML = `
-
-
-
Archived Logs
-
-
-
-
| Employee | In | Out | Duration (Hrs) | Archived On |
- ${res.data.map(e => `| ${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)} |
`).join('') || '| No archived entries found. |
'}
-
-
-
`;
+
Archived Logs
| Employee | In | Out | Duration (Hrs) | Archived On |
${res.data.map(e => `| ${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)} |
`).join('') || '| No archived entries found. |
'}
`;
document.getElementById('back-to-dash-btn').addEventListener('click', () => { showView('admin'); renderAdminDashboard(); });
});
}
@@ -333,34 +248,13 @@ const showView = (viewName) => {
if (!res.success) return;
showView('timeOffHistory');
mainViews.timeOffHistory.innerHTML = `
-
-
-
Time Off History
-
-
-
-
| Employee | Dates | Reason | Status |
- ${res.data.map(r => `| ${r.username} | ${formatDate(r.start_date)} - ${formatDate(r.end_date)} | ${r.reason||''} | ${r.status} |
`).join('') || '| No history. |
'}
-
-
-
`;
+
Time Off History
| Employee | Dates | Reason | Status |
${res.data.map(r => `| ${r.username} | ${formatDate(r.start_date)} - ${formatDate(r.end_date)} | ${r.reason||''} | ${r.status} |
`).join('') || '| 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 = ''; });
@@ -369,56 +263,27 @@ const showView = (viewName) => {
function renderEditModal(id) {
const entry = allTimeEntries.find(e => e.id == id);
if (!entry) { showMessage('Could not find entry to edit.', 'error'); return; }
- const formHTML = `
-
-
-
`;
+ 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;
- 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');
- };
-
+ // --- 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());
@@ -428,111 +293,18 @@ const showView = (viewName) => {
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;
- 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();
- }
- }
+ 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();
+ document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && user) { console.log("Tab is visible, refreshing UI."); updateUI(); } });
+ updateUI(); // Initial load