// Primus IQ — Projects Listing Screen
const SAMPLE_PROJECTS = [
{ id: 'p1', name: 'GCC Market Entry · Acme', desc: 'Sector Potential Realization — KSA vs UAE entry sequencing, pricing strategy, talent map.', tag: 'Sector Potential Realization', role: 'OWNER', files: 24, members: ['N', 'A', 'P'], more: 8, updated: '24 May' },
{ id: 'p2', name: 'Globex APAC Reorg', desc: 'Impact Realization — operating model redesign across 6 markets, change roadmap with cost guardrails.', tag: 'Impact Realization', role: 'MEMBER', files: 38, members: ['N', 'M'], more: 4, updated: '22 May' },
{ id: 'p3', name: 'India Public Policy 2026', desc: 'Policy track for FY26 — state-level digital governance benchmarks and procurement reform notes.', tag: 'Public Policy Realization', role: 'OWNER', files: 12, members: ['N', 'V'], more: 20, updated: '18 May' },
{ id: 'p4', name: 'TCFD · Portfolio Co.', desc: 'Climate disclosure rollup across 18 portfolio companies — Scope 1/2 baselines, gap remediation.', tag: 'Economic Potential Realization', role: 'MEMBER', files: 19, members: ['N', 'S'], more: 5, updated: '12 May' },
];
const DISCOVER_PROJECTS = [
{ id: 'd1', name: 'Indian MSME Financing', desc: 'Quarterly research on fintech, NBFC and public scheme penetration in MSME credit.', tag: 'Economic Potential Realization', owner: 'A. Mehta', members: 9, files: 47, updated: '23 May' },
{ id: 'd2', name: 'Healthcare Supply Atlas', desc: 'Live corpus on global pharma logistics — 220 papers, 40 interviews, monthly refresh.', tag: 'Transaction Realization', owner: 'P. Rao', members: 32, files: 220, updated: '20 May' },
{ id: 'd3', name: 'Auto OEM EV Roadmap', desc: 'Buy-side diligence for an EV component supplier — capacity, IP, customer concentration.', tag: 'Sector Potential Realization', owner: 'V. Kumar', members: 6, files: 31, updated: '19 May' },
{ id: 'd4', name: 'PSU Disinvestment Track', desc: 'Cross-firm track of PSU strategic-sale candidates, valuation desks and timelines.', tag: 'Public Policy Realization', owner: 'S. Iyer', members: 14, files: 88, updated: '17 May' },
{ id: 'd5', name: 'GCC Renewables 2026', desc: 'Annual thought-leadership flagship — 80pp report covering capex, regulation, partnerships.', tag: 'Economic Potential Realization', owner: 'N. Singh', members: 12, files: 65, updated: '20 May' },
{ id: 'd6', name: 'CSR Effectiveness Index', desc: 'Multi-state index of CSR spend efficiency — quantitative model + interview corpus.', tag: 'Impact Realization', owner: 'R. Banerjee', members: 8, files: 28, updated: '14 May' },
];
const adaptProject = (p) => ({
id: p.id,
name: p.name,
desc: p.description || '',
tag: p.practice || '',
role: p.membership ? p.membership.toUpperCase() : null,
membership: p.membership || null,
hasPendingRequest: p.has_pending_request || false,
files: p.artifact_count || 0,
memberCount: p.member_count || 0,
members: (p.members || []).slice(0, 3).map(m => (m.full_name || '?')[0].toUpperCase()),
more: Math.max(0, (p.member_count || 0) - 3),
updated: p.created_at ? new Date(p.created_at).toLocaleDateString('en-IN', { day: 'numeric', month: 'short' }) : '—',
});
const YoursProjectCard = ({ p, onClick, onInvite }) => (
{ e.currentTarget.style.borderColor = 'var(--maroon-20)'; e.currentTarget.style.boxShadow = '0 6px 18px rgba(60,30,20,0.06)'; e.currentTarget.style.transform = 'translateY(-1px)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--line)'; e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.transform = 'translateY(0)'; }}
>
{p.desc}
{p.tag}
{p.files}
{p.memberCount}
· {p.updated}
{p.role === 'OWNER' && onInvite && (
)}
);
const DiscoverProjectCard = ({ p, onOpenProject, onJoined }) => {
const [requested, setRequested] = React.useState(p.hasPendingRequest || false);
const isMember = !!p.membership;
const isOwner = p.membership === 'owner';
const handleRequestJoin = async (e) => {
e.stopPropagation();
setRequested(true);
try {
await PrimusAPI.joinProject(p.id);
// Leadership auto-joins → refresh so it moves into "Your projects"; others get pending state.
if (onJoined) onJoined();
} catch { setRequested(false); }
};
return (
e.currentTarget.style.borderColor = isMember ? 'var(--maroon-20)' : 'var(--line-2)'}
onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--line)'}
onClick={() => isMember && onOpenProject && onOpenProject(p.id)}
>
{p.name}
{p.files} files · {p.memberCount} members · {p.updated}
{isMember &&
{p.role}}
{p.desc}
{p.tag}
{isMember ? 'You have access.' : 'Membership required'}
{!isMember && (
)}
{isMember && (
)}
);
};
const ProjectsListing = ({ setRoute, openCreate, openInvite, projects, onOpenProject, currentUser, onJoined }) => {
const [tab, setTab] = React.useState(0);
const [q, setQ] = React.useState('');
// Only Management / Leadership may create projects (mirrors backend RBAC).
const canCreateProject = currentUser?.tier === 'management' || currentUser?.tier === 'leadership';
// Instant client-side filter — the full list is already loaded, so no network round-trip.
const allData = projects ? projects.map(adaptProject) : [];
const myData = allData.filter(p => p.membership !== null);
const discData = allData;
const data = tab === 0 ? myData : discData;
const ql = q.trim().toLowerCase();
const filtered = ql
? data.filter(p => (p.name || '').toLowerCase().includes(ql) || (p.desc || '').toLowerCase().includes(ql))
: data;
return (
Projects
Workspaces with their own files, instructions and chat history.
{canCreateProject && (
)}
{['Your projects', 'Discover'].map((t, i) => (
))}
{tab === 1 && (
Discover surfaces every project across Primus Partners. Request to join, or browse public deliverables.
)}
{filtered.map(p => tab === 0
? onOpenProject ? onOpenProject(p.id) : setRoute('project-detail')} onInvite={openInvite} />
:
)}
{filtered.length === 0 && (
{q
? `No projects match "${q}".`
: tab === 0
? No projects yet.
: 'No firm-wide projects found.'}
)}
);
};
Object.assign(window, {
SAMPLE_PROJECTS,
DISCOVER_PROJECTS,
adaptProject,
YoursProjectCard,
DiscoverProjectCard,
ProjectsListing,
});