// Primus IQ β€” Tagging Modal const SAMPLE_DROPPED = [ { id: 'd1', name: 'GCC_Market_Sizing_v4.pptx', size: '12.0 MB', ext: 'pptx', suggest: 'strict', pages: 28, tags: ['GCC', 'Sizing'] }, { id: 'd2', name: 'Acme_Financial_Model.xlsx', size: '4.2 MB', ext: 'xlsx', suggest: 'strict', pages: 12, tags: ['Acme', 'Finance'] }, { id: 'd3', name: 'IndustryReport_Q4.pdf', size: '8.6 MB', ext: 'pdf', suggest: 'public', pages: 64, tags: ['Research'] }, { id: 'd4', name: 'Interview_Notes_CFO.docx', size: '120 KB', ext: 'docx', suggest: 'client', pages: 4, tags: ['Acme', 'Interview'] }, { id: 'd5', name: 'ESG_Compliance_Brief.pdf', size: '2.1 MB', ext: 'pdf', suggest: 'internal', pages: 18, tags: ['ESG'] }, { id: 'd6', name: 'Pitch_Storyline_draft.pptx', size: '6.8 MB', ext: 'pptx', suggest: 'internal', pages: 22, tags: ['Pitch'] }, { id: 'd7', name: 'Supply_chain_diagram.png', size: '880 KB', ext: 'png', suggest: 'internal', pages: 1, tags: ['Diagram'] }, { id: 'd8', name: 'Bid_Cost_Calculator.xlsx', size: '1.4 MB', ext: 'xlsx', suggest: 'strict', pages: 8, tags: ['RFP'] }, { id: 'd9', name: 'Client_brief_v2.docx', size: '340 KB', ext: 'docx', suggest: 'client', pages: 6, tags: ['Acme', 'Brief'] }, { id: 'd10', name: 'Site_visit_video.mp4', size: '48.2 MB', ext: 'mp4', suggest: 'internal', pages: 0, tags: ['Field'] }, ]; const CONF_OPTIONS = [ { id: 'public', label: 'Public', desc: 'Anyone' }, { id: 'internal', label: 'Internal', desc: 'All Employees' }, { id: 'client', label: 'Confidential', desc: 'Restricted Projects' }, { id: 'strict', label: 'Restricted Visibility', desc: 'Only MDs' }, ]; const CONF_LABELS = { public: 'Public', internal: 'Internal', client: 'Confidential', strict: 'Restricted Visibility', }; const SUGGESTED_TAGS = ['GCC', 'KSA', 'UAE', 'Fintech', 'Climate', 'Policy', 'Healthcare', 'TCFD', 'CSRD', 'Acme', 'Globex']; const TaggingModal = ({ open, onClose, onCommit, pendingFiles = [], editMode = false }) => { const [files, setFiles] = React.useState([]); const [active, setActive] = React.useState(0); const [tagInput, setTagInput] = React.useState(''); React.useEffect(() => { if (open) { setFiles(pendingFiles.length > 0 ? pendingFiles : SAMPLE_DROPPED.map(f => ({ ...f, conf: f.suggest }))); setActive(0); setTagInput(''); } }, [open, pendingFiles]); // Must be before any early return β€” hooks must always run in the same order. React.useEffect(() => { if (!open) return; const onKey = (e) => { if (e.key === 'Escape') onClose(); if (e.key === 'ArrowDown') setActive(a => Math.min(a + 1, files.length - 1)); if (e.key === 'ArrowUp') setActive(a => Math.max(a - 1, 0)); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [open, files]); if (!open) return null; if (files.length === 0) return (
e.stopPropagation()} style={{ padding: '40px 48px', background: 'var(--bg-card)', borderRadius: 18, boxShadow: '0 30px 80px rgba(26,22,20,0.30)', textAlign: 'center', }}>
πŸ“‚
No files selected
Drop files on the zone or use the Upload button.
); const f = files[active]; const updateFile = (idx, patch) => { setFiles(prev => prev.map((file, i) => i === idx ? { ...file, ...patch } : file)); }; const addTag = (t) => { const tag = t.trim(); if (!tag) return; if (f.tags.includes(tag)) return; updateFile(active, { tags: [...f.tags, tag] }); setTagInput(''); }; const removeTag = (t) => updateFile(active, { tags: f.tags.filter(x => x !== t) }); const applyAllConf = (confId) => setFiles(prev => prev.map(file => ({ ...file, conf: confId }))); const tagged = files.filter(x => x.conf).length; return (
e.stopPropagation()} className="tagging-modal-card" style={{ width: 1040, maxWidth: '94vw', height: 640, maxHeight: '92vh', background: 'var(--bg-card)', borderRadius: 18, boxShadow: '0 30px 80px rgba(26,22,20,0.30), 0 0 0 1px var(--line)', animation: 'rise 0.22s ease-out', display: 'flex', flexDirection: 'column', overflow: 'hidden', }}> {/* Header */}

{editMode ? 'Edit visibility & tags' : 'Tag your files'}

{editMode ? 'Update who can see this file and its tags' : `${files.length} files Β· pick visibility and add tags Β· ${tagged}/${files.length} ready`}
{/* Body */}
{/* LEFT β€” file queue */}
Files {active + 1} / {files.length}
{files.map((file, i) => { const isActive = i === active; return (
setActive(i)} className="tagging-file-item" style={{ background: isActive ? 'white' : 'transparent', borderColor: isActive ? 'var(--maroon-20)' : 'transparent', boxShadow: isActive ? '0 2px 8px rgba(60,30,20,0.06)' : 'none', }}>
{file.name}
{file.size} {file.conf && ( {CONF_LABELS[file.conf] || file.conf} )}
); })}
{/* RIGHT β€” tag panel */}
{/* File header */}
{f.name}
{f.size} {f.pages > 0 && Β· {f.pages} pages} Suggested: {CONF_LABELS[f.suggest] || f.suggest}
{/* Visibility */}
Visibility
{CONF_OPTIONS.map(opt => { const sel = f.conf === opt.id; return ( ); })}
{/* Tags */}
Tags
{f.tags.map(t => ( {t} ))} setTagInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addTag(tagInput); } if (e.key === 'Backspace' && !tagInput && f.tags.length > 0) removeTag(f.tags[f.tags.length - 1]); }} placeholder={f.tags.length === 0 ? 'Add a tag…' : ''} style={{ flex: 1, minWidth: 100, border: 'none', outline: 'none', fontSize: 13, fontFamily: 'inherit', color: 'var(--ink)', background: 'transparent', padding: '4px 2px', }} />
Suggested: {SUGGESTED_TAGS.filter(t => !f.tags.includes(t)).slice(0, 7).map(t => ( ))}
{/* Footer banner */}
Tip: {' '} Use ↑ ↓ to move between files, Enter to add a tag.
{/* Footer */}
); }; const sectionLabel = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 10, fontWeight: 700, color: 'var(--mute)', letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: 8, }; const miniBtn = { background: 'none', border: 'none', cursor: 'pointer', fontSize: 11, color: 'var(--maroon)', fontFamily: 'inherit', fontWeight: 600, textTransform: 'none', letterSpacing: 0, }; const chipTagStyle = { display: 'inline-flex', alignItems: 'center', gap: 6, padding: '4px 4px 4px 10px', borderRadius: 6, background: 'var(--maroon-06)', color: 'var(--maroon)', fontSize: 12, fontWeight: 500, fontFamily: 'inherit', }; const chipRemoveBtn = { width: 16, height: 16, borderRadius: 4, border: 'none', cursor: 'pointer', background: 'transparent', color: 'var(--maroon)', display: 'grid', placeItems: 'center', }; const suggestedTagBtn = { padding: '4px 10px', borderRadius: 999, background: 'white', border: '1px dashed var(--line-2)', fontSize: 11, color: 'var(--mute)', fontFamily: 'inherit', cursor: 'pointer', }; const navBtn = { height: 32, padding: '0 12px', borderRadius: 7, background: 'transparent', border: '1px solid var(--line-2)', fontSize: 12, fontFamily: 'inherit', color: 'var(--ink-2)', cursor: 'pointer', }; const kbdStyle = { display: 'inline-block', padding: '1px 6px', borderRadius: 4, background: 'white', border: '1px solid var(--line-2)', fontSize: 10, fontFamily: 'ui-monospace, monospace', color: 'var(--ink)', }; Object.assign(window, { TaggingModal, SAMPLE_DROPPED, CONF_OPTIONS, CONF_LABELS, SUGGESTED_TAGS, });