172 lines
6.1 KiB
React
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>
|
|
)
|
|
} |