feat: Implement event and questionnaire management features

- Added event creation page with form handling and validation.
- Introduced layout for event management with context provider.
- Created event listing page with active and recent events display.
- Developed questionnaire creation page linked to events.
- Implemented breadcrumb navigation for better user experience.
- Added reusable components for event and questionnaire cards.
- Integrated API calls for fetching and managing event data.
- Enhanced slugify utility for better URL handling.
- Established context for managing event state across components.
This commit is contained in:
miguel
2025-08-12 20:00:21 -06:00
parent 5b0f0ad6bf
commit be8f207ecd
35 changed files with 2091 additions and 642 deletions
+123 -77
View File
@@ -65,87 +65,133 @@ export default function EditFormulario({
};
return (
<div className="row border p-3 rounded my-4">
<h2>Editar Información del formulario</h2>
<p>Modifica los datos del evento que desees actualizar.</p>
<ImageUploader
label="Banner del formulario"
defaultBanner={
cuestionario.banner
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${cuestionario.banner}`
: undefined
}
onChange={(file) => {
setNuevoBanner(file);
}}
/>
<div className="col-md-8">
<SimpleInput
label="Nombre del formulario"
required
value={cuestionario.nombre_form}
onChange={(e) => handleChange('nombre_form', e.target.value)}
/>
</div>
<div className="col-md-4">
<SimpleInput
label="Cupo máximo (Si no se requiere, dejar en blanco)"
type="number"
value={cuestionario.cupo_maximo ?? ''}
onChange={(e) =>
handleChange('cupo_maximo', e.target.value ? e.target.value : '')
}
/>
<div className="my-2">
<div className="p-4 border rounded bg-light">
<h2 className="mb-2">Editar Información del Formulario</h2>
<p className="mb-0 text-muted">
Modifica los datos del formulario que desees actualizar y confrimalos.
</p>
</div>
<div className="mb-4">
<label className="form-label">Descripción del evento</label>
<div data-color-mode="light">
<MDEditor
value={cuestionario.descripcion ?? ''}
onChange={(value) => {
const texto = value || '';
if (texto.length <= 500) {
handleChange(
'descripcion' as keyof GetCuestionario,
texto
);
}
}}
height={250}
commands={[commands.bold, commands.italic, commands.hr]}
/>
<div className="p-4">
<div className="row">
{/* Columna Izquierda - Banner */}
<div className="col-lg-6">
<div className="pe-lg-3">
<ImageUploader
label="Banner del formulario"
defaultBanner={
cuestionario.banner
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${cuestionario.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 usara para la carta de presentación
del formulario.
</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>
</div>
{/* Columna Derecha - Detalles del formulario */}
<div className="col-lg-6">
<div className="ps-lg-3">
<h4 className="mb-3">Detalles del Formulario</h4>
<SimpleInput
label="Nombre del formulario"
required
value={cuestionario.nombre_form}
onChange={(e) => handleChange('nombre_form', e.target.value)}
/>
<SimpleInput
label="Cupo máximo (Si no se requiere, dejar en blanco)"
type="number"
value={cuestionario.cupo_maximo ?? ''}
onChange={(e) =>
handleChange(
'cupo_maximo',
e.target.value ? e.target.value : ''
)
}
/>
<div className="mb-4">
<label className="form-label">Descripción del formulario</label>
<div data-color-mode="light">
<MDEditor
value={cuestionario.descripcion ?? ''}
onChange={(value) => {
const texto = value || '';
if (texto.length <= 500) {
handleChange(
'descripcion' as keyof GetCuestionario,
texto
);
}
}}
height={200}
commands={[commands.bold, commands.italic, commands.hr]}
/>
</div>
</div>
<div className="row">
<div className="col-md-6">
<SimpleInput
label="Fecha de inicio"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(cuestionario.fecha_inicio))}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
/>
</div>
<div className="col-md-6">
<SimpleInput
label="Fecha de fin"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(cuestionario.fecha_fin))}
onChange={(e) =>
handleChange('fecha_fin', new Date(e.target.value))
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="col-md-6">
<SimpleInput
label="Fecha de inicio"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(cuestionario.fecha_inicio))}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
/>
</div>
<div className="col-md-6">
<SimpleInput
label="Fecha de fin"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(cuestionario.fecha_fin))}
onChange={(e) => handleChange('fecha_fin', new Date(e.target.value))}
/>
</div>
<div>
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
{/* Botón de guardar en la parte inferior */}
<div className="row mt-4">
<div className="col-12">
<div className="d-flex justify-content-end">
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
</div>
</div>
</div>
</div>
</div>
);