/* TMD Portal — shell, roles, routing. */ const uSa = React.useState; const NAV = [ { g: 'Work', items: [{ r: 'dash', l: 'Today' }, { r: 'inquiries', l: 'Inquiries' }, { r: 'board', l: 'Work' }, { r: 'tasks', l: 'My tasks' }, { r: 'clients', l: 'Clients' }, { r: 'people', l: 'People' }, { r: 'workload', l: 'Workload' }] }, { g: 'Money', items: [{ r: 'expenses', l: 'Expenses', money: true }, { r: 'invoices', l: 'Invoices', money: true }, { r: 'pnl', l: 'Client profit', owner: true }] }, { g: 'Settings', items: [{ r: 'pipelines', l: 'Pipelines' }] } ]; function App() { const auth = useSession(); const [mobile, setMobile] = uSa(() => window.matchMedia('(max-width: 820px)').matches); React.useEffect(() => { const mq = window.matchMedia('(max-width: 820px)'); const on = e => setMobile(e.matches); mq.addEventListener('change', on); return () => mq.removeEventListener('change', on); }, []); const [route, setRoute] = uSa('inquiries'); const [dels, setDels] = uSa(T.deliverables); const [tasks, setTasks] = uSa(T.tasks); const [expenses, setExpenses] = uSa(T.expenses); const [invoices, setInvoices] = uSa(T.invoices); const [toast, setToast] = uSa(null); const [navOpen, setNavOpen] = uSa(false); const staff = auth.staff || { person_id: 'un', role: 'Intern', name: '' }; const me = staff.person_id || 'un', role = staff.role; const canMoney = role !== 'Intern', owner = role === 'Owner'; const go = r => { setRoute(r); setNavOpen(false); window.scrollTo(0, 0); }; const fire = (msg, undo) => { setToast({ msg, undo }); setTimeout(() => setToast(null), 3200); }; const app = { role, me, mobile, dels, tasks, expenses, invoices, canMoney, go, toast: fire, staff, staffList: auth.staffList, session: auth.session, setDone: id => { setTasks(ts => ts.map(t => t.id === id ? Object.assign({}, t, { state: t.state === 'done' ? 'todo' : 'done' }) : t)); fire('Task updated', true); }, advance: id => { setDels(ds => ds.map(d => { if (d.id !== id) return d; const st = typeOf(d.type).stages, i = st.findIndex(s => s.name === stageMeta(d).name); return i < st.length - 1 ? Object.assign({}, d, { stage: st[i + 1].name }) : d; })); fire('Stage moved', true); }, addExpense: e => { setExpenses(x => [Object.assign({ id: 'e' + Date.now(), date: T.TODAY, desc: e.cat + ' — ' + (e.client ? client(e.client).name : 'overhead') }, e)].concat(x)); fire('Expense logged', true); }, issue: ids => { setInvoices(v => v.map(i => ids.includes(i.id) ? Object.assign({}, i, { status: 'issued', no: 'TMD-' + T.TODAY.slice(0, 4) + '-001', issued: T.TODAY, due: isoShift(7) }) : i)); fire('Invoice issued'); } }; if (auth.loading) return
Loading