import { useEffect, useMemo, useRef, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import { CheckCircle, Image as ImageIcon, FileText, Table, CheckSquare, Square, ArrowRight, UploadCloud, } from "react-feather"; import { postForm, putJson, request } from "../lib/api"; import { getSessionId, setStoredSessionId } from "../lib/session"; import type { Session } from "../types/session"; import { PageFooter } from "../components/PageFooter"; import { PageHeader } from "../components/PageHeader"; import { PageShell } from "../components/PageShell"; export default function ReviewSetupPage() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const sessionId = getSessionId(searchParams.toString()); const photoUploadRef = useRef(null); const dataUploadRef = useRef(null); const [session, setSession] = useState(null); const [selectedPhotoIds, setSelectedPhotoIds] = useState>( new Set(), ); const [showAllPhotos, setShowAllPhotos] = useState(false); const [statusMessage, setStatusMessage] = useState(""); const [isUploadingPhotos, setIsUploadingPhotos] = useState(false); const [isUploadingData, setIsUploadingData] = useState(false); useEffect(() => { if (!sessionId) { setStatusMessage("No active session found. Return to upload to continue."); return; } setStoredSessionId(sessionId); async function loadSession() { try { const data = await request(`/sessions/${sessionId}`); setSession(data); const initial = new Set(data.selected_photo_ids || []); const photoIds = (data.uploads?.photos ?? []).map((photo) => photo.id); if (photoIds.length > 0 && initial.size < photoIds.length) { photoIds.forEach((photoId) => initial.add(photoId)); } setSelectedPhotoIds(initial); } catch (err) { const message = err instanceof Error ? err.message : "Failed to load session."; setStatusMessage(message); } } loadSession(); }, [sessionId]); const photos = session?.uploads?.photos ?? []; const documents = session?.uploads?.documents ?? []; const dataFiles = session?.uploads?.data_files ?? []; const canContinue = selectedPhotoIds.size > 0 || (session?.uploads?.data_files?.length ?? 0) > 0; const previewCount = 9; const visiblePhotos = showAllPhotos ? photos : photos.slice(0, previewCount); const readyStatus = useMemo(() => { if (!sessionId) return "No active session found. Return to upload to continue."; if (!canContinue) return "Choose report example images to continue..."; if (selectedPhotoIds.size === 0 && (session?.uploads?.data_files?.length ?? 0) > 0) { return "Data file detected. Continue to build the report."; } return "Ready. Continue to report viewer."; }, [canContinue, selectedPhotoIds.size, session?.uploads?.data_files?.length, sessionId]); async function handleUploadPhotos(files: FileList | null) { if (!sessionId || !files || files.length === 0) return; setIsUploadingPhotos(true); setStatusMessage(""); const priorPhotoIds = new Set( (session?.uploads?.photos ?? []).map((photo) => photo.id), ); try { let updatedSession: Session | null = session; for (const file of Array.from(files)) { const formData = new FormData(); formData.append("file", file); updatedSession = await postForm( `/sessions/${sessionId}/uploads`, formData, ); } if (updatedSession) { setSession(updatedSession); const next = new Set(selectedPhotoIds); for (const photo of updatedSession.uploads?.photos ?? []) { if (!priorPhotoIds.has(photo.id)) { next.add(photo.id); } } setSelectedPhotoIds(next); } } catch (err) { const message = err instanceof Error ? err.message : "Failed to upload photos."; setStatusMessage(message); } finally { setIsUploadingPhotos(false); if (photoUploadRef.current) photoUploadRef.current.value = ""; } } async function handleUploadDataFile(files: FileList | null) { if (!sessionId || !files || files.length === 0) return; setIsUploadingData(true); setStatusMessage(""); try { const file = files[0]; const formData = new FormData(); formData.append("file", file); const updated = await postForm( `/sessions/${sessionId}/data-files`, formData, ); setSession(updated); const nextSelected = new Set(updated.selected_photo_ids || []); const allPhotoIds = (updated.uploads?.photos ?? []).map((photo) => photo.id); if (nextSelected.size < allPhotoIds.length) { allPhotoIds.forEach((photoId) => nextSelected.add(photoId)); } setSelectedPhotoIds(nextSelected); } catch (err) { const message = err instanceof Error ? err.message : "Failed to upload data file."; setStatusMessage(message); } finally { setIsUploadingData(false); if (dataUploadRef.current) dataUploadRef.current.value = ""; } } async function handleContinue() { if (!sessionId) return; if (selectedPhotoIds.size === 0 && dataFiles.length === 0) return; try { if (selectedPhotoIds.size > 0) { await putJson(`/sessions/${sessionId}/selection`, { selected_photo_ids: Array.from(selectedPhotoIds), }); } navigate(`/report-viewer?session=${encodeURIComponent(sessionId)}`); } catch (err) { const message = err instanceof Error ? err.message : "Failed to save selection."; setStatusMessage(message); } } return ( Uploads processed } />

What happens on this page

Select example photos

Choose which uploaded images should appear as example figures in the report.

Confirm documents

Ensure supporting PDFs/DOCX are correct (and later attach to export if needed).

Use Excel/CSV data

If Excel/CSV exists, it will populate report data areas automatically in later steps.

Review uploaded files

Photos

{photos.length} file{photos.length === 1 ? "" : "s"} handleUploadPhotos(event.target.files)} />

Select images to use as example figures in the report.{" "} Recommended:{" "} 2-6 images.

{photos.length === 0 ? (
No photos were uploaded.
) : ( visiblePhotos.map((photo) => { const isChecked = selectedPhotoIds.has(photo.id); return ( ); }) )}
{photos.length > previewCount ? (
Showing {visiblePhotos.length} of {photos.length} photos
) : null}
Selected for report:{" "} {selectedPhotoIds.size}

Documents

{documents.length} file{documents.length === 1 ? "" : "s"}
    {documents.length === 0 ? (
  • No supporting documents detected.
  • ) : ( documents.map((doc) => (
  • {doc.name}
    {doc.content_type || "File"}
  • )) )}
{documents.length === 0 ? (

PDFs/DOCX appear here after processing.

) : null}

Data files

{dataFiles.length} file{dataFiles.length === 1 ? "" : "s"} handleUploadDataFile(event.target.files)} />
{dataFiles.length === 0 ? (
No Excel/CSV detected
If you upload a CSV or Excel file, RepEx can auto-populate report data fields.
) : ( dataFiles.map((file) => (
{file.name} {file.content_type || "Data"}
Will populate report data areas (tables/fields).
)) )}

If present, these files will populate report tables/fields automatically.

{readyStatus}

Note: This page assumes uploads were completed on a previous processing step.

{statusMessage ? (

{statusMessage}

) : null} ); }