/* Mandera Portal — Admin console (master + team). Lives on MData. */ const A_money = (n) => '$' + n.toLocaleString(); function ATitle({ children }) { return

{children}

; } function AField({ label, children }) { return
{children}
; } function ASelect({ value, onChange, options }) { return ( ); } /* ---------------- Overview ---------------- */ function AdminOverview({ go }) { const { Card } = window.ManderaDesignSystem_8327ce; const clients = window.MData.clients(); const projects = window.MData.get('projects'); const shoots = window.MData.get('shoots'); const db = window.MData.load(); const pendingOnb = Object.values(db.onboarding).filter((o) => o.status === 'submitted').length; const openRev = window.MData.get('revisions').filter((r) => r.status !== 'done').length; const tiles = [ { ic: 'users', lbl: 'Active clients', val: clients.filter((c) => c.status === 'active').length, color: 'var(--accent-text)', go: 'clients' }, { ic: 'folder-kanban', lbl: 'Live projects', val: projects.length, color: 'var(--azure-400)', go: 'clients' }, { ic: 'camera', lbl: 'Shoots booked', val: shoots.length, color: 'var(--positive)', go: 'clients' }, { ic: 'clipboard-check', lbl: 'Onboarding to review', val: pendingOnb, color: 'var(--caution)', go: 'onboarding' }, { ic: 'git-pull-request', lbl: 'Open revisions', val: openRev, color: 'var(--critical)', go: 'clients' }, ]; return (
Mandera team console

Everything across every client.

Create accounts, schedule shoots, manage content and talk to clients — all here.

{tiles.map((t) => (
go(t.go)} style={{ cursor: 'pointer' }}>
{t.val}
{t.lbl}
))}

Coming up — across all clients

); } /* ---------------- Clients list + detail ---------------- */ function AdminClients({ bump }) { const { Button, Input, Badge } = window.ManderaDesignSystem_8327ce; const [q, setQ] = React.useState(''); const [filter, setFilter] = React.useState('all'); const [openId, setOpenId] = React.useState(null); const clients = window.MData.clients().filter((c) => (filter === 'all' || c.status === filter) && (c.brand.toLowerCase().includes(q.toLowerCase()) || c.contact.toLowerCase().includes(q.toLowerCase()))); if (openId) return setOpenId(null)} bump={bump} />; return (
setQ(e.target.value)} iconLeft={} />
setFilter(e.target.value)} options={[{ value: 'all', label: 'All statuses' }, { value: 'active', label: 'Active' }, { value: 'suspended', label: 'Suspended' }]} />
{clients.map((c, i) => { const onb = window.MData.onboardingOf(c.id); return (
setOpenId(c.id)} style={{ display: 'grid', gridTemplateColumns: '1fr 160px 130px 110px 28px', gap: 16, alignItems: 'center', padding: '15px 18px', background: 'var(--surface-card)', borderTop: i ? '1px solid var(--border-subtle)' : 'none', cursor: 'pointer' }}>
{window.mInitials(c.brand)}
{c.brand}
{c.contact}
{c.industry || '—'} {onb.status === 'approved' ? Onboarded : onb.status === 'submitted' ? Review : Pending} {c.status === 'active' ? Active : Suspended}
); })} {!clients.length &&
No clients match.
}
); } function AdminClientDetail({ clientId, back, bump }) { const { Button, Input, Badge } = window.ManderaDesignSystem_8327ce; const [tab, setTab] = React.useState('shoots'); const [, setTick] = React.useState(0); const refresh = () => { setTick((t) => t + 1); bump && bump(); }; const c = window.MData.client(clientId); const onb = window.MData.onboardingOf(clientId); const avatarRef = React.useRef(null); const onAvatar = (e) => { const f = e.target.files && e.target.files[0]; if (!f) return; if (!f.type.startsWith('image')) { alert('Please choose an image file.'); return; } const r = new FileReader(); r.onload = () => { window.MData.update('clients', clientId, { avatar: r.result }); refresh(); }; r.readAsDataURL(f); }; const Tab = ({ id, label }) => ( ); return (

{c.brand}

{c.contact} · {c.email} · lead {c.manager}
{c.avatar && } {((window.MAuth.current() || {}).role === 'master') && ( )}
{tab === 'shoots' && } {tab === 'content' && } {tab === 'timeline' && } {tab === 'files' && } {tab === 'invoices' && } {tab === 'onboarding' && }
); } function AdminShoots({ clientId, refresh }) { const { Button, Input } = window.ManderaDesignSystem_8327ce; const shoots = window.MData.forClient('shoots', clientId).slice().sort((a, b) => a.date.localeCompare(b.date)); const [form, setForm] = React.useState({ title: '', date: '', time: '', location: '', notes: '', status: 'Pending' }); const set = (k, v) => setForm((p) => ({ ...p, [k]: v })); const add = (e) => { e.preventDefault(); if (!form.title.trim() || !form.date) return; const s = window.MData.add('shoots', { clientId, ...form }); window.MData.notify({ clientId, type: 'shoot', text: 'New shoot scheduled: “' + form.title.trim() + '” · ' + window.mFmtDate(form.date) + (form.time ? ' · ' + form.time : ''), link: 'shoots', refId: s.id }); try { fetch('../api/portal-mail.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'shoot_scheduled', clientId, title: form.title.trim(), date: form.date, time: form.time, location: form.location }) }).catch(() => {}); } catch (_) {} setForm({ title: '', date: '', time: '', location: '', notes: '', status: 'Pending' }); refresh(); }; return (
Schedule a shoot set('title', e.target.value)} placeholder="Product shoot…" />
set('date', e.target.value)} style={{ height: 42, padding: '0 12px', background: 'var(--surface-sunken)', border: '1px solid var(--border-default)', borderRadius: 'var(--radius-md)', color: 'var(--text-strong)', fontFamily: 'var(--font-sans)', fontSize: 14 }} /> set('time', e.target.value)} placeholder="09:00 – 13:00" />
set('location', e.target.value)} placeholder="Studio A · Lisbon" />