Files
OPSV---Dashboard-de-Siniest…/src/components/ui/PdfExportModal.jsx
T

172 lines
6.1 KiB
React

import { useState } from 'react'
import { FileDown, Loader2, X } from 'lucide-react'
import { exportarPDF, SECCIONES_EXPORTABLES } from '../../utils/exportPdf'
export default function PdfExportModal({ year, onClose }) {
const [seleccionadas, setSeleccionadas] = useState(
SECCIONES_EXPORTABLES.map(s => s.id)
)
const [generando, setGenerando] = useState(false)
const [progreso, setProgreso] = useState(0)
const [error, setError] = useState(null)
function toggleSeccion(id) {
setSeleccionadas(prev =>
prev.includes(id) ? prev.filter(s => s !== id) : [...prev, id]
)
}
function toggleTodas() {
setSeleccionadas(prev =>
prev.length === SECCIONES_EXPORTABLES.length
? []
: SECCIONES_EXPORTABLES.map(s => s.id)
)
}
async function handleExportar() {
if (!seleccionadas.length) return
setGenerando(true)
setProgreso(0)
setError(null)
try {
await exportarPDF({
seccionesIds: SECCIONES_EXPORTABLES
.map(s => s.id)
.filter(id => seleccionadas.includes(id)),
year,
onProgress: setProgreso,
})
onClose()
} catch (e) {
console.error(e)
setError('Ocurrió un error al generar el PDF. Intentá de nuevo.')
} finally {
setGenerando(false)
}
}
const todasMarcadas = seleccionadas.length === SECCIONES_EXPORTABLES.length
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/40 backdrop-blur-sm"
onClick={!generando ? onClose : undefined}
/>
{/* Modal */}
<div className="relative w-full max-w-md rounded-3xl border border-opsv-border dark:border-slate-700 bg-white dark:bg-slate-900 p-6 shadow-2xl">
{/* Header */}
<div className="flex items-start justify-between mb-5">
<div>
<h2 className="text-lg font-black text-opsv-navy dark:text-white">
Exportar a PDF
</h2>
<p className="mt-1 text-sm text-opsv-muted dark:text-slate-400">
Seleccioná las secciones a incluir en el informe {year}.
</p>
</div>
{!generando && (
<button
onClick={onClose}
className="rounded-full p-1.5 hover:bg-opsv-bg dark:hover:bg-slate-800 transition"
aria-label="Cerrar"
>
<X className="h-4 w-4 text-opsv-muted dark:text-slate-400" />
</button>
)}
</div>
{/* Seleccionar todas */}
<button
type="button"
onClick={toggleTodas}
disabled={generando}
className="mb-3 text-xs font-semibold text-opsv-blue dark:text-sky-400 hover:underline disabled:opacity-50"
>
{todasMarcadas ? 'Deseleccionar todas' : 'Seleccionar todas'}
</button>
{/* Checkboxes */}
<div className="flex flex-col gap-2 mb-5">
{SECCIONES_EXPORTABLES.map(sec => (
<label
key={sec.id}
className={`flex items-center gap-3 rounded-2xl border px-4 py-3 cursor-pointer transition
${seleccionadas.includes(sec.id)
? 'border-opsv-blue bg-opsv-blue/5 dark:border-sky-500 dark:bg-sky-500/10'
: 'border-opsv-border dark:border-slate-700 bg-white dark:bg-slate-800 hover:border-opsv-navy/30'
}
${generando ? 'opacity-50 cursor-not-allowed' : ''}
`}
>
<input
type="checkbox"
checked={seleccionadas.includes(sec.id)}
onChange={() => !generando && toggleSeccion(sec.id)}
className="h-4 w-4 rounded accent-opsv-navy dark:accent-sky-400"
/>
<span className="text-sm font-medium text-opsv-navy dark:text-slate-100">
{sec.label}
</span>
</label>
))}
</div>
{/* Barra de progreso */}
{generando && (
<div className="mb-4">
<div className="flex items-center justify-between mb-1.5">
<span className="text-xs text-opsv-muted dark:text-slate-400">
Generando PDF...
</span>
<span className="text-xs font-semibold text-opsv-navy dark:text-white">
{progreso}%
</span>
</div>
<div className="h-2 w-full rounded-full bg-opsv-border dark:bg-slate-700 overflow-hidden">
<div
className="h-full rounded-full bg-opsv-navy dark:bg-sky-500 transition-all duration-300"
style={{ width: `${progreso}%` }}
/>
</div>
</div>
)}
{/* Error */}
{error && (
<div className="mb-4 rounded-2xl border border-red-200 bg-red-50 dark:border-red-900/40 dark:bg-red-950/30 px-3 py-2 text-xs text-red-700 dark:text-red-300">
{error}
</div>
)}
{/* Botones */}
<div className="flex items-center justify-between gap-3">
<button
type="button"
onClick={onClose}
disabled={generando}
className="rounded-2xl border border-opsv-border dark:border-slate-700 bg-white dark:bg-slate-800 px-4 py-2.5 text-sm font-medium text-opsv-muted dark:text-slate-300 hover:text-opsv-navy dark:hover:text-white transition disabled:opacity-50"
>
Cancelar
</button>
<button
type="button"
onClick={handleExportar}
disabled={generando || seleccionadas.length === 0}
className="flex items-center gap-2 rounded-2xl bg-opsv-navy dark:bg-slate-700 px-5 py-2.5 text-sm font-semibold text-white hover:bg-opsv-navy-dark dark:hover:bg-slate-600 transition disabled:cursor-not-allowed disabled:opacity-50"
>
{generando
? <><Loader2 className="h-4 w-4 animate-spin" /> Generando...</>
: <><FileDown className="h-4 w-4" /> Descargar PDF</>
}
</button>
</div>
</div>
</div>
)
}