/* TMD Portal — work management screens. */
const { useState: uS, useMemo: uM } = React;
/* ================= Dashboard ================= */
function Dashboard({ app }) {
const { role, go, dels, tasks } = app;
if (role === 'Intern' || role === 'Executive') return ;
const live = dels.filter(isLive);
const overdue = live.filter(isOverdue);
const waiting = live.filter(d => stageMeta(d).sem === 'waiting');
const blocked = live.filter(d => stageMeta(d).sem === 'blocked');
const published = dels.filter(d => d.published);
const owner = role === 'Owner';
const retainer = T.clients.filter(c => c.status === 'Active').reduce((s, c) => s + c.retainer, 0);
const inv = T.invoices;
const invoiced = inv.filter(i => i.status !== 'draft').reduce((s, i) => s + i.lines.reduce((a, l) => a + l.amount, 0), 0);
const collected = inv.reduce((s, i) => s + i.payments.reduce((a, p) => a + p.amount, 0), 0);
const outstanding = invoiced - collected;
const overdueAr = inv.filter(i => i.status === 'overdue').reduce((s, i) => s + i.lines.reduce((a, l) => a + l.amount, 0) - i.payments.reduce((a, p) => a + p.amount, 0), 0);
const spend = T.expenses.reduce((s, e) => s + (e.paidBy === 'TMD' ? e.amount : 0), 0);
return (
{owner ? (
<>
Money this month
c.status === 'Active').length + ' active clients'} onClick={() => go('clients')} />
i.status !== 'draft').length + ' issued · ' + inv.filter(i => i.status === 'draft').length + ' draft'} onClick={() => go('invoices')} />
i.status === 'paid').length + ' paid in full'} onClick={() => go('invoices')} />
0 ? 'bad' : null} onClick={() => go('invoices')} />
go('expenses')} />
go('board')} />
>
) : null}
Work that is stuck
Waiting on someone{waiting.length + blocked.length}
{blocked.concat(waiting).map(d => (
))}
Overdue{overdue.length}
{overdue.length ? overdue.sort((a, b) => a.due.localeCompare(b.due)).map(d => (
)) :
}
This week, by person
Published this month
go('board')} />
go('board')} />
go('board')} />
);
}
/* ================= Work board + list ================= */
function Board({ app }) {
const { go, dels } = app;
const [view, setView] = uS(app.mobile ? 'list' : 'board');
const [type, setType] = uS('reel');
const [cl, setCl] = uS('all');
const [asg, setAsg] = uS('all');
const [win, setWin] = uS('all');
const match = d => (cl === 'all' || d.client === cl) && (asg === 'all' || d.assignee === asg) &&
(win === 'all' || (win === 'over' ? isOverdue(d) : win === 'week' ? dayDiff(d.due) <= 7 && dayDiff(d.due) >= 0 : true));
const list = dels.filter(match);
const boardList = list.filter(d => d.type === type);
const stages = typeOf(type).stages;
const filters = (
({ v: c.id, l: c.name })))} />
p.id !== 'un').map(p => ({ v: p.id, l: p.name })))} />
);
return (
New deliverable>} />
{view === 'board' ? (
<>
{T.types.map(t => )}
{filters}
{stages.map(s => {
const col = boardList.filter(d => stageMeta(d).name === s.name);
return (
{s.name}{col.length}
{s.approval ?
{s.approval === 'client' ? 'Client approval — recorded by staff' : 'Manager approval'}
: null}
{col.map(d => (
))}
{col.length === 0 ?
Nothing here
: null}
);
})}
>
) : (
<>
{filters}
>
)}
);
}
function DelTable({ list, go, mobile }) {
if (!list.length) return New deliverable} />;
if (mobile) return (
{list.map(d => (
))}
);
return (
| Ref | Deliverable | Client | Type | Stage | Owner | Due |
{list.map(d => (
go('del:' + d.id)} tabIndex={0} onKeyDown={e => e.key === 'Enter' && go('del:' + d.id)}>
| {d.ref} |
{d.title} |
{client(d.client).name} |
{typeOf(d.type).name} |
|
|
|
))}
);
}
/* ================= My tasks ================= */
function MyTasks({ app }) {
const { tasks, setDone, go, me, role } = app;
const mine = tasks.filter(t => t.who === me);
const open = mine.filter(t => t.state !== 'done');
const groups = [
{ k: 'Overdue', items: open.filter(t => dayDiff(t.due) < 0), tone: 'bad' },
{ k: 'Today', items: open.filter(t => dayDiff(t.due) === 0) },
{ k: 'This week', items: open.filter(t => dayDiff(t.due) > 0 && dayDiff(t.due) <= 7) },
{ k: 'Later', items: open.filter(t => dayDiff(t.due) > 7) },
{ k: 'Done', items: mine.filter(t => t.state === 'done'), done: true }
].filter(g => g.items.length);
return (
{open.length === 0 ?
: null}
{groups.map(g => (
{g.k}{g.items.length}
{g.items.map(t => (
{t.title}
{t.del ? : Standalone}
))}
))}
);
}
/* ================= Deliverable detail ================= */
function DelDetail({ app, id }) {
const d = byId(app.dels, id);
const [tab, setTab] = uS('Brief');
const [comment, setComment] = uS('');
const [rec, setRec] = uS(false);
if (!d) return ;
const t = typeOf(d.type), m = stageMeta(d), c = client(d.client);
const idx = t.stages.findIndex(s => s.name === m.name);
const copy = (d.copy || []).slice().sort((a, b) => b.v - a.v);
const [ver, setVer] = uS(copy.length ? copy[0].v : 0);
const cur = copy.find(x => x.v === ver);
const ap = d.approvals || {};
const Brief = (
Brief
{d.brief || 'No brief written yet. Ask the requester for one line on what this is for — that is enough to start.'}
{d.blockedNote ? Blocked{d.blockedNote}
: null}
Assets
{(d.assets || []).length ? (
) : Add link} />}
{(d.assets || []).length ? Add link : null}
{d.published ? Published{d.published.url}
: null}
);
const Copy = (
Copy
{copy.length ?
{copy.map(v => )}
: null}
{cur ? (
<>
v{cur.v} · {person(cur.author).name} · {cur.at.replace('2026-08-', '').replace('-', ' ')}{cur.v !== copy[0].v ? Read only — older version : null}
{cur.text}
{cur.v === copy[0].v && app.role !== 'Intern' ? (
app.toast('Copy v' + cur.v + ' approved')}>Approve copy app.toast('Changes requested — Maneesha notified', true)}>Request changes
) : null}
>
) : Write v1} />}
Comments
{(d.comments || []).map((c2, i) => (
{person(c2.author).name} · {c2.at.slice(5).replace('-', '/')} · on v{c2.on}{c2.text}
))}
{!(d.comments || []).length ?
No comments. Type below to start the thread — mention with @.
: null}
);
const Activity = (
Approvals
Internal
{ap.internal && ap.internal.state === 'approved' ? Approved by {person(ap.internal.by).name} · {fmtDate(ap.internal.at.slice(0, 10))}
: ap.internal && ap.internal.state === 'pending' ? Waiting on Safiya since 13 Aug : Not reached yet}
{ap.internal && ap.internal.state === 'pending' && app.role !== 'Intern' ? app.toast('Internal approval recorded')}>Approve : null}
Client
{!(ap.client && ap.client.required) ? Not required — {c.name} is an in-house brand
: ap.client.state === 'approved' ? Recorded: {ap.client.by} · {fmtDate(ap.client.at)}
: <>Waiting on {c.contacts[0].name}{ap.client.chased ? · chased {fmtDate(ap.client.chased)} : null} setRec(true)}>Record approval>}
{ap.client && ap.client.required && ap.client.state !== 'approved' ? Clients do not log in. Whoever hears back on WhatsApp records it here, in their own name.
: null}
Activity
{(d.activity || [{ who: 'sa', at: '2026-08-08 09:00', what: 'created this deliverable' }]).map((a, i) => (
- {a.at.slice(5).replace('-', '/')} {person(a.who).name} {a.what}
))}
);
const header = (
{d.ref}{c.name}·{t.name}{d.priority === 'High' ? High priority : null}
Stage
Due
Owner
{t.stages.map((s, i) => {s.name})}
{app.role !== 'Intern' || d.assignee === app.me ?
app.advance(d.id)}>{idx < t.stages.length - 1 ? 'Move to ' + t.stages[idx + 1].name : 'Published'}ReassignChange due date
: null}
);
return (
);
}
/* ================= Clients ================= */
function Clients({ app }) {
const { go, dels } = app;
return (
Add client} />
| Client | Type | Industry | Status | Retainer / month | Open work |
{T.clients.map(c => (
go('client:' + c.id)} tabIndex={0}>
| {c.name} |
{c.kind === 'internal' ? 'In-house' : 'External'} |
{c.industry} |
{c.status} |
{money(c.retainer, { bare: true })} |
{dels.filter(d => d.client === c.id && isLive(d)).length} |
))}
);
}
function ClientDetail({ app, id }) {
const c = byId(T.clients, id);
const work = app.dels.filter(d => d.client === id);
const exp = T.expenses.filter(e => e.client === id);
const inv = T.invoices.filter(i => i.client === id);
return (
Retainer history
{c.history.map((h, i) => - {fmtDate(h.from, true)}{money(h.amount, { bare: true })}{i === 0 ? Current : Until {fmtDate(c.history[i - 1].from, true)}}
)}
Contacts
{c.contacts.map((k, i) => - {k.name}{k.role}{k.channel}
)}
Notes
{c.notes}
Work
{app.canMoney ? (
<>
Invoices
| Number | Period | Status | Total |
{inv.map(i => app.go('inv:' + i.id)}>| {i.no} | {i.period} | {i.status} | {money(i.lines.reduce((s, l) => s + l.amount, 0), { bare: true })} |
)}
Expenses
| Date | Expense | Paid by | Amount |
{exp.map(e => | {fmtDate(e.date)} | {e.desc} | {e.paidBy} | {money(e.amount, { bare: true })} |
)}
>
) : null}
);
}
/* ================= People + workload ================= */
function People({ app }) {
const owner = app.role === 'Owner';
const staff = T.people.filter(p => p.id !== 'un');
return (
Add person} />
| Name | Role | Basis | Joined | Open work |
{staff.map(p => (
| {p.role} | {p.basis} | {fmtDate(p.joined, true)} | {app.dels.filter(d => d.assignee === p.id && isLive(d)).length} |
))}
{owner ? (
Owner onlyCost per personEffective-dated. Nobody else sees this panel — it does not exist for them.
| Name | Effective from | Monthly cost |
{staff.filter(p => p.cost).map(p => | {p.name} | {fmtDate(p.costFrom, true)} | {money(p.cost, { bare: true })} |
)}
) : null}
);
}
function WorkloadStrip({ app, compact }) {
const staff = T.people.filter(p => p.id !== 'un');
const week = d => isLive(d) && dayDiff(d.due) <= 7;
return (
{staff.map(p => {
const mine = app.dels.filter(d => d.assignee === p.id && week(d));
const od = mine.filter(isOverdue);
return (
{mine.length} due{od.length ? · {od.length} over : null}
{mine.slice(0, compact ? 4 : 20).map(d => (
))}
{!mine.length ? - Free this week — give her something.
: null}
);
})}
);
}
function Workload({ app }) {
return (
Unassigned work is not counted against anyone. Two deliverables have no owner.
);
}
Object.assign(window, { Dashboard, Board, DelTable, MyTasks, DelDetail, Clients, ClientDetail, People, Workload, WorkloadStrip });