/* TMD Portal — shared helpers and primitives. */ const { useState, useMemo, useRef, useEffect } = React; const DS = window.TheMarketingDeskDesignSystem_5795c6; const T = window.TMD; const byId = (arr, id) => arr.find(x => x.id === id) || null; const client = id => byId(T.clients, id) || { name: '—', kind: 'internal' }; const person = id => byId(T.people, id) || { name: 'Unassigned', initials: '—', role: '—' }; const typeOf = id => byId(T.types, id) || { name: '—', stages: [] }; const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const MONTHS_LONG = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; const DAYS_LONG = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; // Month helpers relative to today (offset 0 = this month, -1 = last month). const monthOf = offset => { const d = new Date(T.TODAY + 'T00:00:00'); d.setDate(1); d.setMonth(d.getMonth() + (offset || 0)); return d; }; const monthLabel = (offset, withYear) => { const d = monthOf(offset); return MONTHS_LONG[d.getMonth()] + (withYear ? ' ' + d.getFullYear() : ''); }; const monthStart = offset => { const d = monthOf(offset); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-01'; }; const monthEnd = offset => { const d = monthOf((offset || 0) + 1); d.setDate(0); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); }; const isoShift = days => { const d = new Date(T.TODAY + 'T00:00:00'); d.setDate(d.getDate() + days); return d.toISOString().slice(0, 10); }; const longToday = () => { const d = new Date(T.TODAY + 'T00:00:00'); return DAYS_LONG[d.getDay()] + ' ' + d.getDate() + ' ' + MONTHS_LONG[d.getMonth()] + ' ' + d.getFullYear(); }; function money(n, opts) { const o = opts || {}; const s = Math.abs(Math.round(n)).toLocaleString('en-US'); return (n < 0 ? '−' : '') + (o.bare ? '' : 'LKR ') + s; } function fmtDate(iso, withYear) { if (!iso) return '—'; const p = iso.split('-'); return Number(p[2]) + ' ' + MONTHS[Number(p[1]) - 1] + (withYear ? ' ' + p[0] : ''); } function dayDiff(iso, from) { const a = new Date((from || T.TODAY) + 'T00:00:00'), b = new Date(iso + 'T00:00:00'); return Math.round((b - a) / 86400000); } function stageMeta(d) { if (d.stage === 'Blocked') return { sem: 'blocked', name: 'Blocked', approval: null }; const st = typeOf(d.type).stages.find(s => s.name === d.stage); return st ? { sem: st.sem, name: st.name, approval: st.approval || null } : { sem: 'todo', name: d.stage, approval: null }; } const isLive = d => stageMeta(d).sem !== 'done'; const isOverdue = d => isLive(d) && dayDiff(d.due) < 0; const waitingOn = d => { const m = stageMeta(d); if (m.approval === 'client') return client(d.client).contacts && client(d.client).kind === 'external' ? client(d.client).contacts[0].name : client(d.client).name; if (m.approval === 'internal') return 'Safiya'; return null; }; /* ---------- status: five semantic states applied to arbitrary stages ---------- */ function Status({ d, stage, sem, size }) { const m = d ? stageMeta(d) : { sem: sem, name: stage, approval: null }; const who = d ? waitingOn(d) : null; const cls = 'st st-' + m.sem + (size === 'sm' ? ' st-sm' : ''); return ( {m.name}{m.sem === 'waiting' && who ? {who} : null} ); } /* ---------- one consistent overdue / date treatment ---------- */ function DateCell({ iso, live }) { if (!iso) return —; const dd = dayDiff(iso); const over = live !== false && dd < 0; if (over) return {fmtDate(iso)} {Math.abs(dd)}d over; const label = dd === 0 ? 'today' : dd === 1 ? 'tomorrow' : null; return {fmtDate(iso)}{label ? {label} : null}; } function Avatar({ id, size }) { const p = person(id); const s = size || 26; return {p.initials}; } function Who({ id, hideName }) { const p = person(id); return {hideName ? null : {p.name}}; } /* Cocogoose renders digits as a trial watermark — every numeral must fall to Poppins. */ function Dsp({ t }) { if (typeof t !== 'string') return t || null; return <>{t.split(/(\d+)/).map((p, i) => /^\d+$/.test(p) ? {p} : p)}; } function Eyebrow({ children, n }) { return {n ? {n} : null}{children}; } function Mark({ size, variant }) { return ; } function Tag({ children }) { return {children}; } function PageHead({ title, sub, actions, count }) { return (

{count != null ? {count} : null}

{sub ?

{sub}

: null}
{actions ?
{actions}
: null}
); } function Btn({ children, kind, onClick, disabled, sm, full }) { const variant = kind === 'secondary' ? 'secondary' : kind === 'ghost' ? 'ghost' : 'ink'; const style = { minHeight: sm ? 34 : 40, padding: sm ? 'var(--space-2) var(--space-4)' : 'var(--space-3) var(--space-5)', whiteSpace: 'nowrap', fontSize: sm ? 'var(--fs-meta)' : 'var(--fs-ui-sm)' }; if (full) style.width = '100%'; if (kind === 'danger') { style.background = 'var(--desk-caution)'; style.color = 'var(--desk-white)'; } return {children}; } function Seg({ options, value, onChange }) { return (
{options.map(o => { const v = typeof o === 'string' ? o : o.v, l = typeof o === 'string' ? o : o.l; return ; })}
); } function Filter({ label, options, value, onChange }) { return ( ); } function Empty({ title, hint, action }) { return
{title}{hint ?

{hint}

: null}{action || null}
; } function Stat({ label, value, sub, tone, onClick }) { return ( ); } function Sheet({ open, onClose, title, children, footer }) { if (!open) return null; return (
e.stopPropagation()} role="dialog" aria-label={title}>
{title}
{children}
{footer ?
{footer}
: null}
); } function Confirm({ open, title, body, danger, confirmLabel, onConfirm, onClose }) { const [typed, setTyped] = useState(''); useEffect(() => { if (open) setTyped(''); }, [open]); if (!open) return null; const need = danger ? 'ISSUE' : null; return (
e.stopPropagation()} role="dialog" aria-label={title}>

{body}
{need ? : null}
Cancel {confirmLabel}
); } function Toast({ toast, onUndo }) { if (!toast) return null; return
{toast.msg}{toast.undo ? : null}
; } function Denied({ role, onBack }) { return (
No access

Money is owner and manager only.

Your role is {role}. Invoices, expenses and client profit are not part of it. If that is wrong, ask Abidh to change your role in Settings — nothing here is hidden by accident.

Back to my tasks
); } Object.assign(window, { T, DS, Dsp, Mark, Tag, byId, client, person, typeOf, money, fmtDate, dayDiff, stageMeta, isLive, isOverdue, waitingOn, Status, DateCell, Avatar, Who, Eyebrow, PageHead, Btn, Seg, Filter, Empty, Stat, Sheet, Confirm, Toast, Denied, MONTHS, MONTHS_LONG, monthLabel, monthStart, monthEnd, isoShift, longToday });