time-off history
This commit is contained in:
parent
bf3bbc379a
commit
68ba7c4a5e
@ -151,13 +151,12 @@
|
||||
|
||||
async function renderEmployeeDashboard() {
|
||||
clearInterval(employeeTimerInterval);
|
||||
// Fetch notes along with other data
|
||||
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; // Get notes data
|
||||
const notes = notesRes.data;
|
||||
const last = entries[0];
|
||||
const punchedIn = last?.status === 'in';
|
||||
let totalMilliseconds = entries.reduce((acc, e) => {
|
||||
@ -181,36 +180,28 @@
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md p-6">
|
||||
<h3 class="text-xl font-bold text-gray-700 mb-4">Notes from Admin</h3>
|
||||
<ul class="space-y-4 mt-4 max-h-60 overflow-y-auto">
|
||||
${notes.length > 0 ? notes.map(note => `
|
||||
<li class="bg-amber-50 p-3 rounded-lg border-l-4 border-amber-400">
|
||||
<p class="text-gray-800 break-words">"${note.note_text}"</p>
|
||||
<p class="text-xs text-gray-500 text-right mt-2">- ${note.admin_username} on ${formatDate(note.created_at)}</p>
|
||||
</li>
|
||||
`).join('') : '<p class="text-gray-500 text-center">You have no new notes.</p>'}
|
||||
</ul>
|
||||
<ul class="space-y-4 mt-4 max-h-60 overflow-y-auto">${notes.length > 0 ? notes.map(note => `<li class="bg-amber-50 p-3 rounded-lg border-l-4 border-amber-400"><p class="text-gray-800 break-words">"${note.note_text}"</p><p class="text-xs text-gray-500 text-right mt-2">- ${note.admin_username} on ${formatDate(note.created_at)}</p></li>`).join('') : '<p class="text-gray-500 text-center">You have no new notes.</p>'}</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md p-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-xl font-bold text-gray-700">My Account</h3>
|
||||
<button id="change-password-btn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Change Password</button>
|
||||
</div>
|
||||
<div class="mt-4 p-4 bg-blue-50 rounded-lg">
|
||||
<h4 class="font-semibold text-blue-800">My Total Hours (This Pay Period)</h4>
|
||||
<p class="text-3xl font-bold text-blue-600" id="employee-total-hours">${formatDecimal(totalMilliseconds)}</p>
|
||||
</div>
|
||||
<div class="flex justify-between items-center"><h3 class="text-xl font-bold text-gray-700">My Account</h3><button id="change-password-btn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Change Password</button></div>
|
||||
<div class="mt-4 p-4 bg-blue-50 rounded-lg"><h4 class="font-semibold text-blue-800">My Total Hours (This Pay Period)</h4><p class="text-3xl font-bold text-blue-600" id="employee-total-hours">${formatDecimal(totalMilliseconds)}</p></div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md p-6">
|
||||
<h3 class="text-xl font-bold text-gray-700 mb-4">Time Off Requests</h3>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold text-gray-700">Time Off Requests</h3>
|
||||
<button id="view-request-history-btn" class="text-sm px-3 py-1 bg-gray-200 rounded-lg hover:bg-gray-300">View History</button>
|
||||
</div>
|
||||
<form id="time-off-form" class="grid md:grid-cols-3 gap-4 items-end bg-gray-50 p-4 rounded-lg">
|
||||
<div><label class="block text-sm font-medium">Start Date</label><input type="date" id="start-date" class="w-full p-2 border rounded" required></div>
|
||||
<div><label class="block text-sm font-medium">End Date</label><input type="date" id="end-date" class="w-full p-2 border rounded" required></div>
|
||||
<button type="submit" class="w-full bg-indigo-600 text-white p-2 rounded hover:bg-indigo-700">Submit Request</button>
|
||||
<div class="md:col-span-3"><label class="block text-sm font-medium">Reason (optional)</label><input type="text" id="reason" placeholder="e.g., Vacation" class="w-full p-2 border rounded"></div>
|
||||
</form>
|
||||
<div class="mt-4 overflow-x-auto border rounded-lg"><table class="min-w-full text-sm text-left"><thead class="bg-gray-50"><tr><th class="p-2">Dates</th><th class="p-2">Reason</th><th class="p-2">Status</th></tr></thead><tbody>${requests.map(r => `<tr class="border-t"><td class="p-2 whitespace-nowrap">${formatDate(r.start_date)} - ${formatDate(r.end_date)}</td><td class="p-2">${r.reason || ''}</td><td class="p-2 font-medium capitalize text-${r.status === 'approved' ? 'green' : r.status === 'denied' ? 'red' : 'gray'}-600">${r.status}</td></tr>`).join('') || '<tr><td colspan="3" class="text-center p-4">No requests.</td></tr>'}</tbody></table></div>
|
||||
<div class="mt-4 overflow-x-auto border rounded-lg"><table class="min-w-full text-sm text-left"><thead class="bg-gray-50"><tr><th class="p-2">Dates</th><th class="p-2">Reason</th><th class="p-2">Status</th></tr></thead><tbody>${requests.map(r => `<tr class="border-t"><td class="p-2 whitespace-nowrap">${formatDate(r.start_date)} - ${formatDate(r.end_date)}</td><td class="p-2">${r.reason || ''}</td><td class="p-2 font-medium capitalize text-${r.status === 'approved' ? 'green' : r.status === 'denied' ? 'red' : 'gray'}-600">${r.status}</td></tr>`).join('') || '<tr><td colspan="3" class="text-center p-4">No upcoming or pending requests.</td></tr>'}</tbody></table></div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md p-6">
|
||||
<h3 class="text-xl font-bold text-gray-700 mb-2">My Time Log</h3>
|
||||
<div class="overflow-x-auto border rounded-lg"><table class="min-w-full text-sm text-left"><thead class="bg-gray-50"><tr><th class="p-2">In</th><th class="p-2">Out</th><th class="p-2">Duration (Hours)</th></tr></thead><tbody>${entries.map(e => `<tr class="border-t"><td class="p-2">${formatDateTime(e.punch_in_time)}</td><td class="p-2">${formatDateTime(e.punch_out_time)}</td><td class="p-2" id="duration-${e.id}">${e.status === 'in' ? 'Running...' : formatDecimal(new Date(e.punch_out_time) - new Date(e.punch_in_time))}</td></tr>`).join('') || '<tr><td colspan="3" class="text-center p-4">No entries.</td></tr>'}</tbody></table></div>
|
||||
@ -219,6 +210,8 @@
|
||||
document.getElementById('punch-btn').addEventListener('click', handlePunch);
|
||||
document.getElementById('change-password-btn').addEventListener('click', renderChangePasswordModal);
|
||||
document.getElementById('time-off-form').addEventListener('submit', handleTimeOffRequest);
|
||||
document.getElementById('view-request-history-btn').addEventListener('click', handleViewRequestHistoryClick); // Attach handler to new button
|
||||
|
||||
if (punchedIn) {
|
||||
const durationCell = document.getElementById(`duration-${last.id}`);
|
||||
const totalHoursCell = document.getElementById('employee-total-hours');
|
||||
@ -400,6 +393,48 @@ async function renderAdminDashboard() {
|
||||
renderAdminDashboard();
|
||||
}
|
||||
}
|
||||
|
||||
function renderRequestHistoryModal(requests) {
|
||||
const modalBody = `
|
||||
<div class="max-h-[70vh] overflow-y-auto">
|
||||
<table class="min-w-full text-sm text-left">
|
||||
<thead class="bg-gray-50 sticky top-0">
|
||||
<tr><th class="p-2">Dates</th><th class="p-2">Reason</th><th class="p-2">Status</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${requests.map(r => `
|
||||
<tr class="border-t">
|
||||
<td class="p-2 whitespace-nowrap">${formatDate(r.start_date)} - ${formatDate(r.end_date)}</td>
|
||||
<td class="p-2">${r.reason || ''}</td>
|
||||
<td class="p-2 font-medium capitalize text-${r.status === 'approved' ? 'green' : r.status === 'denied' ? 'red' : 'gray'}-600">${r.status}</td>
|
||||
</tr>
|
||||
`).join('') || '<tr><td colspan="3" class="text-center p-4">No history found.</td></tr>'}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>`;
|
||||
|
||||
// Using a simplified modal since we only need a "Close" button
|
||||
modalContainer.innerHTML = `
|
||||
<div class="modal-overlay">
|
||||
<div class="modal-content">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold">Full Time Off History</h3>
|
||||
<button class="cancel-modal-btn font-bold text-2xl">×</button>
|
||||
</div>
|
||||
${modalBody}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
document.querySelector('.cancel-modal-btn').addEventListener('click', () => modalContainer.innerHTML = '');
|
||||
document.querySelector('.modal-overlay').addEventListener('click', (e) => { if (e.target === e.currentTarget) modalContainer.innerHTML = ''; });
|
||||
}
|
||||
|
||||
async function handleViewRequestHistoryClick() {
|
||||
const res = await apiCall('/user/time-off-requests/history');
|
||||
if (res.success) {
|
||||
renderRequestHistoryModal(res.data);
|
||||
}
|
||||
}
|
||||
async function handleAddNote(e) {
|
||||
e.preventDefault();
|
||||
const userId = e.target.elements['note-user-select'].value;
|
||||
|
||||
32
server.js
32
server.js
@ -177,12 +177,34 @@ function setupRoutes() {
|
||||
|
||||
app.get('/api/user/time-off-requests', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const rows = await db.all("SELECT * FROM time_off_requests WHERE user_id = ? ORDER BY start_date DESC", [req.user.id]);
|
||||
res.json(rows);
|
||||
} catch {
|
||||
res.status(500).json({ message: "Failed to fetch requests." });
|
||||
// This query now only gets requests that are pending OR have an end date in the future.
|
||||
const rows = await db.all(
|
||||
`SELECT * FROM time_off_requests
|
||||
WHERE user_id = ? AND (status = 'pending' OR end_date >= date('now'))
|
||||
ORDER BY start_date ASC`,
|
||||
[req.user.id]
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
console.error("Error fetching time off requests:", err);
|
||||
res.status(500).json({ message: "Failed to fetch requests." });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/user/time-off-requests/history', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
// This query gets ALL requests for the user, sorted by newest first.
|
||||
const rows = await db.all(
|
||||
"SELECT * FROM time_off_requests WHERE user_id = ? ORDER BY start_date DESC",
|
||||
[req.user.id]
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
console.error("Error fetching time off history:", err);
|
||||
res.status(500).json({ message: "Failed to fetch request history." });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --- Admin Tools ---
|
||||
app.post('/api/admin/force-clock-out', authenticateToken, requireRole('admin'), async (req, res) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user