import React, { useState, useRef, useEffect } from "react"; import { IconFile as FileType, IconMap as Map, IconDownload as Download, IconAlertCircle as AlertCircle, IconCheck as Check, IconX as X, IconSettings as Settings, IconCloudUpload as CloudUpload } from "@tabler/icons-react"; import { cn } from "../lib/utils"; // --- Types --- interface FileState { file: File | null; error: string | null; } // --- Components --- const FileCard = ({ file, onRemove, icon: Icon }: { file: File; onRemove: () => void; icon: React.ElementType }) => (

{file.name}

{(file.size / 1024 / 1024).toFixed(2)} MB

); const Dropzone = ({ accept, label, sublabel, fileState, onFileSelect, icon: Icon, color = "indigo" }: { accept: string; label: string; sublabel: string; fileState: FileState; onFileSelect: (file: File) => void; icon: React.ElementType; color?: "indigo" | "purple"; }) => { const inputRef = useRef(null); const [isDragging, setIsDragging] = useState(false); const handleDrag = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (e.type === "dragenter" || e.type === "dragover") { setIsDragging(true); } else if (e.type === "dragleave") { setIsDragging(false); } }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { onFileSelect(e.dataTransfer.files[0]); } }; const bgStyles = { indigo: "from-indigo-500/5 via-blue-500/5 to-transparent", purple: "from-purple-500/5 via-pink-500/5 to-transparent" }; const borderStyles = { indigo: "group-hover:border-indigo-500/30", purple: "group-hover:border-purple-500/30" }; return (
!fileState.file && inputRef.current?.click()} onDragEnter={handleDrag} onDragLeave={handleDrag} onDragOver={handleDrag} onDrop={handleDrop} className={cn( "relative h-56 rounded-3xl border-2 border-dashed transition-all duration-500 flex flex-col items-center justify-center p-8 text-center cursor-pointer overflow-hidden", isDragging ? "border-indigo-400 bg-indigo-500/10 scale-[1.02] shadow-2xl" : "border-white/10 hover:bg-white/[0.02]", fileState.file ? "border-solid bg-white/5 cursor-default border-white/5" : bgStyles[color], !fileState.file && borderStyles[color] )} > e.target.files?.[0] && onFileSelect(e.target.files[0])} /> {fileState.file ? (
onFileSelect(null as any)} icon={Icon} />

Archivo cargado

) : ( <>

{label}

{sublabel}

)}
); }; // --- Main App --- export default function ClimateMapApp() { const [pdf, setPdf] = useState(null); const [shp, setShp] = useState(null); const [isProcessing, setIsProcessing] = useState(false); const [resultImage, setResultImage] = useState(null); const [errorDetails, setErrorDetails] = useState(null); const [showConfig, setShowConfig] = useState(false); const [backendUrl, setBackendUrl] = useState(import.meta.env.PUBLIC_BACKEND_URL || ""); const resultRef = useRef(null); // Auto-scroll to results useEffect(() => { if (resultImage && resultRef.current) { resultRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }, [resultImage]); const handleProcess = async () => { if (!pdf || !shp) return; setIsProcessing(true); setResultImage(null); setErrorDetails(null); const formData = new FormData(); formData.append("pdf", pdf); formData.append("shp", shp); const baseUrl = backendUrl || (typeof window !== "undefined" ? window.location.origin : ""); const endpoint = `${baseUrl.replace(/\/$/, "")}/api/procesar/`; try { const response = await fetch(endpoint, { method: "POST", body: formData, }); if (!response.ok) { const errJson = await response.json().catch(() => ({ detail: response.statusText })); throw new Error(errJson.detail || "Error processing files"); } const blob = await response.blob(); const imageUrl = URL.createObjectURL(blob); // Cleanup previous ObjectURL to prevent memory leak if (resultImage) { URL.revokeObjectURL(resultImage); } setResultImage(imageUrl); } catch (err: any) { setErrorDetails(`${err.message} (URL: ${endpoint})`); } finally { setIsProcessing(false); } }; const handleReset = () => { // Cleanup ObjectURL when resetting if (resultImage) { URL.revokeObjectURL(resultImage); } setPdf(null); setShp(null); setResultImage(null); setErrorDetails(null); }; return (
{/* Main Processor Card */}
{/* Toolbar */}
{/* Header */}

Generador Inteligente

Sube el informe PDF y los archivos geográficos SHP. Nuestro algoritmo unificará los datos automáticamente.

{showConfig && (
setBackendUrl(e.target.value)} className="w-full bg-[#0b0d13] border-none rounded-lg px-4 py-3 text-white/80 placeholder:text-white/20 focus:ring-1 focus:ring-indigo-500 font-mono text-sm text-center" placeholder="http://localhost:8000" />
)}
{/* Upload Grid */}
setPdf(f)} icon={FileType} color="indigo" /> setShp(f)} icon={Map} color="purple" />
{/* Action Area */}
{(!pdf || !shp) && (

Sube ambos archivos para continuar

)}
{/* Error Message */} {errorDetails && (

Error en el proceso

{errorDetails}

)}
{/* Results View */} {resultImage && (
{/* Result Header */}

Mapa Generado con Éxito

Zonificación completada

Descargar PNG
{/* Image Container */}
Mapa Climático
)}
); }