import { type Component, createSignal, onMount, Show, For, } from 'solid-js'; import { useParams, useNavigate, A } from '@solidjs/router'; import { ArrowLeft, ExternalLink, Trash2, AlertTriangle, Mail, Send, FileDown } from 'lucide-solid'; import { useI18n } from '~/i18n'; import { useAuth } from '~/contexts/AuthContext'; import { synthesesApi } from '~/api/syntheses'; import { articleHistoryApi } from '~/api/articleHistory'; import { isApiError } from '~/types'; import type { Synthesis, NewsItem as NewsItemType, ArticleHistoryEntry } from '~/types'; import { extractWeekNumber, formatDateLong } from '~/utils/dates'; import LoadingSpinner from '~/components/ui/LoadingSpinner'; /** Renders a single news article with its title (linked) and summary. */ const NewsItemCard: Component<{ item: NewsItemType }> = (props) => { return (

{props.item.title}

{props.item.summary}

); }; /** Renders a titled category section containing a list of {@link NewsItemCard}s. */ const Section: Component<{ title: string; items: NewsItemType[] }> = (props) => { return ( 0}>

{props.title}

{(item) => }
); }; /** * Detail view for a single synthesis, with email send and export capabilities. * * - **Email send flow**: The email input is pre-filled from the authenticated * user's address. On send, a POST is made to the backend; a success banner * auto-dismisses after 5 seconds. * - **Export download**: Markdown and PDF exports call {@link fetchFile} to * obtain a binary Response, then {@link triggerDownload} to create a * temporary `` element that triggers the browser download dialog. */ const SynthesisDetail: Component = () => { const { t } = useI18n(); const { user } = useAuth(); const params = useParams<{ id: string }>(); const navigate = useNavigate(); const [synthesis, setSynthesis] = createSignal(null); const [loading, setLoading] = createSignal(true); const [error, setError] = createSignal(null); const [showDeleteConfirm, setShowDeleteConfirm] = createSignal(false); const [isDeleting, setIsDeleting] = createSignal(false); // Email state const [email, setEmail] = createSignal(''); const [sendingEmail, setSendingEmail] = createSignal(false); const [emailSuccess, setEmailSuccess] = createSignal(false); const [emailError, setEmailError] = createSignal(null); // Export state const [exportingMarkdown, setExportingMarkdown] = createSignal(false); const [exportingPdf, setExportingPdf] = createSignal(false); const [exportError, setExportError] = createSignal(null); // Provenance state const [provenance, setProvenance] = createSignal([]); const [showProvenance, setShowProvenance] = createSignal(false); const [provenanceLoading, setProvenanceLoading] = createSignal(false); onMount(async () => { // Pre-fill email from authenticated user const currentUser = user(); if (currentUser?.email) { setEmail(currentUser.email); } try { const data = await synthesesApi.get(params.id); setSynthesis(data); } catch (err) { if (isApiError(err) && err.status === 404) { setError(t('synthesis.notFound')); } else if (isApiError(err)) { setError(err.message); } else { setError(t('synthesis.loadError')); } } finally { setLoading(false); } }); const handleDelete = async () => { const synth = synthesis(); if (!synth) return; setIsDeleting(true); try { await synthesesApi.remove(synth.id); navigate('/'); } catch (err) { if (isApiError(err)) { setError(err.message); } else { setError(t('synthesis.deleteError')); } setIsDeleting(false); setShowDeleteConfirm(false); } }; const handleSendEmail = async () => { const synth = synthesis(); if (!synth || !email()) return; setSendingEmail(true); setEmailError(null); setEmailSuccess(false); try { await synthesesApi.sendEmail(synth.id, email()); setEmailSuccess(true); setTimeout(() => setEmailSuccess(false), 5000); } catch (err) { if (isApiError(err)) { setEmailError(err.message); } else { setEmailError(t('synthesis.email.error')); } } finally { setSendingEmail(false); } }; const handleExportMarkdown = async () => { const synth = synthesis(); if (!synth) return; setExportingMarkdown(true); setExportError(null); try { await synthesesApi.exportMarkdown(synth.id); } catch (err) { if (isApiError(err)) { setExportError(err.message); } else { setExportError(t('synthesis.export.error')); } } finally { setExportingMarkdown(false); } }; const handleExportPdf = async () => { const synth = synthesis(); if (!synth) return; setExportingPdf(true); setExportError(null); try { await synthesesApi.exportPdf(synth.id); } catch (err) { if (isApiError(err)) { setExportError(err.message); } else { setExportError(t('synthesis.export.error')); } } finally { setExportingPdf(false); } }; const loadProvenance = async () => { if (provenance().length > 0) return; // Already loaded setProvenanceLoading(true); try { const data = await articleHistoryApi.getProvenance(params.id); setProvenance(data); } catch { // Provenance may not be available for old syntheses } finally { setProvenanceLoading(false); } }; const toggleProvenance = () => { const newState = !showProvenance(); setShowProvenance(newState); if (newState) loadProvenance(); }; return ( }>

{error()}

← {t('synthesis.backToHome')} } > {(synth) => (
{/* Top bar: back link + delete */}
{t('synthesis.backLink')}
{/* Delete confirmation banner */}

{t('synthesis.deleteConfirmMessage')}

{/* Title + date badge */}

{t('synthesis.title', { week: extractWeekNumber(synth().week) })}

{t('synthesis.generatedAt', { date: formatDateLong(synth().created_at) })}
{/* Email section */}
setEmail(e.currentTarget.value)} />
{/* Email success message */}
{t('synthesis.email.success')}
{/* Email error message */}
{emailError()}
{/* Export section */}

{t('synthesis.export.title')}

{/* Export error message */}
{exportError()}
{/* Sections */}
0} fallback={

{t('synthesis.noSections')}

} > {(section) =>
}
{/* Provenance */}

{t('articleHistory.provenanceEmpty')}

0}>
{(entry) => ( )}
{t('articleHistory.status')} {t('articleHistory.articleTitle')} {t('articleHistory.url')} {t('articleHistory.sourceType')} {t('articleHistory.category')}
{entry.status} {entry.title || '—'} {entry.url.length > 50 ? entry.url.slice(0, 50) + '...' : entry.url} {entry.source_type} {entry.category || '—'}
)} ); }; export default SynthesisDetail;