feat: employee calendar tab
This commit is contained in:
parent
61401d8dc7
commit
0421d840e3
@ -23,6 +23,7 @@ import {
|
||||
let user = null;
|
||||
let authToken = null;
|
||||
let lastAdminTab = 'overview';
|
||||
let lastEmployeeTab = 'dashboard';
|
||||
export { lastAdminTab };
|
||||
|
||||
// --- NOTIFICATION LOGIC ---
|
||||
@ -165,6 +166,26 @@ async function handleViewRequestHistoryClick() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadEmployeeCalendar() {
|
||||
const iframe = document.getElementById('employee-calendar-iframe');
|
||||
const empty = document.getElementById('employee-calendar-empty');
|
||||
const link = document.getElementById('employee-calendar-link');
|
||||
if (!iframe || !empty || !link) return;
|
||||
const res = await apiCall('/calendar-url');
|
||||
if (!res.success) return;
|
||||
const url = res.data?.url || '';
|
||||
if (!url) {
|
||||
iframe.removeAttribute('src');
|
||||
empty.classList.remove('hidden');
|
||||
link.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
iframe.src = url;
|
||||
link.href = url;
|
||||
link.classList.remove('hidden');
|
||||
empty.classList.add('hidden');
|
||||
}
|
||||
|
||||
async function handleEditSubmit(e) {
|
||||
e.preventDefault();
|
||||
const id = e.target.elements['edit-id'].value;
|
||||
@ -450,6 +471,16 @@ export function attachEmployeeDashboardListeners() {
|
||||
if (timeOffForm) {
|
||||
timeOffForm.addEventListener('submit', handleTimeOffRequest);
|
||||
}
|
||||
|
||||
setupEmployeeTabs();
|
||||
if (lastEmployeeTab !== 'dashboard') {
|
||||
const tabsContainer = document.getElementById('employee-tabs-nav');
|
||||
tabsContainer?.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active-tab'));
|
||||
tabsContainer?.querySelector(`.tab-btn[data-tab="${lastEmployeeTab}"]`)?.classList.add('active-tab');
|
||||
document.getElementById('tab-content-employee-dashboard')?.classList.add('hidden');
|
||||
document.getElementById(`tab-content-employee-${lastEmployeeTab}`)?.classList.remove('hidden');
|
||||
}
|
||||
if (lastEmployeeTab === 'calendar') loadEmployeeCalendar();
|
||||
}
|
||||
|
||||
export function attachAdminDashboardListeners() {
|
||||
@ -527,6 +558,30 @@ function setupTabbedInterface() {
|
||||
});
|
||||
}
|
||||
|
||||
function setupEmployeeTabs() {
|
||||
const tabsContainer = document.getElementById('employee-tabs-nav');
|
||||
const contentContainer = document.getElementById('employee-tabs-content');
|
||||
if (!tabsContainer || !contentContainer) return;
|
||||
|
||||
tabsContainer.addEventListener('click', (e) => {
|
||||
const clickedTab = e.target.closest('.tab-btn');
|
||||
if (!clickedTab) return;
|
||||
|
||||
const tabTarget = clickedTab.dataset.tab;
|
||||
lastEmployeeTab = tabTarget;
|
||||
|
||||
tabsContainer.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active-tab'));
|
||||
clickedTab.classList.add('active-tab');
|
||||
|
||||
contentContainer.querySelectorAll('[id^="tab-content-employee-"]').forEach(panel => {
|
||||
panel.classList.add('hidden');
|
||||
});
|
||||
document.getElementById(`tab-content-employee-${tabTarget}`).classList.remove('hidden');
|
||||
|
||||
if (tabTarget === 'calendar') loadEmployeeCalendar();
|
||||
});
|
||||
}
|
||||
|
||||
// --- START THE APP ---
|
||||
// Register the service worker when the page loads
|
||||
if ('serviceWorker' in navigator) {
|
||||
|
||||
@ -94,7 +94,13 @@ export async function renderEmployeeDashboard() {
|
||||
}, 0);
|
||||
|
||||
mainViews.employee.innerHTML = `
|
||||
<div class="max-w-4xl mx-auto space-y-8">
|
||||
<div class="max-w-5xl mx-auto space-y-4">
|
||||
<div id="employee-tabs-nav" class="flex space-x-4 border-b">
|
||||
<button data-tab="dashboard" class="tab-btn py-3 px-4 active-tab">Dashboard</button>
|
||||
<button data-tab="calendar" class="tab-btn py-3 px-4">Calendar</button>
|
||||
</div>
|
||||
<div id="employee-tabs-content" class="space-y-8">
|
||||
<div id="tab-content-employee-dashboard" class="space-y-8">
|
||||
<div class="bg-white rounded-xl shadow-md p-6">
|
||||
<div class="grid md:grid-cols-2 gap-6 items-center">
|
||||
<div class="p-6 rounded-lg text-white text-center h-48 flex flex-col justify-center ${punchedIn ? 'bg-red-500' : 'bg-green-500'}">
|
||||
@ -160,6 +166,21 @@ export async function renderEmployeeDashboard() {
|
||||
<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">${utils.formatDateTime(e.punch_in_time)}</td><td class="p-2">${utils.formatDateTime(e.punch_out_time)}</td><td class="p-2" id="duration-${e.id}">${e.status === 'in' ? 'Running...' : utils.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>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-content-employee-calendar" class="space-y-4 hidden">
|
||||
<div class="bg-white rounded-xl shadow-md p-6">
|
||||
<div class="flex flex-wrap justify-between items-center gap-2 mb-4">
|
||||
<h3 class="text-xl font-bold text-gray-700">Calendar</h3>
|
||||
<a id="employee-calendar-link" class="text-sm text-blue-600 hover:underline hidden" target="_blank" rel="noopener">Open in new tab</a>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg border bg-white">
|
||||
<iframe id="employee-calendar-iframe" title="Employee Calendar" style="width: 100%; height: 70vh; border: none;"></iframe>
|
||||
</div>
|
||||
<p id="employee-calendar-empty" class="text-sm text-gray-500 mt-2 hidden">Calendar not configured.</p>
|
||||
<p class="text-xs text-gray-400 mt-1">If the calendar doesn't display, use the “Open in new tab” link.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
attachEmployeeDashboardListeners();
|
||||
|
||||
@ -591,6 +591,15 @@ app.post('/api/admin/notify', authenticateToken, requireRole('admin'), async (re
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/calendar-url', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const storedUrl = await getSetting('nextcloud_calendar_embed_url');
|
||||
res.json({ url: storedUrl || process.env.NEXTCLOUD_CALENDAR_EMBED_URL || null });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: 'Failed to fetch calendar settings.' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/admin/archive', authenticateToken, requireRole('admin'), async (req, res) => {
|
||||
try {
|
||||
const entriesToArchive = await db.all("SELECT * FROM time_entries WHERE status = 'out'");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user