// Research Hub + Thought Leadership (wired to PrimaAPI) const ResearchHub = ({ showRbac }) => { const [cat, setCat] = React.useState("all"); const [items, setItems] = React.useState([]); const [loading, setLoading] = React.useState(true); const [showAdd, setShowAdd] = React.useState(false); const loadItems = () => { setLoading(true); PrimaAPI.listResearch() .then(data => setItems(data || [])) .catch(() => setItems([])) .finally(() => setLoading(false)); }; React.useEffect(() => { loadItems(); }, []); // Map API fields to display format const display = items.map(r => ({ id: r.id, title: r.title, source: r.source, category: r.category, scope: r.geo_scope || [], industries: r.industry_tags || [], date: r.publish_date || "—", conf: r.confidentiality || "internal", })); const filtered = display.filter(r => cat === "all" || r.category === cat); return (
External intelligence

Research Hub

Whitepapers, market scans, competitor benchmarks, and regulatory notes from external sources — tagged for downstream retrieval.

{["all", ...TAXONOMY.researchCats].map(c => ( ))}
Sources: {TAXONOMY.publishers.slice(0, 4).map(p => {p})}
{loading ? (
Loading…
) : filtered.length === 0 ? (

No research items yet

Add external intelligence to build the firm's knowledge base.

) : (
{filtered.map((r, i) => (
{r.source} · {r.date}
{r.title}
{r.industries.join(", ")} · {r.scope.join(", ")}
{r.category} {r.industries.map(ind => {ind})} {r.scope.map(s => {s})}
))}
)} setShowAdd(false)} onSaved={() => { loadItems(); setShowAdd(false); }} />
); }; // ─── Add Research Modal ─── const AddResearchModal = ({ open, onClose, onSaved }) => { const [form, setForm] = React.useState({ title: "", source: "", category: "", geo_scope: [], industry_tags: [], publish_date: "", confidentiality: "internal" }); const [file, setFile] = React.useState(null); const [errors, setErrors] = React.useState({}); const [loading, setLoading] = React.useState(false); React.useEffect(() => { if (open) { setForm({ title: "", source: "", category: "", geo_scope: [], industry_tags: [], publish_date: "", confidentiality: "internal" }); setFile(null); setErrors({}); } }, [open]); const validate = () => { const e = {}; if (!form.title.trim()) e.title = "Required."; if (!form.source.trim()) e.source = "Required."; if (!form.category) e.category = "Required."; setErrors(e); return Object.keys(e).length === 0; }; const submit = async () => { if (!validate()) return; setLoading(true); try { let gcs_path = ""; if (file) { const resp = await PrimaAPI.uploadHubFile("research", file); gcs_path = resp.gcs_path || ""; } await PrimaAPI.addResearch({ ...form, gcs_path }); onSaved(); } catch (e) { setErrors({ api: e.message || "Failed to add research item." }); } finally { setLoading(false); } }; return ( }>
{errors.api && (
{errors.api}
)} setForm({ ...form, title: e.target.value })} placeholder="e.g. State of Urban Mobility 2026" error={errors.title} autoFocus />
setForm({ ...form, category: v })} options={TAXONOMY.researchCats} error={errors.category} />
setForm({ ...form, geo_scope: v })} options={TAXONOMY.regions} /> setForm({ ...form, industry_tags: v })} options={TAXONOMY.industries} />
setForm({ ...form, publish_date: e.target.value })} placeholder="e.g. Apr 2026" /> setFile(e.target.files[0] || null)} /> {file && }
); }; const ThoughtLeadership = () => { const [type, setType] = React.useState("all"); const [items, setItems] = React.useState([]); const [loading, setLoading] = React.useState(true); const [showAdd, setShowAdd] = React.useState(false); const loadItems = () => { setLoading(true); PrimaAPI.listThought() .then(data => setItems(data || [])) .catch(() => setItems([])) .finally(() => setLoading(false)); }; React.useEffect(() => { loadItems(); }, []); // Map API fields to display format const display = items.map(p => ({ id: p.id, title: p.title, type: p.content_type, domain: p.primary_domain || "—", themes: p.themes || [], authors: p.authors || [], date: p.publish_date || "—", conf: p.confidentiality || "internal", })); const filtered = display.filter(p => type === "all" || p.type === type); return (
Internal POVs

Thought Leadership

Articles, op-eds, and whitepapers authored by Primus consultants — the firm's external voice, organised by domain and theme.

{["all", ...TAXONOMY.contentTypes].map(c => ( ))}
{loading ? (
Loading…
) : filtered.length === 0 ? (

No thought leadership items yet

Publish internal POVs to build the firm's voice.

) : (
{filtered.map((p, i) => (
{p.type} {p.domain} {p.date}

{p.title}

{p.authors.map((a, ai) => (
))}
{p.authors.join(" · ")}
{p.themes.map(t => {t})}
))}
)} setShowAdd(false)} onSaved={() => { loadItems(); setShowAdd(false); }} />
); }; // ─── Add Thought Leadership Modal ─── const AddThoughtModal = ({ open, onClose, onSaved }) => { const [form, setForm] = React.useState({ title: "", content_type: "", primary_domain: "", themes: [], authors: [], publish_date: "", confidentiality: "internal" }); const [file, setFile] = React.useState(null); const [errors, setErrors] = React.useState({}); const [loading, setLoading] = React.useState(false); React.useEffect(() => { if (open) { setForm({ title: "", content_type: "", primary_domain: "", themes: [], authors: [], publish_date: "", confidentiality: "internal" }); setFile(null); setErrors({}); } }, [open]); const validate = () => { const e = {}; if (!form.title.trim()) e.title = "Required."; if (!form.content_type) e.content_type = "Required."; setErrors(e); return Object.keys(e).length === 0; }; const submit = async () => { if (!validate()) return; setLoading(true); try { let gcs_path = ""; if (file) { const resp = await PrimaAPI.uploadHubFile("thought-leadership", file); gcs_path = resp.gcs_path || ""; } await PrimaAPI.addThought({ ...form, gcs_path }); onSaved(); } catch (e) { setErrors({ api: e.message || "Failed to publish POV." }); } finally { setLoading(false); } }; return ( }>
{errors.api && (
{errors.api}
)} setForm({ ...form, title: e.target.value })} placeholder="e.g. Why citizen-experience must lead digital-government roadmaps" error={errors.title} autoFocus />
setForm({ ...form, primary_domain: v })} options={TAXONOMY.domains} placeholder="Optional…" />
setForm({ ...form, themes: v })} options={["Generative AI", "Agentic AI", "Urbanization", "Climate", "Disclosure", "Citizen Services", "Programme Mgmt", "Banking", "Health"]} allowCustom /> setForm({ ...form, authors: v })} options={[]} allowCustom />
setForm({ ...form, publish_date: e.target.value })} placeholder="e.g. Apr 24, 2026" /> setFile(e.target.files[0] || null)} /> {file && }
); }; Object.assign(window, { ResearchHub, ThoughtLeadership, AddResearchModal, AddThoughtModal });