I'm a paragraph. Click here to add your own text and edit me. It's easy.
Heading 1
import { getMyWork, getAdminWork, createTask, updateTask } from 'backend/staffHub.web';
import { getMyWork, getAdminWork, createTask, updateTask } from 'backend/staffHub.web'; const C = { purple: '#662F93', pink: '#FA0381', orange: '#FC7C34', aqua: '#9FECEF', pale: '#E0FBFC', ink: '#172033', slate: '#334155' }; function esc(value) { return String(value == null ? '' : value) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"'); } function dateValue(value) { if (!value) return null; const date = new Date(value); return Number.isNaN(date.getTime()) ? null : date; } function dueText(value) { const date = dateValue(value); return date ? date.toLocaleDateString() : (value ? esc(value) : 'No due date'); } function isOverdue(task) { const due = dateValue(task.dueDate); if (!due) return false; const today = new Date(); const start = new Date(today.getFullYear(), today.getMonth(), today.getDate()); return due < start; } function isDueToday(task) { const due = dateValue(task.dueDate); if (!due) return false; const today = new Date(); return due.getFullYear() === today.getFullYear() && due.getMonth() === today.getMonth() && due.getDate() === today.getDate(); } function isBlocked(task) { return Boolean(task.blocker) || String(task.status || '').toLowerCase().includes('blocked'); } function accent(priority) { const value = String(priority || '').toLowerCase(); if (value === 'urgent') return C.pink; if (value === 'high') return C.orange; if (value === 'normal' || value === 'medium') return C.aqua; return C.purple; } function taskCard(task, showAdminDetails = false) { const blocker = task.blocker ? '<p style="margin:10px 0 0;color:#7C2D12;font-size:15px;"><strong>Blocked:</strong> ' + esc(task.blocker) + '</p>' : ''; const approval = task.approvalRequired ? '<p style="margin:10px 0 0;color:#662F93;font-size:15px;"><strong>Approval required</strong></p>' : ''; const adminDetails = showAdminDetails ? '<p style="margin:10px 0 0;padding-top:10px;border-top:1px solid #DCE6E8;color:#334155;font-size:14px;line-height:1.55;">' + '<strong>Owner:</strong> ' + esc(task.ownerName || task.ownerPersonId || 'Unassigned') + '<br><strong>Assigned by:</strong> ' + esc(task.assignedByName || task.assignedByPersonId || 'Not recorded') + '<br><strong>Project:</strong> ' + esc(task.projectName || task.projectId || 'Not linked') + '<br><strong>Location:</strong> ' + esc(task.locationId || 'Multisite') + '</p>' : ''; return '<div style="box-sizing:border-box;flex:1 1 320px;background:#FFFFFF;border:1px solid #DCE6E8;border-left:7px solid ' + accent(task.priority) + ';padding:18px;color:#172033;">' + '<p style="margin:0 0 12px;color:#662F93;font-size:19px;line-height:1.3;"><strong>' + esc(task.title || 'Untitled assignment') + '</strong></p>' + '<p style="margin:0;color:#334155;font-size:15px;line-height:1.6;"><strong>Due:</strong> ' + dueText(task.dueDate) + '<br><strong>Priority:</strong> ' + esc(task.priority || 'Normal') + '<br><strong>Status:</strong> ' + esc(task.status || 'Not started') + '</p>' + adminDetails + approval + blocker + '</div>'; } function completedWithinDays(task, days = 30) { const completed = dateValue(task.completedDate); if (!completed) return false; const cutoff = new Date(); cutoff.setHours(0, 0, 0, 0); cutoff.setDate(cutoff.getDate() - days); return completed >= cutoff; } function completedCard(task, showAdminDetails = false) { const details = showAdminDetails ? '<p style="margin:12px 0 0;padding-top:10px;border-top:1px solid #DCE6E8;color:#334155;font-size:14px;line-height:1.55;">' + '<strong>Owner:</strong> ' + esc(task.ownerName || task.ownerPersonId || 'Unassigned') + '<br><strong>Project:</strong> ' + esc(task.projectName || task.projectId || 'Not linked') + '<br><strong>Location:</strong> ' + esc(task.locationId || 'Multisite') + '</p>' : ''; return '<div style="box-sizing:border-box;flex:1 1 300px;background:#F8FAFC;border:1px solid #DCE6E8;border-left:7px solid #20C4CB;padding:16px;color:#172033;">' + '<p style="margin:0 0 9px;color:#662F93;font-size:18px;line-height:1.3;"><strong>' + esc(task.title || 'Untitled assignment') + '</strong></p>' + '<p style="margin:0;color:#334155;font-size:14px;line-height:1.55;"><strong>Completed:</strong> ' + dueText(task.completedDate) + '<br><strong>Priority:</strong> ' + esc(task.priority || 'Normal') + '</p>' + details + '</div>'; } function completedCards(tasks, showAdminDetails = false) { if (!tasks.length) return emptyState('No completed work is recorded for this period.'); return '<div style="display:flex;flex-wrap:wrap;gap:16px;align-items:stretch;">' + tasks.map((task) => completedCard(task, showAdminDetails)).join('') + '</div>'; } function metric(label, value, color, background) { return '<div style="box-sizing:border-box;flex:1 1 140px;background:' + background + ';border-top:5px solid ' + color + ';padding:13px 12px;text-align:center;">' + '<p style="margin:0;color:#172033;font-size:25px;"><strong>' + value + '</strong></p>' + '<p style="margin:5px 0 0;color:#334155;font-size:13px;">' + label + '</p></div>'; } function summary(tasks) { const dueToday = tasks.filter(isDueToday).length; const overdue = tasks.filter(isOverdue).length; const blocked = tasks.filter(isBlocked).length; return '<div style="display:flex;flex-wrap:wrap;gap:12px;margin:0 0 20px;">' + metric('Active', tasks.length, C.purple, '#F5EFF9') + metric('Due today', dueToday, C.aqua, C.pale) + metric('Overdue', overdue, C.pink, '#FFF0F7') + metric('Blocked', blocked, C.orange, '#FFF2EA') + '</div>'; } function emptyState(message) { return '<div style="background:#E0FBFC;border-left:7px solid #9FECEF;padding:18px;color:#172033;">' + '<p style="margin:0;">' + esc(message) + '</p></div>'; } function cards(tasks, adminDetails = false) { if (!tasks.length) return emptyState('Nothing is waiting in this view.'); return '<div style="display:flex;flex-wrap:wrap;gap:16px;align-items:stretch;">' + tasks.map((task) => taskCard(task, adminDetails)).join('') + '</div>'; } function section(title, description, content, color = C.purple) { return '<div style="margin:28px 0 0;padding-top:20px;border-top:5px solid ' + color + ';">' + '<h2 style="margin:0;color:#662F93;font-size:26px;"><strong>' + esc(title) + '</strong></h2>' + '<p style="margin:6px 0 15px;color:#334155;font-size:15px;">' + esc(description) + '</p>' + content + '</div>'; } function myWorkHtml(result) { const name = esc(result.profile?.name || result.profile?.email || 'Staff member'); const tasks = (result.tasks || []).filter((task) => task.status !== 'Requested'); const completed = (result.completedTasks || []).filter((task) => completedWithinDays(task, 30)); const heading = '<div style="background:#662F93;padding:22px 24px;color:#FFFFFF;margin:0 0 16px;">' + '<h1 style="margin:0;color:#FFFFFF;font-size:38px;"><strong>My Work</strong></h1>' + '<p style="margin:8px 0 0;color:#FFFFFF;font-size:17px;">' + name + '</p></div>'; return heading + summary(tasks); } function staffWorkload(admin) { const tasks = (admin.tasks || []).filter((task) => task.status !== 'Requested'); const recentlyCompleted = (admin.completedTasks || []).filter((task) => completedWithinDays(task, 30)).slice(0, 12); const staff = admin.staff || []; if (!staff.length) return emptyState('No active staff records are available.'); return '<div style="display:flex;flex-wrap:wrap;gap:12px;">' + staff.map((person) => { const owned = tasks.filter((task) => task.ownerPersonId === person.personId); const attention = owned.filter((task) => isOverdue(task) || isBlocked(task) || task.status === 'Ready for Approval' ).length; return '<div style="box-sizing:border-box;flex:1 1 220px;background:#F8FAFC;border-top:5px solid #9FECEF;padding:15px;">' + '<p style="margin:0;color:#662F93;font-size:17px;"><strong>' + esc(person.name || person.personId) + '</strong></p>' + '<p style="margin:5px 0 0;color:#334155;font-size:14px;">' + esc(person.role || 'Staff') + '<br><strong>' + owned.length + '</strong> active · <strong>' + attention + '</strong> need attention</p></div>'; }).join('') + '</div>'; } function projectCards(tasks) { const groups = new Map(); tasks.forEach((task) => { const key = task.projectId || 'unlinked'; if (!groups.has(key)) { groups.set(key, { name: task.projectName || (key === 'unlinked' ? 'Work not linked to a project' : key), tasks: [] }); } groups.get(key).tasks.push(task); }); if (!groups.size) return emptyState('No active project work is available.'); return '<div style="display:flex;flex-wrap:wrap;gap:12px;">' + Array.from(groups.values()).map((project) => { const attention = project.tasks.filter((task) => isOverdue(task) || isBlocked(task) || task.status === 'Ready for Approval' ).length; return '<div style="box-sizing:border-box;flex:1 1 250px;background:#FFFFFF;border:1px solid #DCE6E8;border-top:6px solid #FA0381;padding:16px;">' + '<p style="margin:0;color:#662F93;font-size:18px;"><strong>' + esc(project.name) + '</strong></p>' + '<p style="margin:7px 0 0;color:#334155;font-size:14px;"><strong>' + project.tasks.length + '</strong> active assignments · <strong>' + attention + '</strong> need attention</p></div>'; }).join('') + '</div>'; } function dashboardSectionsHtml(result, admin) { const tasks = (result.tasks || []).filter((task) => task.status !== 'Requested'); const completed = (result.completedTasks || []).filter((task) => completedWithinDays(task, 30)); let html = section( 'My Active Assignments', 'Your personal work queue.', cards(tasks), C.aqua ) + section( 'Recently Completed', 'Work you completed during the last 30 days.', completedCards(completed), C.orange ); if (admin) html += adminHtml(admin); return html; } function adminHtml(admin) { const tasks = (admin.tasks || []).filter((task) => task.status !== 'Requested'); const recentlyCompleted = (admin.completedTasks || []).filter((task) => completedWithinDays(task, 30)).slice(0, 12); const myPersonId = admin.profile?.personId || ''; const assignedByMe = tasks.filter((task) => task.assignedByPersonId === myPersonId); const needsAttention = tasks.filter((task) => isOverdue(task) || isBlocked(task) || task.status === 'Ready for Approval' ); const adminHeader = '<div style="box-sizing:border-box;margin:34px 0 0;background:#E0FBFC;border-left:8px solid #662F93;padding:20px 22px;color:#172033;">' + '<h2 style="margin:0;color:#662F93;font-size:25px;line-height:1.2;"><strong>Super Admin Workspace</strong></h2>' + '<p style="margin:8px 0 0;color:#334155;font-size:15px;">Organization-wide visibility is separate from your personal queue.</p></div>' + '<div style="display:flex;flex-wrap:wrap;gap:8px;background:#F5EFF9;padding:14px;margin-bottom:18px;">' + ['All Staff Work', 'Assigned by Me', 'Needs Attention', 'Projects'].map((label) => '<span style="background:#FFFFFF;border:1px solid #662F93;color:#662F93;padding:7px 10px;font-size:13px;"><strong>' + label + '</strong></span>' ).join('') + '</div>'; return adminHeader + summary(tasks) + section( 'Team workload', 'A quick view of active assignments and exceptions by staff member.', staffWorkload(admin), C.aqua ) + section( 'All Staff Work', 'Every active assignment across the organization.', cards(tasks, true), C.purple ) + section( 'Assigned by Me', 'Organization work assigned by the signed-in Super Admin.', cards(assignedByMe, true), C.orange ) + section( 'Needs Attention', 'Overdue, blocked, or approval-required work.', cards(needsAttention, true), C.pink ) + section( 'Recently Completed Across the Team', 'The latest work completed during the last 30 days.', completedCards(recentlyCompleted, true), C.orange ) + section( 'Projects', 'Active assignments grouped by project.', projectCards(tasks), C.aqua ); } $w.onReady(async function () { $w('#text30').collapse(); $w('#text31').html = '<p style="color:#334155;font-size:16px;">Loading Staff Hub…</p>'; try { const myWork = await getMyWork(); let html = myWorkHtml(myWork); let dashboard = dashboardSectionsHtml(myWork, null); if (myWork.profile?.isDashboardAdmin) { try { const admin = await getAdminWork(); dashboard = dashboardSectionsHtml(myWork, admin); } catch (adminError) { html += '<div style="margin-top:26px;background:#FFF0F7;border-left:7px solid #FA0381;padding:18px;color:#63113E;">' + '<p style="margin:0;"><strong>The Super Admin view could not be loaded.</strong><br>' + 'Your personal assignments are still available above.</p></div>'; console.error('Staff Hub admin:', adminError); } } $w('#text31').html = html; $w('#text32').html = dashboard; } catch (error) { $w('#text31').html = '<div style="background:#FFF0F7;border-left:7px solid #FA0381;padding:18px;color:#63113E;">' + '<p style="margin:0;"><strong>Staff Hub access is not active for this account.</strong><br>' + 'Please contact the site administrator.</p></div>'; console.error('Staff Hub:', error); } }); async function refreshDashboardCards() { try { const myWork = await getMyWork(); let html = myWorkHtml(myWork); let dashboard = dashboardSectionsHtml(myWork, null); if (myWork.profile?.isDashboardAdmin) { const admin = await getAdminWork(); dashboard = dashboardSectionsHtml(myWork, admin); } $w('#text31').html = html; $w('#text32').html = dashboard; } catch (error) { console.error('Staff Hub refresh:', error); } } function panelDate(value) { if (!value) return ''; const date = value instanceof Date ? value : new Date(value); return Number.isNaN(date.getTime()) ? '' : date.toISOString(); } async function getManageWorkData() { const myWork = await getMyWork(); const data = myWork.profile?.isDashboardAdmin ? await getAdminWork() : myWork; return { profile: data.profile || {}, sentRequests: data.sentRequests || (data.tasks || []).filter((task) => task.status === 'Requested' && task.assignedByPersonId === data.profile?.personId), staff: data.staff || [], projects: data.projects || [], tasks: (data.tasks || []).map((task) => ({ ...task, dueDate: panelDate(task.dueDate) })), completedTasks: (data.completedTasks || []).map((task) => ({ ...task, dueDate: panelDate(task.dueDate), completedDate: panelDate(task.completedDate) })) }; } $w.onReady(async function () { const managePanel = $w('#html1'); async function refreshManagePanel() { const data = await getManageWorkData(); managePanel.postMessage({ type: 'init', data }); return data; } managePanel.onMessage(async (event) => { const message = event.data || {}; if (message.type === 'ready') { try { await refreshManagePanel(); } catch (error) { console.error('Manage Work:', error); } return; } if (message.type !== 'create-task' && message.type !== 'update-task') { return; } const action = message.type === 'create-task' ? 'create' : 'update'; try { if (action === 'create') { await createTask(message.payload || {}); } else { await updateTask(message.payload || {}); } const data = await getManageWorkData(); managePanel.postMessage({ type: 'task-result', ok: true, action, message: action === 'create' ? 'Assignment created. Dashboard updated.' : 'Changes saved. Dashboard updated.', data }); await refreshDashboardCards(); } catch (error) { managePanel.postMessage({ type: 'task-result', ok: false, action, message: error?.message || 'That change could not be saved.' }); console.error('Manage Work:', error); } }); try { await refreshManagePanel(); } catch (error) { console.error('Manage Work:', error); } });
import { getMyWork, getAdminWork, createTask, updateTask } from 'backend/staffHub.web';const C = { purple: '#662F93', pink: '#FA0381', orange: '#FC7C34', aqua: '#9FECEF', pale: '#E0FBFC', ink: '#172033', slate: '#334155'};function esc(value) { return String(value == null ? '' : value) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"');}function dateValue(value) { if (!value) return null; const date = new Date(value); return Number.isNaN(date.getTime()) ? null : date;}function dueText(value) { const date = dateValue(value); return date ? date.toLocaleDateString() : (value ? esc(value) : 'No due date');}function isOverdue(task) { const due = dateValue(task.dueDate); if (!due) return false; const today = new Date(); const start = new Date(today.getFullYear(), today.getMonth(), today.getDate()); return due < start;}function isDueToday(task) { const due = dateValue(task.dueDate); if (!due) return false; const today = new Date(); return due.getFullYear() === today.getFullYear() && due.getMonth() === today.getMonth() && due.getDate() === today.getDate();}function isBlocked(task) { return Boolean(task.blocker) || String(task.status || '').toLowerCase().includes('blocked');}function accent(priority) { const value = String(priority || '').toLowerCase(); if (value === 'urgent') return C.pink; if (value === 'high') return C.orange; if (value === 'normal' || value === 'medium') return C.aqua; return C.purple;}function taskCard(task, showAdminDetails = false) { const blocker = task.blocker ? '<p style="margin:10px 0 0;color:#7C2D12;font-size:15px;"><strong>Blocked:</strong> ' + esc(task.blocker) + '</p>' : ''; const approval = task.approvalRequired ? '<p style="margin:10px 0 0;color:#662F93;font-size:15px;"><strong>Approval required</strong></p>' : ''; const adminDetails = showAdminDetails ? '<p style="margin:10px 0 0;padding-top:10px;border-top:1px solid #DCE6E8;color:#334155;font-size:14px;line-height:1.55;">' + '<strong>Owner:</strong> ' + esc(task.ownerName || task.ownerPersonId || 'Unassigned') + '<br><strong>Assigned by:</strong> ' + esc(task.assignedByName || task.assignedByPersonId || 'Not recorded') + '<br><strong>Project:</strong> ' + esc(task.projectName || task.projectId || 'Not linked') + '<br><strong>Location:</strong> ' + esc(task.locationId || 'Multisite') + '</p>' : ''; return '<div style="box-sizing:border-box;flex:1 1 320px;background:#FFFFFF;border:1px solid #DCE6E8;border-left:7px solid ' + accent(task.priority) + ';padding:18px;color:#172033;">' + '<p style="margin:0 0 12px;color:#662F93;font-size:19px;line-height:1.3;"><strong>' + esc(task.title || 'Untitled assignment') + '</strong></p>' + '<p style="margin:0;color:#334155;font-size:15px;line-height:1.6;"><strong>Due:</strong> ' + dueText(task.dueDate) + '<br><strong>Priority:</strong> ' + esc(task.priority || 'Normal') + '<br><strong>Status:</strong> ' + esc(task.status || 'Not started') + '</p>' + adminDetails + approval + blocker + '</div>';}function completedWithinDays(task, days = 30) { const completed = dateValue(task.completedDate); if (!completed) return false; const cutoff = new Date(); cutoff.setHours(0, 0, 0, 0); cutoff.setDate(cutoff.getDate() - days); return completed >= cutoff;}function completedCard(task, showAdminDetails = false) { const details = showAdminDetails ? '<p style="margin:12px 0 0;padding-top:10px;border-top:1px solid #DCE6E8;color:#334155;font-size:14px;line-height:1.55;">' + '<strong>Owner:</strong> ' + esc(task.ownerName || task.ownerPersonId || 'Unassigned') + '<br><strong>Project:</strong> ' + esc(task.projectName || task.projectId || 'Not linked') + '<br><strong>Location:</strong> ' + esc(task.locationId || 'Multisite') + '</p>' : ''; return '<div style="box-sizing:border-box;flex:1 1 300px;background:#F8FAFC;border:1px solid #DCE6E8;border-left:7px solid #20C4CB;padding:16px;color:#172033;">' + '<p style="margin:0 0 9px;color:#662F93;font-size:18px;line-height:1.3;"><strong>' + esc(task.title || 'Untitled assignment') + '</strong></p>' + '<p style="margin:0;color:#334155;font-size:14px;line-height:1.55;"><strong>Completed:</strong> ' + dueText(task.completedDate) + '<br><strong>Priority:</strong> ' + esc(task.priority || 'Normal') + '</p>' + details + '</div>';}function completedCards(tasks, showAdminDetails = false) { if (!tasks.length) return emptyState('No completed work is recorded for this period.'); return '<div style="display:flex;flex-wrap:wrap;gap:16px;align-items:stretch;">' + tasks.map((task) => completedCard(task, showAdminDetails)).join('') + '</div>';}function metric(label, value, color, background) { return '<div style="box-sizing:border-box;flex:1 1 140px;background:' + background + ';border-top:5px solid ' + color + ';padding:13px 12px;text-align:center;">' + '<p style="margin:0;color:#172033;font-size:25px;"><strong>' + value + '</strong></p>' + '<p style="margin:5px 0 0;color:#334155;font-size:13px;">' + label + '</p></div>';}function summary(tasks) { const dueToday = tasks.filter(isDueToday).length; const overdue = tasks.filter(isOverdue).length; const blocked = tasks.filter(isBlocked).length; return '<div style="display:flex;flex-wrap:wrap;gap:12px;margin:0 0 20px;">' + metric('Active', tasks.length, C.purple, '#F5EFF9') + metric('Due today', dueToday, C.aqua, C.pale) + metric('Overdue', overdue, C.pink, '#FFF0F7') + metric('Blocked', blocked, C.orange, '#FFF2EA') + '</div>';}function emptyState(message) { return '<div style="background:#E0FBFC;border-left:7px solid #9FECEF;padding:18px;color:#172033;">' + '<p style="margin:0;">' + esc(message) + '</p></div>';}function cards(tasks, adminDetails = false) { if (!tasks.length) return emptyState('Nothing is waiting in this view.'); return '<div style="display:flex;flex-wrap:wrap;gap:16px;align-items:stretch;">' + tasks.map((task) => taskCard(task, adminDetails)).join('') + '</div>';}function section(title, description, content, color = C.purple) { return '<div style="margin:28px 0 0;padding-top:20px;border-top:5px solid ' + color + ';">' + '<h2 style="margin:0;color:#662F93;font-size:26px;"><strong>' + esc(title) + '</strong></h2>' + '<p style="margin:6px 0 15px;color:#334155;font-size:15px;">' + esc(description) + '</p>' + content + '</div>';}function myWorkHtml(result) { const name = esc(result.profile?.name || result.profile?.email || 'Staff member'); const tasks = (result.tasks || []).filter((task) => task.status !== 'Requested'); const completed = (result.completedTasks || []).filter((task) => completedWithinDays(task, 30)); const heading = '<div style="background:#662F93;padding:22px 24px;color:#FFFFFF;margin:0 0 16px;">' + '<h1 style="margin:0;color:#FFFFFF;font-size:38px;"><strong>My Work</strong></h1>' + '<p style="margin:8px 0 0;color:#FFFFFF;font-size:17px;">' + name + '</p></div>'; return heading + summary(tasks);}function staffWorkload(admin) { const tasks = (admin.tasks || []).filter((task) => task.status !== 'Requested'); const recentlyCompleted = (admin.completedTasks || []).filter((task) => completedWithinDays(task, 30)).slice(0, 12); const staff = admin.staff || []; if (!staff.length) return emptyState('No active staff records are available.'); return '<div style="display:flex;flex-wrap:wrap;gap:12px;">' + staff.map((person) => { const owned = tasks.filter((task) => task.ownerPersonId === person.personId); const attention = owned.filter((task) => isOverdue(task) || isBlocked(task) || task.status === 'Ready for Approval' ).length; return '<div style="box-sizing:border-box;flex:1 1 220px;background:#F8FAFC;border-top:5px solid #9FECEF;padding:15px;">' + '<p style="margin:0;color:#662F93;font-size:17px;"><strong>' + esc(person.name || person.personId) + '</strong></p>' + '<p style="margin:5px 0 0;color:#334155;font-size:14px;">' + esc(person.role || 'Staff') + '<br><strong>' + owned.length + '</strong> active · <strong>' + attention + '</strong> need attention</p></div>'; }).join('') + '</div>';}function projectCards(tasks) { const groups = new Map(); tasks.forEach((task) => { const key = task.projectId || 'unlinked'; if (!groups.has(key)) { groups.set(key, { name: task.projectName || (key === 'unlinked' ? 'Work not linked to a project' : key), tasks: [] }); } groups.get(key).tasks.push(task); }); if (!groups.size) return emptyState('No active project work is available.'); return '<div style="display:flex;flex-wrap:wrap;gap:12px;">' + Array.from(groups.values()).map((project) => { const attention = project.tasks.filter((task) => isOverdue(task) || isBlocked(task) || task.status === 'Ready for Approval' ).length; return '<div style="box-sizing:border-box;flex:1 1 250px;background:#FFFFFF;border:1px solid #DCE6E8;border-top:6px solid #FA0381;padding:16px;">' + '<p style="margin:0;color:#662F93;font-size:18px;"><strong>' + esc(project.name) + '</strong></p>' + '<p style="margin:7px 0 0;color:#334155;font-size:14px;"><strong>' + project.tasks.length + '</strong> active assignments · <strong>' + attention + '</strong> need attention</p></div>'; }).join('') + '</div>';}function dashboardSectionsHtml(result, admin) { const tasks = (result.tasks || []).filter((task) => task.status !== 'Requested'); const completed = (result.completedTasks || []).filter((task) => completedWithinDays(task, 30)); let html = section( 'My Active Assignments', 'Your personal work queue.', cards(tasks), C.aqua ) + section( 'Recently Completed', 'Work you completed during the last 30 days.', completedCards(completed), C.orange ); if (admin) html += adminHtml(admin); return html;}function adminHtml(admin) { const tasks = (admin.tasks || []).filter((task) => task.status !== 'Requested'); const recentlyCompleted = (admin.completedTasks || []).filter((task) => completedWithinDays(task, 30)).slice(0, 12); const myPersonId = admin.profile?.personId || ''; const assignedByMe = tasks.filter((task) => task.assignedByPersonId === myPersonId); const needsAttention = tasks.filter((task) => isOverdue(task) || isBlocked(task) || task.status === 'Ready for Approval' ); const adminHeader = '<div style="box-sizing:border-box;margin:34px 0 0;background:#E0FBFC;border-left:8px solid #662F93;padding:20px 22px;color:#172033;">' + '<h2 style="margin:0;color:#662F93;font-size:25px;line-height:1.2;"><strong>Super Admin Workspace</strong></h2>' + '<p style="margin:8px 0 0;color:#334155;font-size:15px;">Organization-wide visibility is separate from your personal queue.</p></div>' + '<div style="display:flex;flex-wrap:wrap;gap:8px;background:#F5EFF9;padding:14px;margin-bottom:18px;">' + ['All Staff Work', 'Assigned by Me', 'Needs Attention', 'Projects'].map((label) => '<span style="background:#FFFFFF;border:1px solid #662F93;color:#662F93;padding:7px 10px;font-size:13px;"><strong>' + label + '</strong></span>' ).join('') + '</div>'; return adminHeader + summary(tasks) + section( 'Team workload', 'A quick view of active assignments and exceptions by staff member.', staffWorkload(admin), C.aqua ) + section( 'All Staff Work', 'Every active assignment across the organization.', cards(tasks, true), C.purple ) + section( 'Assigned by Me', 'Organization work assigned by the signed-in Super Admin.', cards(assignedByMe, true), C.orange ) + section( 'Needs Attention', 'Overdue, blocked, or approval-required work.', cards(needsAttention, true), C.pink ) + section( 'Recently Completed Across the Team', 'The latest work completed during the last 30 days.', completedCards(recentlyCompleted, true), C.orange ) + section( 'Projects', 'Active assignments grouped by project.', projectCards(tasks), C.aqua );}$w.onReady(async function () { $w('#text30').collapse(); $w('#text31').html = '<p style="color:#334155;font-size:16px;">Loading Staff Hub…</p>'; try { const myWork = await getMyWork(); let html = myWorkHtml(myWork); let dashboard = dashboardSectionsHtml(myWork, null); if (myWork.profile?.isDashboardAdmin) { try { const admin = await getAdminWork(); dashboard = dashboardSectionsHtml(myWork, admin); } catch (adminError) { html += '<div style="margin-top:26px;background:#FFF0F7;border-left:7px solid #FA0381;padding:18px;color:#63113E;">' + '<p style="margin:0;"><strong>The Super Admin view could not be loaded.</strong><br>' + 'Your personal assignments are still available above.</p></div>'; console.error('Staff Hub admin:', adminError); } } $w('#text31').html = html; $w('#text32').html = dashboard; } catch (error) { $w('#text31').html = '<div style="background:#FFF0F7;border-left:7px solid #FA0381;padding:18px;color:#63113E;">' + '<p style="margin:0;"><strong>Staff Hub access is not active for this account.</strong><br>' + 'Please contact the site administrator.</p></div>'; console.error('Staff Hub:', error); }});async function refreshDashboardCards() { try { const myWork = await getMyWork(); let html = myWorkHtml(myWork); let dashboard = dashboardSectionsHtml(myWork, null); if (myWork.profile?.isDashboardAdmin) { const admin = await getAdminWork(); dashboard = dashboardSectionsHtml(myWork, admin); } $w('#text31').html = html; $w('#text32').html = dashboard; } catch (error) { console.error('Staff Hub refresh:', error); }}function panelDate(value) { if (!value) return ''; const date = value instanceof Date ? value : new Date(value); return Number.isNaN(date.getTime()) ? '' : date.toISOString();}async function getManageWorkData() { const myWork = await getMyWork(); const data = myWork.profile?.isDashboardAdmin ? await getAdminWork() : myWork; return { profile: data.profile || {}, sentRequests: data.sentRequests || (data.tasks || []).filter((task) => task.status === 'Requested' && task.assignedByPersonId === data.profile?.personId), staff: data.staff || [], projects: data.projects || [], tasks: (data.tasks || []).map((task) => ({ ...task, dueDate: panelDate(task.dueDate) })), completedTasks: (data.completedTasks || []).map((task) => ({ ...task, dueDate: panelDate(task.dueDate), completedDate: panelDate(task.completedDate) })) };}$w.onReady(async function () { const managePanel = $w('#html1'); async function refreshManagePanel() { const data = await getManageWorkData(); managePanel.postMessage({ type: 'init', data }); return data; } managePanel.onMessage(async (event) => { const message = event.data || {}; if (message.type === 'ready') { try { await refreshManagePanel(); } catch (error) { console.error('Manage Work:', error); } return; } if (message.type !== 'create-task' && message.type !== 'update-task') { return; } const action = message.type === 'create-task' ? 'create' : 'update'; try { if (action === 'create') { await createTask(message.payload || {}); } else { await updateTask(message.payload || {}); } const data = await getManageWorkData(); managePanel.postMessage({ type: 'task-result', ok: true, action, message: action === 'create' ? 'Assignment created. Dashboard updated.' : 'Changes saved. Dashboard updated.', data }); await refreshDashboardCards(); } catch (error) { managePanel.postMessage({ type: 'task-result', ok: false, action, message: error?.message || 'That change could not be saved.' }); console.error('Manage Work:', error); } }); try { await refreshManagePanel(); } catch (error) { console.error('Manage Work:', error); }});
