/* 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
{sub}
: null}{hint}
: null}{action || null}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.