"use client"; import Link from "next/link"; import { ChangeEvent, DragEvent, useEffect, useMemo, useState } from "react"; import { api, DocumentItem, Job, Source, Subject, Week } from "@/lib/api"; import { Shell } from "@/components/Shell"; import { Button, Card, ConfirmDialog, StatusBadge } from "@/components/ui"; type PendingDelete = | { type: "document"; item: DocumentItem } | { type: "source"; item: Source }; export default function WeekPage({ params }: { params: { subjectId: string; weekNumber: string } }) { const subjectId = Number(params.subjectId); const weekNumber = Number(params.weekNumber); const [subject, setSubject] = useState(null); const [week, setWeek] = useState(null); const [sources, setSources] = useState([]); const [jobs, setJobs] = useState([]); const [documents, setDocuments] = useState([]); const [pdfMethod, setPdfMethod] = useState<"standard" | "mistral_ocr">("standard"); const [pageMode, setPageMode] = useState<"all" | "ranges">("all"); const [pageRanges, setPageRanges] = useState(""); const [pendingFiles, setPendingFiles] = useState([]); const [uploading, setUploading] = useState(false); const [deletingId, setDeletingId] = useState(null); const [reprocessingId, setReprocessingId] = useState(null); const [pendingDelete, setPendingDelete] = useState(null); const [dragActive, setDragActive] = useState(false); const [error, setError] = useState(""); const activeJobs = useMemo(() => jobs.some((job) => ["queued", "processing"].includes(job.status)), [jobs]); const pendingPdfCount = useMemo(() => pendingFiles.filter(isPdfFile).length, [pendingFiles]); async function load() { const [subjectData, weekData, sourceList, jobList, documentList] = await Promise.all([ api.subject(subjectId), api.week(subjectId, weekNumber), api.sources(subjectId, weekNumber), api.jobs(subjectId, weekNumber), api.documents(subjectId, weekNumber), ]); setSubject(subjectData); setWeek(weekData); setSources(sourceList); setJobs(jobList); setDocuments(documentList); } useEffect(() => { load().catch((err) => setError(err.message)); }, [subjectId, weekNumber]); useEffect(() => { if (!activeJobs) return; const timer = window.setInterval(() => load().catch((err) => setError(err.message)), 2500); return () => window.clearInterval(timer); }, [activeJobs]); async function uploadFileList(files: File[]) { if (!files.length) return; setUploading(true); setError(""); try { const ranges = pageMode === "ranges" ? pageRanges : undefined; for (const file of files) await api.upload(subjectId, weekNumber, file, pdfMethod === "mistral_ocr", ranges); await load(); } catch (err) { setError(err instanceof Error ? err.message : "No se pudo subir archivo"); } finally { setUploading(false); setDragActive(false); } } function isPdfFile(file: File) { return file.type === "application/pdf" || file.name.toLowerCase().endsWith(".pdf"); } async function prepareFiles(files: File[]) { if (!files.length) return; setError(""); setDragActive(false); if (files.some(isPdfFile)) { setPendingFiles(files); return; } await uploadFileList(files); } async function confirmPendingUpload() { const files = pendingFiles; setPendingFiles([]); await uploadFileList(files); } async function uploadFiles(event: ChangeEvent) { const files = Array.from(event.target.files || []); try { await prepareFiles(files); } finally { event.target.value = ""; } } function handleDrag(event: DragEvent) { event.preventDefault(); event.stopPropagation(); if (uploading) return; if (event.type === "dragenter" || event.type === "dragover") setDragActive(true); if (event.type === "dragleave") setDragActive(false); } async function handleDrop(event: DragEvent) { event.preventDefault(); event.stopPropagation(); if (uploading) return; const files = Array.from(event.dataTransfer.files || []); await prepareFiles(files); } async function deleteDocument(document: DocumentItem) { setDeletingId(`document-${document.id}`); setError(""); try { await api.deleteDocument(document.id); await load(); setPendingDelete(null); } catch (err) { setError(err instanceof Error ? err.message : "No se pudo eliminar el documento"); } finally { setDeletingId(null); } } async function deleteSource(source: Source) { setDeletingId(`source-${source.id}`); setError(""); try { await api.deleteSource(source.id); await load(); setPendingDelete(null); } catch (err) { setError(err instanceof Error ? err.message : "No se pudo eliminar la fuente"); } finally { setDeletingId(null); } } async function reprocessSource(source: Source) { setReprocessingId(source.id); setError(""); try { const ranges = source.source_type === "pdf" && pageMode === "ranges" ? pageRanges : undefined; await api.reprocessSource(source.id, source.source_type === "pdf" && pdfMethod === "mistral_ocr", ranges); await load(); } catch (err) { setError(err instanceof Error ? err.message : "No se pudo reprocesar la fuente"); } finally { setReprocessingId(null); } } function formatFileSize(size?: number | null) { if (!size) return "tamano pendiente"; if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`; return `${(size / (1024 * 1024)).toFixed(1)} MB`; } function sourceProcessingLabel(source: Source) { if (source.document_id) return `Genero: ${source.document_title}`; if (["queued", "processing"].includes(source.status)) return "Procesamiento en curso"; if (source.status === "failed") return "Fallo el procesamiento"; return "Sin Markdown generado"; } function confirmPendingDelete() { if (!pendingDelete) return; if (pendingDelete.type === "document") void deleteDocument(pendingDelete.item); if (pendingDelete.type === "source") void deleteSource(pendingDelete.item); } const deleteDialog = pendingDelete ? { title: pendingDelete.type === "document" ? "Eliminar Markdown procesado" : "Eliminar fuente", description: pendingDelete.type === "document" ? `Se eliminara el archivo Markdown "${pendingDelete.item.title}". La fuente original quedara disponible.` : `Se eliminara la fuente "${pendingDelete.item.original_name}" junto con sus trabajos y Markdown procesado asociado.`, } : null; return ( Volver a {subject?.name || "materia"}

Semana {weekNumber}

{week?.title || "Sube fuentes y genera Markdown procesado."}

{error && {error}}

Subir archivos

PDF, DOCX, TXT, Markdown, audio y video.

Arrastra archivos aqui

Puedes soltar varios archivos a la vez. Se procesaran en esta semana.

Soporta PDF, DOCX, TXT, Markdown, audio y video.

{uploading &&

Subiendo archivos...

}
{pendingFiles.length > 0 && (

Configurar PDFs antes de subir

Detecte {pendingPdfCount} PDF en {pendingFiles.length} archivo(s). Elige como procesarlos antes de continuar.

{pageMode === "ranges" && (
setPageRanges(event.target.value)} placeholder="Ej: 1-5, 8, 10-12" />

Separa paginas o rangos con comas. Esta seleccion se aplicara a los PDFs pendientes.

)}

Los archivos que no sean PDF se subiran junto con esta tanda.

)}
{documents.map((doc) => (
{doc.title}
Fuente: {doc.source_original_name || `#${doc.source_id}`}
{doc.markdown_path}
))} {documents.length === 0 &&

Aun no hay documentos procesados.

}
setPendingDelete(null)} />
); }