Files
formularios_front/src/containers/edit-evento.tsx
T

244 lines
8.6 KiB
TypeScript

'use client';
import ImageUploader from '@/components/banner-uploader';
import Button from '@/components/button';
import Input from '@/components/input';
import Select from '@/components/select';
import EventoCardPreview from '@/components/evento/evento-card-preview';
import { CreateEventoType } from '@/types/create-evento';
import { GetEventoWithCuestionariosWithCupos } from '@/types/evento';
import axiosInstance from '@/utils/api-config';
import { tipoEventos } from '@/utils/arrays';
import { formatDateLocal } from '@/utils/date-utils';
import { getAxiosError } from '@/utils/errors-utils';
import { commands } from '@uiw/react-md-editor';
import dynamic from 'next/dynamic';
import React, { useState } from 'react';
import toast from 'react-hot-toast';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
const MAX_LENGTH = 500;
interface EditEventoProps {
evento: GetEventoWithCuestionariosWithCupos;
handleChange: (field: keyof CreateEventoType, value: string | Date) => void;
handleOnChange: (
eventoActualizado: GetEventoWithCuestionariosWithCupos
) => void;
}
export default function EditEvento({
evento,
handleChange,
handleOnChange,
}: EditEventoProps) {
const tipo_usuario = sessionStorage.getItem('axxxd')?.replace(/"/g, '') || '';
const [loading, setLoading] = useState(false);
const [nuevoBanner, setNuevoBanner] = useState<File | null>(null);
const handleOnSave = async () => {
setLoading(true);
try {
// 1. Subir nuevo banner si fue cambiado
if (nuevoBanner) {
const formData = new FormData();
formData.append('banner', nuevoBanner);
await axiosInstance.post(
`/evento/${evento.id_evento}/banner`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
}
// 2. Actualizar el evento (sin incluir 'banner')
const res = await axiosInstance.patch(`/evento/${evento.id_evento}`, {
nombre_evento: evento.nombre_evento,
descripcion_evento: evento.descripcion_evento,
tipo_evento: evento.tipo_evento,
fecha_inicio: evento.fecha_inicio,
fecha_fin: evento.fecha_fin,
});
toast.success('Evento actualizado correctamente');
handleOnChange(res.data); // Actualiza en el padre
} catch (error) {
const msg = getAxiosError(error);
toast.error(`Error al actualizar el evento: ${msg.message}`);
} finally {
setLoading(false);
}
};
// Convertir evento a CreateEventoType para el preview
const eventoPreview: CreateEventoType = {
tipo_evento: evento.tipo_evento,
nombre_evento: evento.nombre_evento,
descripcion_evento: evento.descripcion_evento,
fecha_inicio: new Date(evento.fecha_inicio),
fecha_fin: new Date(evento.fecha_fin),
};
return (
<div className="container-fluid my-4">
{/* Header */}
<div className="row mb-4">
<div className="col-12">
<div className="p-4 border rounded bg-light">
<h2 className="mb-2">Editar Información del Evento</h2>
<p className="mb-0 text-muted">
Modifica los datos del evento que desees actualizar y confirmalos.
</p>
</div>
</div>
</div>
<div className="row">
{/* Columna Izquierda - Formulario */}
<div className="col-xl-7 col-lg-6">
<div className="pe-lg-4">
<div className="p-4">
{/* Banner */}
<div className="mb-4">
{tipo_usuario === 'administrador' && (
<>
<ImageUploader
label="Banner del evento"
defaultBanner={
evento.banner
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${evento.banner}`
: undefined
}
onChange={(file) => {
setNuevoBanner(file);
}}
/>
<p className="text-muted mt-2 small">
<strong>
<i className="bi bi-info-circle-fill me-2"></i>
Esta es la imagen que se verá en el carrusel del evento.
</strong>
<br />
<strong>
<i className="bi bi-image me-2"></i>
Dimensiones recomendadas: 1200x600 píxeles.
</strong>
<br />
<strong>
<i className="bi bi-file-earmark-image me-2"></i>
Formatos soportados: PNG, JPG, JPEG.
</strong>
</p>
</>
)}
</div>
{/* Detalles del evento */}
<div className="col-12">
<h4 className="mb-3">Detalles del Evento</h4>
<Input
label="Nombre del evento"
required
value={evento.nombre_evento}
onChange={(e) =>
handleChange('nombre_evento', e.target.value)
}
disabled={tipo_usuario !== 'administrador'}
/>
<div className="mb-4">
<label className="form-label">Descripción del evento</label>
<div data-color-mode="light">
<MDEditor
value={evento.descripcion_evento ?? ''}
onChange={(value) => {
if (tipo_usuario !== 'administrador') return;
const texto = value || '';
if (texto.length <= MAX_LENGTH) {
handleChange('descripcion_evento', texto);
}
}}
height={200}
commands={[commands.bold, commands.italic, commands.hr]}
/>
</div>
</div>
<Select
label="Tipo de evento"
value={evento.tipo_evento}
placeholder="Selecciona un tipo de evento"
onChange={(e) => handleChange('tipo_evento', e.target.value)}
options={tipoEventos}
disabled={tipo_usuario !== 'administrador'}
/>
<div className="row">
<div className="col-md-6">
<Input
label="Fecha de inicio"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(evento.fecha_inicio))}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
/>
</div>
<div className="col-md-6">
<Input
label="Fecha de fin"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(evento.fecha_fin))}
onChange={(e) =>
handleChange('fecha_fin', new Date(e.target.value))
}
/>
</div>
</div>
</div>
{/* Botón de guardar */}
{tipo_usuario === "administrador" && <div className="d-flex justify-content-end mt-4">
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
</div>}
</div>
</div>
</div>
{/* Columna Derecha - Preview */}
<div className="col-xl-5 col-lg-6">
<div className="ps-lg-4">
<div className="sticky-top" style={{ top: '20px' }}>
<h4 className="mb-3 d-none d-lg-block">Preview del Evento</h4>
<div className="d-lg-none mt-4">
<h4 className="mb-3">Preview del Evento</h4>
</div>
<EventoCardPreview
evento={eventoPreview}
eventoBanner={nuevoBanner}
existingBanner={evento.banner}
cuestionariosCount={evento.cuestionarios?.length || 0}
/>
<div className="alert alert-info text-center mt-3">
<strong>Vista previa:</strong> Así se verá tu evento en las
listas de eventos.
</div>
</div>
</div>
</div>
</div>
</div>
);
}