diff --git a/src/app/(admins)/administrador/evento/[id_evento]/formulario/[id_cuestionario]/page.tsx b/src/app/(admins)/administrador/evento/[id_evento]/formulario/[id_cuestionario]/page.tsx deleted file mode 100644 index bb8a754..0000000 --- a/src/app/(admins)/administrador/evento/[id_evento]/formulario/[id_cuestionario]/page.tsx +++ /dev/null @@ -1,192 +0,0 @@ -'use client'; -import Breadcrumb from '@/components/breadcrumb'; -import SimpleInput from '@/components/input'; -import Table, { Header } from '@/components/table'; -import EditFormulario from '@/containers/edit-formulario'; -import { useGetApi } from '@/hooks/use-get-api'; -import { GetCuestionario } from '@/types/cuestionario'; -import { ParticipacionEvento } from '@/types/participante-evento'; -import axiosInstance from '@/utils/api-config'; -import { useParams } from 'next/navigation'; -import React, { useEffect } from 'react'; -import toast from 'react-hot-toast'; -import { useEvento } from '@/context/evento'; - -type Params = { - id_evento: string; - id_cuestionario: string; -}; - -export default function Page() { - const params = useParams(); - const { evento } = useEvento(); - - const [cuestionario, setCuestionario] = - React.useState(null); - const [busquedaCorreo, setBusquedaCorreo] = React.useState(''); - const [loadingAsistencia, setLoadingAsistencia] = React.useState< - Record - >({}); - - const { data: participantes, setData } = useGetApi( - `/participante-evento/evento/${params.id_cuestionario}` - ); - const { data } = useGetApi( - `/cuestionario/${params.id_cuestionario}` - ); - - useEffect(() => { - if (data) setCuestionario(data); - }, [data]); - - const confirmarAsistencia = async ( - id_participante: number, - id_evento: number - ) => { - setLoadingAsistencia((prev) => ({ ...prev, [id_participante]: true })); - try { - await axiosInstance.post( - `/participante-evento/asistencia/${id_participante}/${id_evento}` - ); - - toast.success('Asistencia confirmada'); - // Actualiza lista - const { data } = await axiosInstance.get( - `/participante-evento/evento/${id_evento}` - ); - setData(data); - } catch (error) { - toast.error('Error al confirmar asistencia'); - console.error(error); - } finally { - setLoadingAsistencia((prev) => ({ ...prev, [id_participante]: false })); - } - }; - - const headers: Header[] = [ - { - key: 'id_participante', - label: '#', - render: (_, row) => row.participante.id_participante, - }, - { - key: 'participante', - label: 'Correo', - render: (_, row) => row.participante.correo, - }, - { - key: 'fecha_registro', - label: 'Fecha registro', - render: (_, row) => - row.fecha_registro - ? new Date(row.fecha_registro).toLocaleString() - : 'Sin registro', - }, - { - key: 'estatus', - label: 'Asistencia', - render: (_, row) => { - const isLoading = loadingAsistencia[row.id_participante]; - - return row.fecha_asistencia ? ( - - - Asistió - - ) : ( - - ); - }, - }, - ]; - - const handleChange = (field: keyof GetCuestionario, value: string | Date) => { - setCuestionario((prev) => - prev - ? { - ...prev, - [field]: value, - } - : null - ); - }; - - const handleCuestionarioActualizado = ( - eventoActualizado: GetCuestionario - ) => { - setCuestionario(eventoActualizado); - }; - - const participantesFiltrados = - participantes && - participantes.filter((p) => - p.participante.correo.toLowerCase().includes(busquedaCorreo.toLowerCase()) - ); - - const breadcrumbItems = [ - { label: 'Inicio', href: '/administrador/eventos' }, - { - label: evento?.nombre_evento || 'Evento', - href: `/administrador/evento/${params.id_evento}`, - }, - { label: cuestionario?.nombre_form || 'Formulario' }, - ]; - - return ( -
- - - {cuestionario && ( - - )} - -

Participantes

- - {participantes?.length === 0 && ( -

No hay participantes registrados aún.

- )} - - {participantes && participantes.length > 0 && ( - <> - setBusquedaCorreo(e.target.value)} - /> - -
- row.id_participante} - /> - - - )} - - ); -} diff --git a/src/app/(admins)/administrador/evento/[id_evento]/formularios/crear/page.tsx b/src/app/(admins)/administrador/evento/[id_evento]/formularios/crear/page.tsx deleted file mode 100644 index 36aeb2b..0000000 --- a/src/app/(admins)/administrador/evento/[id_evento]/formularios/crear/page.tsx +++ /dev/null @@ -1,189 +0,0 @@ -'use client'; -import { plantillasDisponibles } from '@/data/plantillas'; -import { FormularioCreacion } from '@/types/create-formulario'; -import Image from 'next/image'; -import React, { useState } from 'react'; -import CreateFormulario from '@/containers/create-formulario'; -import Button from '@/components/button'; -import Breadcrumb from '@/components/breadcrumb'; -import { useEvento } from '@/context/evento'; -import { useParams, useRouter } from 'next/navigation'; -import axiosInstance from '@/utils/api-config'; -import { getAxiosError } from '@/utils/errors-utils'; -import toast from 'react-hot-toast'; - -type Params = { - id_evento: string; -}; - -export default function Page() { - const params = useParams(); - const router = useRouter(); - const { evento, refetch } = useEvento(); - const [datosFormulario, setDatosFormulario] = - useState(null); - - const handleSeleccionarPlantilla = (plantillaId: string) => { - const seleccionada = plantillasDisponibles.find( - (p) => p.id === plantillaId - ); - if (seleccionada) { - setDatosFormulario(seleccionada.datos); - console.log('Plantilla seleccionada:', seleccionada); - } - }; - - const breadcrumbItems = [ - { label: 'Inicio', href: '/administrador/eventos' }, - { - label: evento?.nombre_evento || 'Evento', - href: `/administrador/evento/${params.id_evento}`, - }, - { label: 'Crear formulario' }, - ]; - - const handleCrearEvento = async () => { - if (!datosFormulario || !evento) { - toast.error('Faltan datos para crear el formulario'); - return; - } - - // Validar fechas del formulario respecto al evento - const fechaInicioFormulario = new Date(datosFormulario.fecha_inicio); - const fechaFinFormulario = new Date(datosFormulario.fecha_fin); - const fechaInicioEvento = new Date(evento.fecha_inicio); - const fechaFinEvento = new Date(evento.fecha_fin); - - // Convertir a solo fecha (sin hora) para comparación - const fechaInicioFormularioSoloFecha = new Date( - fechaInicioFormulario.getFullYear(), - fechaInicioFormulario.getMonth(), - fechaInicioFormulario.getDate() - ); - const fechaFinFormularioSoloFecha = new Date( - fechaFinFormulario.getFullYear(), - fechaFinFormulario.getMonth(), - fechaFinFormulario.getDate() - ); - const fechaInicioEventoSoloFecha = new Date( - fechaInicioEvento.getFullYear(), - fechaInicioEvento.getMonth(), - fechaInicioEvento.getDate() - ); - const fechaFinEventoSoloFecha = new Date( - fechaFinEvento.getFullYear(), - fechaFinEvento.getMonth(), - fechaFinEvento.getDate() - ); - - console.log('Fechas del formulario:', { - inicio: fechaInicioFormulario, - fin: fechaFinFormulario, - }); - console.log('Fechas del evento:', { - inicio: fechaInicioEvento, - fin: fechaFinEvento, - }); - - if (fechaInicioFormularioSoloFecha < fechaInicioEventoSoloFecha) { - toast.error( - 'La fecha de inicio del formulario debe ser posterior o igual a la fecha de inicio del evento' - ); - return; - } - - if (fechaFinFormularioSoloFecha > fechaFinEventoSoloFecha) { - toast.error( - 'La fecha de fin del formulario debe ser anterior o igual a la fecha de fin del evento' - ); - return; - } - - if (fechaInicioFormulario > fechaFinFormulario) { - toast.error( - 'La fecha de inicio del formulario debe ser anterior a la fecha de fin' - ); - return; - } - - try { - const res = await axiosInstance.post(`/cuestionario`, { - id_evento: evento?.id_evento || params.id_evento, - ...datosFormulario, - }); - console.log('Formulario creado:', res.data); - refetch(); - toast.success('Formulario creado exitosamente'); - router.push(`/administrador/evento/${params.id_evento}`); - } catch (error) { - const msg = getAxiosError(error); - toast.error(msg.message || 'Error al crear el formulario'); - } - }; - - return ( -
- - -
-

Creación de formulario

-

- Esta sección te permite crear y configurar sub-eventos en forma de - formularios asociados al evento. Selecciona una plantilla compatible, - ajusta los campos necesarios y publícalo para habilitar el registro, - encuestas o confirmaciones dentro de este evento. -

-
- {!datosFormulario && ( -
-

- Selecciona una plantilla para el formulario: -

-

- Elige una de las plantillas disponibles para crear tu formulario de - registro. -
- Estas plantillas están diseñadas para garantizar la correcta - integración con el sistema, permitiendo que los datos se obtengan y - se prellenan automáticamente de forma precisa. -
- - Para asegurar el funcionamiento óptimo del prellenado, modifica la - estructura solo si es necesario. - -

- -
- {plantillasDisponibles.map((plantilla) => ( -
handleSeleccionarPlantilla(plantilla.id)} - > -
- -
-
- ))} -
-
- )} - {datosFormulario && ( - setDatosFormulario({ ...nuevo })} - /> - )} - -
- ); -} diff --git a/src/app/(admins)/administrador/evento/[id_evento]/layout.tsx b/src/app/(admins)/administrador/evento/[id_evento]/layout.tsx deleted file mode 100644 index b82638e..0000000 --- a/src/app/(admins)/administrador/evento/[id_evento]/layout.tsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client'; -import { EventoProvider } from '@/context/evento/evento-context'; -import { useParams } from 'next/navigation'; -import React from 'react'; - -type Params = { - id_evento: string; -}; - -export default function Layout({ children }: { children: React.ReactNode }) { - const params = useParams(); - - if (!params.id_evento) { - return
Error: ID de evento no encontrado
; - } - - return ( - {children} - ); -} diff --git a/src/app/(admins)/administrador/evento/[id_evento]/page.tsx b/src/app/(admins)/administrador/evento/[id_evento]/page.tsx deleted file mode 100644 index 8181bbc..0000000 --- a/src/app/(admins)/administrador/evento/[id_evento]/page.tsx +++ /dev/null @@ -1,130 +0,0 @@ -'use client'; -import EditEvento from '@/containers/edit-evento'; -import { GetEventoWithCuestionariosWithCupos } from '@/types/evento'; -import { useParams, useRouter } from 'next/navigation'; -import React, { useState } from 'react'; -import { CreateEventoType } from '@/types/create-evento'; -import { useEvento } from '@/context/evento'; -import axiosInstance from '@/utils/api-config'; -import toast from 'react-hot-toast'; -import { downloadFile } from '@/utils/downloas-utils'; -import NewCuestionarioCard from '@/components/new-cuestionario-card'; -import Breadcrumb from '@/components/breadcrumb'; -import Button from '@/components/button'; - -type Params = { - id_evento: string; -}; - -export default function Page() { - const router = useRouter(); - const params = useParams(); - const { evento, loading, error, updateEvento, setEventoData } = useEvento(); - - const [loadingStates, setLoadingStates] = useState>( - {} - ); - - const handleChange = ( - field: keyof CreateEventoType, - value: string | Date - ) => { - // Update the evento state directly using the context - updateEvento({ [field]: value }); - }; - - const handleEventoActualizado = ( - eventoActualizado: GetEventoWithCuestionariosWithCupos - ) => { - // Set the complete updated evento data - setEventoData(eventoActualizado); - }; - - if (loading) return

Cargando...

; - if (error) return

Error: {error}

; - if (!evento) return

No se encontró el evento

; - - const handleDownload = async (id_cuestionario: number, nombre: string) => { - setLoadingStates((prev) => ({ ...prev, [id_cuestionario]: true })); - - try { - const res = await axiosInstance.get( - `/cuestionario-respondido/reporte-respuestas/${id_cuestionario}`, - { - responseType: 'blob', - } - ); - - downloadFile(res.data, `${nombre}`, 'csv'); - } catch (error) { - console.error('Error al descargar el archivo:', error); - toast.error('Error al descargar el archivo'); - } finally { - setLoadingStates((prev) => ({ ...prev, [id_cuestionario]: false })); - } - }; - - return ( -
- - - - -
-
-
-

Formularios del evento

-

- Aquí puedes ver y gestionar los formularios asociados a este - evento. -

-
- -
-
- - {evento?.cuestionarios && evento.cuestionarios.length > 0 && ( -
- {evento.cuestionarios.map((item, key) => { - const fadeClass = `delay-${(key % 5) + 1}`; - return ( -
- -
- ); - })} -
- )} -
- ); -} diff --git a/src/app/(admins)/administrador/eventos/crear/page.tsx b/src/app/(admins)/administrador/eventos/crear/page.tsx deleted file mode 100644 index 45ba8fd..0000000 --- a/src/app/(admins)/administrador/eventos/crear/page.tsx +++ /dev/null @@ -1,128 +0,0 @@ -'use client'; - -import React, { useState } from 'react'; -import { CreateEventoType } from '@/types/create-evento'; -import CreateEvento from '@/containers/create-evento'; -import EventoCardPreview from '@/components/evento/evento-card-preview'; -import axiosInstance from '@/utils/api-config'; -import toast from 'react-hot-toast'; -import { useRouter } from 'next/navigation'; -import { getAxiosError } from '@/utils/errors-utils'; -import Button from '@/components/button'; - -export default function Page() { - const [eventoBanner, setEventoBanner] = useState(null); - const [evento, setEvento] = useState({ - tipo_evento: '', - nombre_evento: '', - descripcion_evento: '', - fecha_inicio: new Date(), - fecha_fin: new Date(), - }); - const router = useRouter(); - - const handleChange = ( - field: keyof CreateEventoType, - value: string | Date - ) => { - setEvento((prev) => ({ - ...prev, - [field]: value, - })); - }; - - const handleBannerChange = (file: File | null) => { - setEventoBanner(file); - }; - - const handleCrearEvento = async () => { - try { - const createEvento = await axiosInstance.post('/evento', evento); - - if (createEvento) { - const formData = new FormData(); - if (eventoBanner) { - formData.append('banner', eventoBanner); - const bannercreated = await axiosInstance.post( - `/evento/${createEvento.data.id_evento}/banner`, - formData, - { - headers: { - 'Content-Type': 'multipart/form-data', - }, - } - ); - console.log('Banner creado:', bannercreated.data); - } - } - - toast.success('Evento y formulario creados exitosamente'); - router.push( - `/administrador/evento/${createEvento.data.id_evento}/formularios/crear` - ); - } catch (error) { - const msg = getAxiosError(error); - toast.error( - `Error al crear el evento o formulario: ${ - msg.message || 'Error desconocido' - }` - ); - console.error('Error al crear evento y formulario:', error); - } - }; - - return ( -
- {/* Header */} -
-
-
-

Creación del Evento

-

- Completa la siguiente información para describir los detalles - generales del evento. Presiona siguiente para continuar con la - creación del formulario. -

-
-
-
- -
- {/* Columna Izquierda - Formulario */} -
-
- - - {/* Botón de guardar */} -
- -
-
-
- - {/* Columna Derecha - Preview */} -
-
-
-

Preview del Evento

-
-

Preview del Evento

-
- -
- Nota: Esta preview es la que veras en tu ruta - de eventos y en la ruta publica de eventos -
-
-
-
-
-
- ); -} diff --git a/src/app/(admins)/administrador/eventos/page.tsx b/src/app/(admins)/administrador/eventos/page.tsx deleted file mode 100644 index 3715f07..0000000 --- a/src/app/(admins)/administrador/eventos/page.tsx +++ /dev/null @@ -1,67 +0,0 @@ -'use client'; - -import EventoCardAdmin from '@/components/evento/evento-card-admin'; -import FormularioCardAdmin from '@/components/formulario/formulario-card-admin'; -import { useGetApi } from '@/hooks/use-get-api'; -import { GetEventoWithCuestionariosWithCupos } from '@/types/evento'; -import React from 'react'; - -export default function Page() { - const { - loading, - data: activos, - error, - } = useGetApi( - '/evento/activos/cuestionarios' - ); - const { data: recientes } = useGetApi( - '/evento/recientes/cuestionarios' - ); - - if (loading) return
Cargando formularios...
; - if (error) return
{error.message}
; - - return ( -
- {loading &&
Cargando...
} - {activos && activos.length > 0 && ( -
-

Eventos Activos

- {activos.map((evento, key) => { - const fadeClass = `delay-${(key % 5) + 1}`; - return ( -
- -
- ); - })} -
- )} - {recientes && recientes.length > 0 && ( -
-

Formularios Recientes

-

Eventos de los ultimos 30 días.

- {recientes.map((evento) => - evento.cuestionarios.map((cuestionario, cuestionarioIndex) => { - const fadeClass = `delay-${(cuestionarioIndex % 5) + 1}`; - return ( -
- -
- ); - }) - )} -
- )} -
- ); -} diff --git a/src/app/(admins)/administrador/layout.tsx b/src/app/(admins)/administrador/layout.tsx deleted file mode 100644 index 239525a..0000000 --- a/src/app/(admins)/administrador/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import Footer from '@/components/layout/footer'; -import Header from '@/components/layout/header'; -import Navbar from '@/components/navbar'; -import React from 'react'; - -export default function Layout({ children }: { children: React.ReactNode }) { - return ( -
-
- -
{children}
-
-
- ); -} diff --git a/src/app/(admins)/administrador/profesores/page.tsx b/src/app/(admins)/administrador/profesores/page.tsx deleted file mode 100644 index ac94232..0000000 --- a/src/app/(admins)/administrador/profesores/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client'; -import SimpleInput from '@/components/input'; -import axiosInstance from '@/utils/api-config'; -import React, { useState } from 'react'; - -export default function Page() { - const [selectedFile, setSelectedFile] = useState(null); - const [response, setResponse] = useState<{ - insertados: number; - omitidos: number; - } | null>(null); - - const handleFileChange = (e: React.ChangeEvent) => { - if (e.target.files && e.target.files.length > 0) { - setSelectedFile(e.target.files[0]); - } - }; - - const handleSubmit = async () => { - if (!selectedFile) return; - const formData = new FormData(); - formData.append('file', selectedFile); - - try { - const res = await axiosInstance.post('/trabajadores/cargar', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - setResponse(res.data); - } catch (error) { - console.error('Error al subir el archivo:', error); - } - }; - - return ( -
-

Subir Excel de Profesores

- - - - {response && ( -
-

Resultado de la carga:

-

Insertados: {response.insertados}

-

Omitidos: {response.omitidos}

-
- )} -
- ); -} diff --git a/src/app/(admins)/staff/layout.tsx b/src/app/(admins)/staff/layout.tsx deleted file mode 100644 index 2582403..0000000 --- a/src/app/(admins)/staff/layout.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import Footer from '@/components/layout/footer'; -import Header from '@/components/layout/header'; -import Link from 'next/link'; -import React from 'react'; - -export default function Layout({ children }: { children: React.ReactNode }) { - return ( -
-
-
- - {children} -
-
-
- ); -} diff --git a/src/app/(admins)/staff/lista-manual/page.tsx b/src/app/(admins)/staff/lista-manual/page.tsx deleted file mode 100644 index 994d00c..0000000 --- a/src/app/(admins)/staff/lista-manual/page.tsx +++ /dev/null @@ -1,181 +0,0 @@ -'use client'; -import Button from '@/components/button'; -import Input from '@/components/input'; -import Select from '@/components/select'; -import Table, { Header } from '@/components/table'; -import { - GetCuestionario, - GetCuestionarioWithEvento, -} from '@/types/cuestionario'; -import { GetEvento } from '@/types/evento'; -import { ParticipacionEvento } from '@/types/participante-evento'; -import axiosInstance from '@/utils/api-config'; -import React, { useEffect } from 'react'; -import toast from 'react-hot-toast'; - -export default function Page() { - const headers: Header[] = [ - { - key: 'id_participante', - label: '#', - render: (_, row) => row.participante.id_participante, - }, - { - key: 'participante', - label: 'Correo', - render: (_, row) => row.participante.correo, - }, - { - key: 'fecha_registro', - label: 'Fecha registro', - render: (val) => new Date(val as string).toLocaleString(), - }, - { - key: 'estatus', - label: 'Asistencia', - render: (_, row) => { - const isLoading = loadingAsistencia[row.id_participante]; - - return row.fecha_asistencia ? ( - - - Asistió - - ) : ( - - ); - }, - }, - ]; - - const [eventos, setEventos] = React.useState([]); - const [participantes, setParticipantes] = React.useState< - ParticipacionEvento[] - >([]); - const [loadingAsistencia, setLoadingAsistencia] = React.useState< - Record - >({}); - const [busquedaCorreo, setBusquedaCorreo] = React.useState(''); - - useEffect(() => { - const getEventos = async () => { - try { - const { data } = await axiosInstance.get( - `/cuestionario` - ); - if (!data) throw new Error('No se encontraron eventos'); - - const cuestionariosConEvento = await Promise.all( - data.map(async (cuestionario) => { - const { data: evento } = await axiosInstance.get( - `/evento/${cuestionario.id_evento}` - ); - return { ...cuestionario, evento }; - }) - ); - - setEventos(cuestionariosConEvento); - } catch (error) { - console.error('Error en getEventos:', error); - } - }; - - getEventos(); - }, []); - - const handleOnSelect = async (idSeleccionado: number) => { - try { - const { data } = await axiosInstance.get( - `/participante-evento/evento/${idSeleccionado}` - ); - setParticipantes(data); - } catch (error) { - console.error('Error al obtener participante-evento:', error); - } - }; - - const confirmarAsistencia = async ( - id_participante: number, - id_cuestionario: number - ) => { - setLoadingAsistencia((prev) => ({ ...prev, [id_participante]: true })); - try { - await axiosInstance.post( - `/participante-evento/asistencia/${id_participante}/${id_cuestionario}` - ); - - toast.success('Asistencia confirmada'); - // Actualiza lista - await handleOnSelect(id_cuestionario); - } catch (error) { - toast.error('Error al confirmar asistencia'); - console.error(error); - } finally { - setLoadingAsistencia((prev) => ({ ...prev, [id_participante]: false })); - } - }; - - const participantesFiltrados = participantes.filter((p) => - p.participante.correo.toLowerCase().includes(busquedaCorreo.toLowerCase()) - ); - - return ( -
- setBusquedaCorreo(e.target.value)} - /> - -
-
row.id_participante} - /> - - - )} - {participantes.length === 0 && ( -
- No hay participantes registrados para este evento. -
- )} - - ); -} diff --git a/src/app/(admins)/staff/page.tsx b/src/app/(admins)/staff/page.tsx deleted file mode 100644 index 6aa1277..0000000 --- a/src/app/(admins)/staff/page.tsx +++ /dev/null @@ -1,144 +0,0 @@ -'use client'; - -import React, { useState } from 'react'; -import dynamic from 'next/dynamic'; -import { Scanner } from '@yudiel/react-qr-scanner'; -import Button from '@/components/button'; -import axiosInstance from '@/utils/api-config'; -import toast from 'react-hot-toast'; - -const Modal = dynamic(() => import('@/components/modal'), { ssr: false }); - -export default function Page() { - const [scannedData, setScannedData] = useState<{ - id_participante: number; - id_cuestionario: number; - } | null>(null); - - const [showModal, setShowModal] = useState(false); - const [enableScan, setEnableScan] = useState(true); - const [statusMessage, setStatusMessage] = useState(''); - - const [participante, setParticipante] = useState(''); - - const handleScan = async (rawValue: string) => { - console.log('QR Escaneado:', rawValue); - if (!enableScan) return; - - try { - const data = JSON.parse(rawValue); - if (data.id_participante && data.id_cuestionario) { - try { - const response = await axiosInstance.get( - `/participante-evento/${data.id_participante}/${data.id_cuestionario}` - ); - - setParticipante(response.data.participante.correo); - setScannedData({ - id_participante: data.id_participante, - id_cuestionario: data.id_cuestionario, - }); - setShowModal(true); - setEnableScan(false); - } catch (err) { - console.warn('Participante no registrado en el cuestionario:', err); - setStatusMessage( - '❌ El participante no está registrado en este evento.' - ); - } - } else { - setStatusMessage('⚠️ El QR no contiene los campos requeridos'); - } - } catch (err) { - console.error('QR malformado:', err); - setStatusMessage('❌ Error al leer el QR: formato inválido'); - } - }; - - const handleConfirm = async () => { - if (!scannedData) return; - - try { - const response = await axiosInstance.post( - `/participante-evento/asistencia/${scannedData.id_participante}/${scannedData.id_cuestionario}` - ); - - console.log('Asistencia registrada:', response.data); - toast.success('✅ Asistencia registrada correctamente'); - } catch (err) { - console.error('Error en la petición:', err); - setStatusMessage('❌ Error de red al registrar asistencia'); - } - - setShowModal(false); - setEnableScan(true); - setScannedData(null); - }; - - return ( -
-

Lectura de QRs

- - {enableScan ? ( - <> -
- { - if (codes.length > 0) { - handleScan(codes[0].rawValue); - } - }} - onError={(err) => console.error('QR Error:', err)} - constraints={{ facingMode: 'environment' }} - /> -
- - - ) : ( - <> -

La cámara está detenida.

- - - )} - - {statusMessage &&

{statusMessage}

} - - { - setShowModal(false); - setEnableScan(true); - setScannedData(null); - }} - closeButton - className={{ - content: 'p-3', - body: 'text-center', - }} - > -
Datos escaneados
- {scannedData ? ( - <> -

- Participante: {participante} -

- - - ) : ( -

QR inválido

- )} -
-
- ); -} diff --git a/src/app/(admins)/user/evento/[id_evento]/formulario/[id_cuestionario]/page.tsx b/src/app/(admins)/user/evento/[id_evento]/formulario/[id_cuestionario]/page.tsx index bb8a754..78f4611 100644 --- a/src/app/(admins)/user/evento/[id_evento]/formulario/[id_cuestionario]/page.tsx +++ b/src/app/(admins)/user/evento/[id_evento]/formulario/[id_cuestionario]/page.tsx @@ -143,10 +143,10 @@ export default function Page() { ); const breadcrumbItems = [ - { label: 'Inicio', href: '/administrador/eventos' }, + { label: 'Inicio', href: '/user/eventos' }, { label: evento?.nombre_evento || 'Evento', - href: `/administrador/evento/${params.id_evento}`, + href: `/user/evento/${params.id_evento}`, }, { label: cuestionario?.nombre_form || 'Formulario' }, ]; @@ -158,6 +158,7 @@ export default function Page() { {cuestionario && ( diff --git a/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx b/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx index 36aeb2b..ecc009e 100644 --- a/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx +++ b/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx @@ -4,6 +4,7 @@ import { FormularioCreacion } from '@/types/create-formulario'; import Image from 'next/image'; import React, { useState } from 'react'; import CreateFormulario from '@/containers/create-formulario'; +import FormularioCardPreview from '@/components/formulario/formulario-card-preview'; import Button from '@/components/button'; import Breadcrumb from '@/components/breadcrumb'; import { useEvento } from '@/context/evento'; @@ -27,17 +28,46 @@ export default function Page() { const seleccionada = plantillasDisponibles.find( (p) => p.id === plantillaId ); - if (seleccionada) { - setDatosFormulario(seleccionada.datos); + if (seleccionada && evento) { + // Crear una copia de los datos de la plantilla + const datosPlantilla = { ...seleccionada.datos }; + + // Ajustar las fechas para que estén dentro del rango del evento + const fechaInicioEvento = new Date(evento.fecha_inicio); + const fechaFinEvento = new Date(evento.fecha_fin); + + // Formatear las fechas para inputs datetime-local (YYYY-MM-DDTHH:MM) + const formatoFechaInput = (fecha: Date) => { + return fecha.toISOString().slice(0, 16); + }; + + // Establecer fecha de inicio del formulario igual a la del evento + datosPlantilla.fecha_inicio = formatoFechaInput(fechaInicioEvento); + + // Establecer fecha de fin del formulario igual a la del evento + datosPlantilla.fecha_fin = formatoFechaInput(fechaFinEvento); + + setDatosFormulario(datosPlantilla); console.log('Plantilla seleccionada:', seleccionada); + console.log('Fechas ajustadas al evento:', { + inicio: datosPlantilla.fecha_inicio, + fin: datosPlantilla.fecha_fin, + }); + } else if (seleccionada) { + // Si no hay evento disponible, usar las fechas originales de la plantilla + setDatosFormulario(seleccionada.datos); + console.log( + 'Plantilla seleccionada (sin ajuste de fechas):', + seleccionada + ); } }; const breadcrumbItems = [ - { label: 'Inicio', href: '/administrador/eventos' }, + { label: 'Inicio', href: '/user/eventos' }, { label: evento?.nombre_evento || 'Evento', - href: `/administrador/evento/${params.id_evento}`, + href: `/user/evento/${params.id_evento}`, }, { label: 'Crear formulario' }, ]; @@ -114,7 +144,7 @@ export default function Page() { console.log('Formulario creado:', res.data); refetch(); toast.success('Formulario creado exitosamente'); - router.push(`/administrador/evento/${params.id_evento}`); + router.push(`/user/evento/${params.id_evento}`); } catch (error) { const msg = getAxiosError(error); toast.error(msg.message || 'Error al crear el formulario'); @@ -122,18 +152,25 @@ export default function Page() { }; return ( -
+
-
-

Creación de formulario

-

- Esta sección te permite crear y configurar sub-eventos en forma de - formularios asociados al evento. Selecciona una plantilla compatible, - ajusta los campos necesarios y publícalo para habilitar el registro, - encuestas o confirmaciones dentro de este evento. -

+ {/* Header */} +
+
+
+

Creación de formulario

+

+ Esta sección te permite crear y configurar sub-eventos en forma de + formularios asociados al evento. Selecciona una plantilla + compatible, ajusta los campos necesarios y publícalo para + habilitar el registro, encuestas o confirmaciones dentro de este + evento. +

+
+
+ {!datosFormulario && (

@@ -174,16 +211,80 @@ export default function Page() {

)} + {datosFormulario && ( - setDatosFormulario({ ...nuevo })} - /> + <> +
+ {/* Columna Izquierda - Formulario */} +
+
+ {(() => { + const formularioEditor = CreateFormulario({ + formulario: datosFormulario, + onChange: (nuevo) => setDatosFormulario({ ...nuevo }), + evento: evento ? evento : undefined, + }); + return formularioEditor.informacionBasica(); + })()} + + {/* Botón de crear */} +
+ +
+
+
+ + {/* Columna Derecha - Preview */} +
+
+
+

+ Preview del Formulario +

+
+

Preview del Formulario

+
+ + + {/* Alert de imagen del evento */} + {evento?.banner && ( +
+ + Nota: Al no proporcionar una imagen para + este formulario, se usará automáticamente la imagen del + evento. +
+ )} + +
+ Vista previa: Así se verá tu formulario en + las listas de formularios. +
+
+
+
+
+ + {/* Secciones y preguntas - Ancho completo */} +
+
+ {(() => { + const formularioEditor = CreateFormulario({ + formulario: datosFormulario, + onChange: (nuevo) => setDatosFormulario({ ...nuevo }), + evento: evento ? evento : undefined, + }); + return formularioEditor.seccionesYPreguntas(); + })()} +
+
+ )} -
); } diff --git a/src/app/(admins)/user/evento/[id_evento]/page.tsx b/src/app/(admins)/user/evento/[id_evento]/page.tsx index 8181bbc..744cc67 100644 --- a/src/app/(admins)/user/evento/[id_evento]/page.tsx +++ b/src/app/(admins)/user/evento/[id_evento]/page.tsx @@ -8,9 +8,9 @@ import { useEvento } from '@/context/evento'; import axiosInstance from '@/utils/api-config'; import toast from 'react-hot-toast'; import { downloadFile } from '@/utils/downloas-utils'; -import NewCuestionarioCard from '@/components/new-cuestionario-card'; import Breadcrumb from '@/components/breadcrumb'; import Button from '@/components/button'; +import FormularioCardAdmin from '@/components/formulario/formulario-card-admin'; type Params = { id_evento: string; @@ -44,7 +44,11 @@ export default function Page() { if (error) return

Error: {error}

; if (!evento) return

No se encontró el evento

; - const handleDownload = async (id_cuestionario: number, nombre: string) => { + const handleDownload = async ( + id_cuestionario: number, + nombre_formulario: string, + nombre_evento: string + ) => { setLoadingStates((prev) => ({ ...prev, [id_cuestionario]: true })); try { @@ -55,7 +59,7 @@ export default function Page() { } ); - downloadFile(res.data, `${nombre}`, 'csv'); + downloadFile(res.data, `${nombre_evento} - ${nombre_formulario}`, 'csv'); } catch (error) { console.error('Error al descargar el archivo:', error); toast.error('Error al descargar el archivo'); @@ -68,10 +72,10 @@ export default function Page() {
@@ -95,9 +99,7 @@ export default function Page() { icon="plus" variant="primary" onClick={() => - router.push( - `/administrador/evento/${params.id_evento}/formularios/crear` - ) + router.push(`/user/evento/${params.id_evento}/formularios/crear`) } > Crear nuevo formulario @@ -114,11 +116,11 @@ export default function Page() { className={`col-md-6 col-lg-4 my-4 fade-in-up-bounce ${fadeClass}`} key={key} > -
); diff --git a/src/app/(admins)/user/eventos/crear/page.tsx b/src/app/(admins)/user/eventos/crear/page.tsx index 45ba8fd..996811c 100644 --- a/src/app/(admins)/user/eventos/crear/page.tsx +++ b/src/app/(admins)/user/eventos/crear/page.tsx @@ -58,7 +58,7 @@ export default function Page() { toast.success('Evento y formulario creados exitosamente'); router.push( - `/administrador/evento/${createEvento.data.id_evento}/formularios/crear` + `/user/evento/${createEvento.data.id_evento}/formularios/crear` ); } catch (error) { const msg = getAxiosError(error); diff --git a/src/app/(admins)/user/eventos/page.tsx b/src/app/(admins)/user/eventos/page.tsx index 3715f07..06c370d 100644 --- a/src/app/(admins)/user/eventos/page.tsx +++ b/src/app/(admins)/user/eventos/page.tsx @@ -4,9 +4,15 @@ import EventoCardAdmin from '@/components/evento/evento-card-admin'; import FormularioCardAdmin from '@/components/formulario/formulario-card-admin'; import { useGetApi } from '@/hooks/use-get-api'; import { GetEventoWithCuestionariosWithCupos } from '@/types/evento'; -import React from 'react'; +import axiosInstance from '@/utils/api-config'; +import { downloadFile } from '@/utils/downloas-utils'; +import React, { useState } from 'react'; +import toast from 'react-hot-toast'; export default function Page() { + const [loadingStates, setLoadingStates] = useState>( + {} + ); const { loading, data: activos, @@ -21,6 +27,30 @@ export default function Page() { if (loading) return
Cargando formularios...
; if (error) return
{error.message}
; + const handleDownload = async ( + id_cuestionario: number, + nombre_formulario: string, + nombre_evento: string + ) => { + setLoadingStates((prev) => ({ ...prev, [id_cuestionario]: true })); + + try { + const res = await axiosInstance.get( + `/cuestionario-respondido/reporte-respuestas/${id_cuestionario}`, + { + responseType: 'blob', + } + ); + + downloadFile(res.data, `${nombre_evento} - ${nombre_formulario}`, 'csv'); + } catch (error) { + console.error('Error al descargar el archivo:', error); + toast.error('Error al descargar el archivo'); + } finally { + setLoadingStates((prev) => ({ ...prev, [id_cuestionario]: false })); + } + }; + return (
{loading &&
Cargando...
} @@ -55,6 +85,10 @@ export default function Page() {
); diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index bbadee4..8d164cf 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -2,6 +2,7 @@ import ClientCarousel from '@/client-components/client-carousel'; import FormularioCardUser from '@/components/formulario/formulario-card-user'; +import EmptyEventsState from '@/components/empty-events-state'; import { useGetApi } from '@/hooks/use-get-api'; import { GetEventoWithCuestionariosWithCupos } from '@/types/evento'; import React from 'react'; @@ -13,25 +14,53 @@ export default function Page() { return (
-
- ({ - src: `${process.env.NEXT_PUBLIC_API_URL}/banners/${evento.banner}`, - alt: `Banner de ${evento.nombre_evento}`, - })) - : [ - { - src: '/banner.jpeg', - alt: 'Banner de eventos activos', - }, - ] - } - /> -
- {loading &&
Cargando...
} - {data && ( + {data && data.length > 0 && ( +
+ 0 + ? data.map((evento) => ({ + src: `${process.env.NEXT_PUBLIC_API_URL}/banners/${evento.banner}`, + alt: `Banner de ${evento.nombre_evento}`, + })) + : [ + { + src: '/default-banner.jpeg', + alt: 'Banner de eventos activos', + }, + ] + } + /> +
+ )} + + {loading && ( +
+
+
+ Cargando... +
+
+
Cargando eventos...
+ + Esto solo tomará unos segundos + +
+
+
+ )} + + {!loading && data && data.length === 0 && ( +
+ +
+ )} + + {!loading && data && data.length > 0 && (
{data.flatMap((evento) => evento.cuestionarios.map((cuestionario, index) => { diff --git a/src/components/empty-events-state.tsx b/src/components/empty-events-state.tsx new file mode 100644 index 0000000..b285531 --- /dev/null +++ b/src/components/empty-events-state.tsx @@ -0,0 +1,62 @@ +'use client'; + +import React from 'react'; +import Button from './button'; + +export default function EmptyEventsState() { + return ( +
+
+
+
+ {/* Ilustración principal con Bootstrap */} +
+
+ +
+
+ + {/* Título y mensaje principal */} +
+

+ No hay eventos disponibles +

+

+ En este momento no tenemos eventos activos para mostrar. +
+ + ¡No te preocupes! + {' '} + Pronto habrá nuevas oportunidades de participación. +

+
+ + {/* Botón de acción usando clases Bootstrap */} +
+ +
+ + {/* Mensaje adicional */} +
+ + + Esta página se actualiza automáticamente para mostrar los + eventos más recientes + +
+
+
+
+
+ ); +} diff --git a/src/components/evento/evento-card-admin.tsx b/src/components/evento/evento-card-admin.tsx index 6220e96..2367ff3 100644 --- a/src/components/evento/evento-card-admin.tsx +++ b/src/components/evento/evento-card-admin.tsx @@ -31,7 +31,7 @@ export default function EventoCardAdmin({ style={{ borderRadius: '12px', overflow: 'hidden' }} >
- +
{cuestionario.nombre_form} @@ -131,7 +131,7 @@ export default function EventoCardAdmin({

Sin formularios disponibles

@@ -142,7 +142,7 @@ export default function EventoCardAdmin({ {/* Botón principal */} Ver/Editar evento diff --git a/src/components/evento/evento-card-preview.tsx b/src/components/evento/evento-card-preview.tsx index 5953ef6..bbb6651 100644 --- a/src/components/evento/evento-card-preview.tsx +++ b/src/components/evento/evento-card-preview.tsx @@ -10,11 +10,15 @@ import Button from '../button'; interface EventoPreviewProps { evento: CreateEventoType; eventoBanner?: File | null; + existingBanner?: string | null; // Banner existente desde la API + cuestionariosCount?: number; // Número de formularios existentes } export default function EventoCardPreview({ evento, eventoBanner, + existingBanner, + cuestionariosCount = 0, }: EventoPreviewProps) { const [verMas, setVerMas] = useState(false); const [bannerUrl, setBannerUrl] = useState('/default-banner.png'); @@ -27,18 +31,25 @@ export default function EventoCardPreview({ ? `${descripcion.slice(0, limite)}...` : descripcion; - // Actualizar URL del banner cuando cambie el archivo + // Actualizar URL del banner cuando cambie el archivo o cuando haya un banner existente useEffect(() => { if (eventoBanner) { + // Si hay un nuevo banner seleccionado, usarlo const url = URL.createObjectURL(eventoBanner); setBannerUrl(url); // Limpiar URL anterior cuando el componente se desmonte return () => URL.revokeObjectURL(url); + } else if (existingBanner) { + // Si no hay nuevo banner pero sí hay uno existente desde la API, usarlo + setBannerUrl( + `${process.env.NEXT_PUBLIC_API_URL}/banners/${existingBanner}` + ); } else { + // Si no hay ningún banner, usar el banner por defecto setBannerUrl('/default-banner.png'); } - }, [eventoBanner]); + }, [eventoBanner, existingBanner]); // Obtener información de fecha usando la utilidad const getDateInfo = () => { @@ -66,9 +77,16 @@ export default function EventoCardPreview({ style={{ objectFit: 'cover', height: '200px' }} /> - {/* Badge indicando que no hay formularios aún */} + {/* Badge de cantidad de formularios en la esquina superior derecha */}
- Sin formularios + {cuestionariosCount > 0 ? ( + + {cuestionariosCount} formulario + {cuestionariosCount !== 1 ? 's' : ''} + + ) : ( + Sin formularios + )}
diff --git a/src/components/formulario/formulario-card-admin.tsx b/src/components/formulario/formulario-card-admin.tsx index 01c52ae..9321db0 100644 --- a/src/components/formulario/formulario-card-admin.tsx +++ b/src/components/formulario/formulario-card-admin.tsx @@ -9,9 +9,17 @@ import Button from '../button'; export default function FormularioCardAdmin({ cuestionario, evento, + handleDownload, + disabled, }: { cuestionario: CuestionarioWithCupo; evento: GetEvento; + handleDownload: ( + id_cuestionario: number, + nombre_formulario: string, + nombre_evento: string + ) => void; + disabled: boolean; }) { const [verMas, setVerMas] = useState(false); const descripcion = cuestionario.descripcion ?? ''; @@ -35,7 +43,7 @@ export default function FormularioCardAdmin({ >
+ {/* Mensaje cuando se usa la imagen del evento */} + {!cuestionario.banner && evento.banner && ( +
+
+ + Usando imagen del evento +
+
+ )} + {/* Badge de tipo de cuestionario en la esquina superior izquierda */}
@@ -130,14 +150,27 @@ export default function FormularioCardAdmin({ {/* Botones de acción */}
Ver/Editar formulario {/* Botón secundario para ver respuestas */} - diff --git a/src/components/formulario/formulario-card-preview.tsx b/src/components/formulario/formulario-card-preview.tsx new file mode 100644 index 0000000..aa3fc6c --- /dev/null +++ b/src/components/formulario/formulario-card-preview.tsx @@ -0,0 +1,280 @@ +import { GetCuestionario } from '@/types/cuestionario'; +import { FormularioCreacion } from '@/types/create-formulario'; +import { GetEvento, GetEventoWithCuestionariosWithCupos } from '@/types/evento'; +import Image from 'next/image'; +import React, { useState, useEffect } from 'react'; +import MarkdownRenderer from '../markdown-render'; +import { formatearFechaCard } from '@/utils/date-utils'; + +interface FormularioCardPreviewProps { + // Para formularios existentes + cuestionario?: GetCuestionario; + // Para formularios en creación + formulario?: FormularioCreacion; + // Evento asociado + evento?: GetEvento | GetEventoWithCuestionariosWithCupos | null; + // Banner personalizado + nuevoBanner?: File | null; +} + +export default function FormularioCardPreview({ + cuestionario, + formulario, + evento, + nuevoBanner, +}: FormularioCardPreviewProps) { + const [verMas, setVerMas] = useState(false); + const [bannerUrl, setBannerUrl] = useState('/default-banner.png'); + + // Determinar qué datos usar (formulario existente o en creación) + const isCreating = !!formulario && !cuestionario; + const data = cuestionario || formulario; + + // Manejar la imagen del banner + useEffect(() => { + if (nuevoBanner) { + // Si hay un nuevo banner seleccionado, usarlo + const url = URL.createObjectURL(nuevoBanner); + setBannerUrl(url); + return () => URL.revokeObjectURL(url); + } else if (!isCreating && cuestionario?.banner) { + // Si es un formulario existente y tiene banner propio + setBannerUrl( + `${process.env.NEXT_PUBLIC_API_URL}/banners/${cuestionario.banner}` + ); + } else if (evento?.banner) { + // Si hay banner del evento, usarlo + setBannerUrl( + `${process.env.NEXT_PUBLIC_API_URL}/banners/${evento.banner}` + ); + } else { + // Banner por defecto + setBannerUrl('/default-banner.png'); + } + }, [nuevoBanner, evento?.banner, cuestionario?.banner, isCreating]); + + if (!data) { + return null; // No hay datos para mostrar + } + + const descripcion = data.descripcion ?? ''; + const limite = 100; + + const descripcionRecortada = + descripcion.length > limite && !verMas + ? `${descripcion.slice(0, limite)}...` + : descripcion; + + // Obtener información de fecha usando la utilidad + const getDateInfo = () => { + if (!data.fecha_inicio || !data.fecha_fin) { + return { mesTexto: 'MES', diaTexto: 'DD' }; + } + + let fechaInicio: Date, fechaFin: Date; + + if (isCreating) { + // Para formularios en creación, las fechas vienen como string + fechaInicio = new Date(data.fecha_inicio); + fechaFin = new Date(data.fecha_fin); + } else { + // Para formularios existentes, convertir a Date también + fechaInicio = new Date(data.fecha_inicio); + fechaFin = new Date(data.fecha_fin); + } + + return formatearFechaCard(fechaInicio, fechaFin); + }; + + const { mesTexto, diaTexto } = getDateInfo(); + + // Determinar si se está usando la imagen del evento + const isUsingEventImage = + !nuevoBanner && (isCreating || !cuestionario?.banner) && evento?.banner; + + // Obtener el tipo de cuestionario + const getTipoCuestionario = () => { + const tipoId = isCreating + ? (formulario as FormularioCreacion).id_tipo_cuestionario + : (cuestionario as GetCuestionario).id_tipo_cuestionario; + + if (tipoId === 1) return 'Registro'; + if (tipoId === 2) return 'Evaluación'; + return 'Formulario'; + }; + + // Obtener nombre del formulario + const getNombreFormulario = () => { + return data.nombre_form || 'Nombre del formulario'; + }; + + // Obtener información de cupos + const getCuposInfo = () => { + if (isCreating) { + const form = formulario as FormularioCreacion; + return form.cupo_maximo && form.cupo_maximo > 0 + ? `0 / ${form.cupo_maximo}` + : 'Sin límite'; + } else { + const cuest = cuestionario as GetCuestionario; + return cuest.cupo_maximo !== null && cuest.cupo_maximo !== undefined + ? `0 / ${cuest.cupo_maximo}` + : 'Sin límite'; + } + }; + + // Obtener información de secciones y preguntas (solo para formularios en creación) + const getSeccionesInfo = () => { + if (!isCreating || !formulario) return null; + + const totalSecciones = formulario.secciones.length; + const totalPreguntas = formulario.secciones.reduce( + (total, seccion) => total + seccion.preguntas.length, + 0 + ); + + return { totalSecciones, totalPreguntas }; + }; + + const seccionesInfo = getSeccionesInfo(); + + return ( +
+
+ + + {/* Mensaje cuando se usa la imagen del evento */} + {isUsingEventImage && ( +
+
+ + Usando imagen del evento +
+
+ )} + + {/* Badge de tipo de cuestionario en la esquina superior izquierda */} +
+ {getTipoCuestionario()} +
+
+ +
+ {/* Fecha del formulario */} +
+
+
+ {mesTexto} +
+
2 ? '1rem' : '1.25rem', + }} + > + {diaTexto} +
+
+
+
{getNombreFormulario()}
+ {evento && ( + + + {evento.nombre_evento} + + )} +
+
+ + {/* Descripción */} +
+ {descripcion ? ( + <> + + {descripcion.length > limite && ( + + )} + + ) : ( +

Descripción del formulario...

+ )} +
+ + {/* Información de cupos */} +
+
+
+ +
+ Cupos + {getCuposInfo()} +
+
+
+
+ + {/* Información de secciones y preguntas (solo para formularios en creación) */} + {seccionesInfo && ( +
+
+
+
+ + + {seccionesInfo.totalSecciones} sección + {seccionesInfo.totalSecciones !== 1 ? 'es' : ''} + +
+
+ + + {seccionesInfo.totalPreguntas} pregunta + {seccionesInfo.totalPreguntas !== 1 ? 's' : ''} + +
+
+
+
+ )} + + {/* Botón de acción */} +
+ + +
+
+
+ ); +} diff --git a/src/components/formulario/formulario-card-user.tsx b/src/components/formulario/formulario-card-user.tsx index 4a86c91..b9c8008 100644 --- a/src/components/formulario/formulario-card-user.tsx +++ b/src/components/formulario/formulario-card-user.tsx @@ -27,6 +27,12 @@ export default function FormularioCardUser({ ? `${descripcion.slice(0, limite)}...` : descripcion; + const imgSrc = formulario.banner + ? `${process.env.NEXT_PUBLIC_API_URL}/banners/${formulario.banner}` + : evento.banner + ? `${process.env.NEXT_PUBLIC_API_URL}/banners/${evento.banner}` + : `/default-banner.png`; + return (
diff --git a/src/containers/create-formulario.tsx b/src/containers/create-formulario.tsx index 714a5da..c0e3408 100644 --- a/src/containers/create-formulario.tsx +++ b/src/containers/create-formulario.tsx @@ -146,28 +146,31 @@ export default function FormularioEditor({ onChange(actualizado); }; - return ( -
-

Editar Formulario

+ // Renderizar solo información básica del formulario + const renderInformacionBasica = () => ( +
+

Información del Formulario

-
- actualizarCampo('nombre_form', e.target.value)} - /> -
+
+
+ actualizarCampo('nombre_form', e.target.value)} + /> +
-
- - actualizarCampo('cupo_maximo', Number(e.target.value)) - } - /> +
+ + actualizarCampo('cupo_maximo', Number(e.target.value) || 0) + } + /> +
+ {evento && ( -
- Este evento se realizara{' '} - {formatearRangoFechas(evento.fecha_inicio, evento.fecha_fin)} +
+ +
+ Evento: {evento.nombre_evento} +
+ + Se realizará{' '} + {formatearRangoFechas(evento.fecha_inicio, evento.fecha_fin)} + +
)} @@ -210,14 +221,27 @@ export default function FormularioEditor({ options={tiposCuestionario} placeholder="Selecciona un tipo" /> +
+ ); -
+ // Renderizar secciones y preguntas + const renderSeccionesYPreguntas = () => ( +
+
+

Secciones y Preguntas

+ +
- {/* Secciones */} {formulario.secciones.map((seccion, seccionIdx) => (
-

Sección {seccionIdx + 1}

+
Sección {seccionIdx + 1}
+
))} - -
- -
); + + return { + informacionBasica: renderInformacionBasica, + seccionesYPreguntas: renderSeccionesYPreguntas, + }; } diff --git a/src/containers/edit-evento.tsx b/src/containers/edit-evento.tsx index 42d015a..151c939 100644 --- a/src/containers/edit-evento.tsx +++ b/src/containers/edit-evento.tsx @@ -3,9 +3,11 @@ 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'; @@ -14,6 +16,8 @@ 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; @@ -68,136 +72,158 @@ export default function EditEvento({ } }; + // 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 ( -
-
-

Editar Información del Evento

-

- Modifica los datos del evento que desees actualizar y confrimalos. -

+
+ {/* Header */} +
+
+
+

Editar Información del Evento

+

+ Modifica los datos del evento que desees actualizar y confirmalos. +

+
+
-
-
- {/* Columna Izquierda - Banner y Nombre */} -
-
- { - setNuevoBanner(file); - }} - /> -

- - - Esta es la imagen que se verá en el carrusel del evento. - -
- - - Dimensiones recomendadas: 1200x600 píxeles. - -
- - - Formatos soportados: PNG, JPG, JPEG. - -

-
-
- - {/* Columna Derecha - Descripción, Tipo y Fechas */} -
-
-

Detalles del Evento

- - handleChange('nombre_evento', e.target.value)} - /> - +
+ {/* Columna Izquierda - Formulario */} +
+
+
+ {/* Banner */}
- -
- { - const texto = value || ''; - if (texto.length <= 500) { - handleChange('descripcion_evento', texto); + { + setNuevoBanner(file); + }} + /> +

+ + + Esta es la imagen que se verá en el carrusel del evento. + +
+ + + Dimensiones recomendadas: 1200x600 píxeles. + +
+ + + Formatos soportados: PNG, JPG, JPEG. + +

+
+ + {/* Detalles del evento */} +
+

Detalles del Evento

+ + + handleChange('nombre_evento', e.target.value) + } + /> + +
+ +
+ { + const texto = value || ''; + if (texto.length <= MAX_LENGTH) { + handleChange('descripcion_evento', texto); + } + }} + height={200} + commands={[commands.bold, commands.italic, commands.hr]} + /> +
+
+ + + handleChange('fecha_inicio', new Date(e.target.value)) } - }} - height={200} - commands={[commands.bold, commands.italic, commands.hr]} - /> + /> +
+ +
+ + handleChange('fecha_fin', new Date(e.target.value)) + } + /> +
- - handleChange('fecha_inicio', new Date(e.target.value)) - } - /> -
- -
- - handleChange('fecha_fin', new Date(e.target.value)) - } - /> -
+ {/* Botón de guardar */} +
+
- {/* Botón de guardar en la parte inferior */} -
-
-
- + {/* Columna Derecha - Preview */} +
+
+
+

Preview del Evento

+
+

Preview del Evento

+
+ +
+ Vista previa: Así se verá tu evento en las + listas de eventos. +
diff --git a/src/containers/edit-formulario.tsx b/src/containers/edit-formulario.tsx index 33c4b15..3d7da8f 100644 --- a/src/containers/edit-formulario.tsx +++ b/src/containers/edit-formulario.tsx @@ -1,7 +1,9 @@ import ImageUploader from '@/components/banner-uploader'; import Button from '@/components/button'; import SimpleInput from '@/components/input'; +import FormularioCardPreview from '@/components/formulario/formulario-card-preview'; import { GetCuestionario } from '@/types/cuestionario'; +import { GetEvento } from '@/types/evento'; import axiosInstance from '@/utils/api-config'; import { formatDateLocal } from '@/utils/date-utils'; import { getAxiosError } from '@/utils/errors-utils'; @@ -11,12 +13,14 @@ import toast from 'react-hot-toast'; interface EditFormularioProps { cuestionario: GetCuestionario; + evento: GetEvento | null; handleChange: (field: keyof GetCuestionario, value: string | Date) => void; handleOnChange: (eventoActualizado: GetCuestionario) => void; } export default function EditFormulario({ cuestionario, + evento, handleChange, }: EditFormularioProps) { const [loading, setLoading] = useState(false); @@ -65,130 +69,176 @@ export default function EditFormulario({ }; return ( -
-
-

Editar Información del Formulario

-

- Modifica los datos del formulario que desees actualizar y confrimalos. -

+
+ {/* Header */} +
+
+
+

Editar Información del Formulario

+

+ Modifica los datos del formulario que desees actualizar y + confirmalos. +

+
+
-
-
- {/* Columna Izquierda - Banner */} -
-
- { - setNuevoBanner(file); - }} - /> -

- - - Esta es la imagen que se usara para la carta de presentación - del formulario. - -
- - - Dimensiones recomendadas: 1200x600 píxeles. - -
- - - Formatos soportados: PNG, JPG, JPEG. - -

-
-
- - {/* Columna Derecha - Detalles del formulario */} -
-
-

Detalles del Formulario

- - handleChange('nombre_form', e.target.value)} - /> - - - handleChange( - 'cupo_maximo', - e.target.value ? e.target.value : '' - ) - } - /> - -
- -
- { - const texto = value || ''; - if (texto.length <= 500) { - handleChange( - 'descripcion' as keyof GetCuestionario, - texto - ); +
+ {/* Columna Izquierda - Formulario */} +
+
+
+
+ {/* Banner */} +
+
+ { + setNuevoBanner(file); + }} + /> +

+ + + Esta es la imagen que se usara para la carta de + presentación del formulario. + +
+ + + Dimensiones recomendadas: 1200x600 píxeles. + +
+ + + Formatos soportados: PNG, JPG, JPEG. + +

+ + {/* Alert sobre imagen por defecto */} + {!cuestionario.banner && !nuevoBanner && evento?.banner && ( +
+ + Imagen por defecto: Si no seleccionas + una imagen específica para este formulario, se utilizará + automáticamente la imagen del evento. +
+ )} +
+
+ + {/* Detalles del formulario */} +
+

Detalles del Formulario

+ + + handleChange('nombre_form', e.target.value) + } /> + + + handleChange( + 'cupo_maximo', + e.target.value ? e.target.value : '' + ) + } + /> + +
+ +
+ { + const texto = value || ''; + if (texto.length <= 500) { + handleChange( + 'descripcion' as keyof GetCuestionario, + texto + ); + } + }} + height={200} + commands={[commands.bold, commands.italic, commands.hr]} + /> +
+
+ +
+
+ + handleChange('fecha_inicio', new Date(e.target.value)) + } + /> +
+ +
+ + handleChange('fecha_fin', new Date(e.target.value)) + } + /> +
+
-
-
- - handleChange('fecha_inicio', new Date(e.target.value)) - } - /> -
- -
- - handleChange('fecha_fin', new Date(e.target.value)) - } - /> -
+ {/* Botón de guardar */} +
+
- {/* Botón de guardar en la parte inferior */} -
-
-
- + {/* Columna Derecha - Preview */} +
+
+
+

Preview del Formulario

+
+

Preview del Formulario

+
+ +
+ Vista previa: Así se verá tu formulario en las + listas de formularios. +