'use client'; import { useEffect, useRef, useState } from 'react'; import CVTree from '@/components/cv/CVTree'; import DiffViewer from '@/components/cv/DiffViewer'; import InsightsPanel from '@/components/cv/InsightsPanel'; import Link from 'next/link'; import { appendPatches, createBranch, createSubmission, deleteDocument, deleteVersion, Document, downloadVersionUrl, fetchDocuments, fetchInsights, fetchSubmissions, fetchPublicAssetAnalytics, getPublicPdfUrl, InsightsResult, IS_DEMO, publishVersion, PublicAsset, PublicAssetAnalytics, requestAiSuggestions, Submission, SubmissionStatus, StructuredBlock, Suggestion, updateSubmissionStatus, updateSuggestion, uploadDocument, Version, } from '@/libs/api'; import { DEMO_DOCUMENTS, DEMO_DOC_ID, DEMO_INSIGHTS, DEMO_SUBMISSIONS, } from './demo-data'; // ── helpers ─────────────────────────────────────────────────────────────────── function fmt(iso: string) { return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } const SUBMISSION_STATUS_LABELS: Record = { draft: 'Draft', tailoring: 'Tailoring', pending_review: 'Passed screening', published: 'Submitted', archived: 'Closed', }; const SUBMISSION_STATUS_OPTIONS: Array<{ value: SubmissionStatus; label: string }> = [ { value: 'draft', label: 'Draft' }, { value: 'tailoring', label: 'Tailoring' }, { value: 'published', label: 'Submitted' }, { value: 'pending_review', label: 'Passed screening' }, { value: 'archived', label: 'Closed' }, ]; function isSubmittedStatus(status: SubmissionStatus) { return status === 'published' || status === 'pending_review' || status === 'archived'; } function statusBadge(status: SubmissionStatus) { const cls = ({ draft: 'badge-draft', tailoring: 'badge-submitted', pending_review: 'badge-interviewing', published: 'badge-public', archived: 'badge-closed', } as Record)[status] ?? 'badge-draft'; return {SUBMISSION_STATUS_LABELS[status] ?? status.replace('_', ' ')}; } // ── modals ──────────────────────────────────────────────────────────────────── function UploadModal({ onClose, onDone }: { onClose: () => void; onDone: (doc: Document) => void }) { const [title, setTitle] = useState(''); const [desc, setDesc] = useState(''); const [file, setFile] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const ref = useRef(null); const submit = async () => { if (!title.trim() || !file) { setError('Title and file required.'); return; } setLoading(true); setError(''); try { const doc = await uploadDocument(title.trim(), desc.trim() || null, file); await Promise.resolve(onDone(doc)); } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Upload failed'); } finally { setLoading(false); } }; return (
e.stopPropagation()}>
Upload CV
setTitle(e.target.value)} autoFocus /> setDesc(e.target.value)} />
ref.current?.click()} style={{ border: '1px dashed var(--border-strong)', borderRadius: 5, padding: '16px 0', textAlign: 'center', cursor: 'pointer', fontSize: 13, color: file ? 'var(--text)' : 'var(--text-muted)', }}> {file ? file.name : 'Click to select .docx'}
setFile(e.target.files?.[0] ?? null)} /> {error &&
{error}
}
); } function BranchModal({ version, initialPatches, onClose, onDone, }: { version: Version; initialPatches?: Array<{ target_path: string; operation: string; old_value: string; new_value: string }>; onClose: () => void; onDone: (v: Version) => void; }) { const [name, setName] = useState(''); const [label, setLabel] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const patches = initialPatches ?? []; const submit = async () => { if (!name.trim()) { setError('Branch name required.'); return; } setLoading(true); setError(''); try { const v = await createBranch(version.id, name.trim(), label.trim() || null, patches as Record[]); await Promise.resolve(onDone(v)); } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Failed'); } finally { setLoading(false); } }; return (
e.stopPropagation()} style={{ maxWidth: 460 }}>
New branch from {version.branch_name}
setName(e.target.value)} autoFocus /> setLabel(e.target.value)} /> {patches.length > 0 && (
Staged edits ({patches.length})
{patches.map((p, i) => (
± {p.target_path}
))}
)} {error &&
{error}
}
); } function SubmissionModal({ version, onClose, onDone }: { version: Version; onClose: () => void; onDone: (s: Submission) => void }) { const [company, setCompany] = useState(''); const [role, setRole] = useState(''); const [url, setUrl] = useState(''); const [jd, setJd] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const submit = async () => { if (!company.trim() || !role.trim()) { setError('Company and role required.'); return; } setLoading(true); setError(''); try { const s = await createSubmission(version.id, company.trim(), role.trim(), url.trim() || null, jd.trim() || null); await Promise.resolve(onDone(s)); } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Failed'); } finally { setLoading(false); } }; return (
e.stopPropagation()} style={{ maxWidth: 480 }}>
New submission from {version.branch_name}
setCompany(e.target.value)} autoFocus /> setRole(e.target.value)} />
setUrl(e.target.value)} />