// Client Portal screen — the client-facing view (previewed inside the agency app). (() => { const { Card, Badge, Avatar } = window.TechyFuelOSDesignSystem_be0222; function mimeIcon(mime) { if (!mime) return { icon: 'file', tone: 'var(--slate-400)' }; if (mime.includes('pdf')) return { icon: 'file-text', tone: 'var(--red-500)' }; if (mime.includes('image')) return { icon: 'image', tone: 'var(--violet-500)' }; if (mime.includes('video')) return { icon: 'video', tone: 'var(--blue-500)' }; if (mime.includes('zip')) return { icon: 'folder-archive', tone: 'var(--amber-500)' }; if (mime.includes('sheet') || mime.includes('excel') || mime.includes('csv')) return { icon: 'table', tone: 'var(--green-600)' }; if (mime.includes('word') || mime.includes('doc')) return { icon: 'file-text', tone: 'var(--blue-600)' }; return { icon: 'file', tone: 'var(--slate-400)' }; } function fmtSize(bytes) { if (!bytes) return '—'; if (bytes >= 1073741824) return (bytes / 1073741824).toFixed(1) + ' GB'; if (bytes >= 1048576) return Math.round(bytes / 1048576) + ' MB'; if (bytes >= 1024) return Math.round(bytes / 1024) + ' KB'; return bytes + ' B'; } function fmtDate(ds) { if (!ds) return '—'; return new Date(ds).toLocaleDateString('en', { month: 'short', day: 'numeric', year: 'numeric' }); } function fmtWhen(ds) { if (!ds) return ''; const diff = Math.round((Date.now() - new Date(ds)) / 3600000); if (diff < 1) return 'Just now'; if (diff < 24) return diff + 'h ago'; if (diff < 48) return 'Yesterday'; return fmtDate(ds); } function fmtAmt(n, currency) { if (!n && n !== 0) return '—'; const symbols = { USD: '$', EUR: '€', GBP: '£', AED: 'AED ', SAR: 'SAR ', PKR: '₨', CAD: 'CA$', AUD: 'A$' }; return (symbols[currency] || '$') + Number(n).toLocaleString(); } function getProjectHealth(tasks) { if (!tasks || tasks.length === 0) return { label: 'No tasks', color: 'var(--slate-400)' }; const total = tasks.length; const done = tasks.filter(t => t.status === 'done' || t.status === 'completed').length; const overdue = tasks.filter(t => t.due_date && new Date(t.due_date) < new Date() && t.status !== 'done' && t.status !== 'completed').length; if (overdue > 0) return { label: 'At risk', color: 'var(--red-500)' }; if (done / total >= 0.8) return { label: 'On track', color: 'var(--green-600)' }; return { label: 'In progress', color: 'var(--amber-500)' }; } function taskToMilestone(task) { if (task.status === 'done' || task.status === 'completed') return 'done'; if (task.status === 'in_progress') return 'active'; return 'todo'; } // Shares Finance.jsx's buildInvoiceHtml so the client portal's own PDF // download always matches the branded template staff see in Finance — // not a second, separately-maintained template that drifts out of sync. function downloadInvoicePDF(inv, clients) { if (!window.buildInvoiceHtml) return; const printScript = '