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
+14
View File
@@ -0,0 +1,14 @@
export const tipoEventos = [
{ label: 'Conferencia', value: 'conferencia' },
{ label: 'Taller', value: 'taller' },
{ label: 'Seminario', value: 'seminario' },
{ label: 'Curso', value: 'curso' },
{ label: 'Webinar', value: 'webinar' },
{ label: 'Reunión', value: 'reunion' },
{ label: 'Feria', value: 'feria' },
{ label: 'Concierto', value: 'concierto' },
{ label: 'Exposición', value: 'exposicion' },
{ label: 'Deportivo', value: 'deportivo' },
{ label: 'Cultural', value: 'cultural' },
{ label: 'Otro', value: 'otro' },
];
+90 -1
View File
@@ -28,7 +28,6 @@
export function formatFecha(
fechaStr: string,
config: { horas?: boolean; minutos?: boolean; diaSemana?: boolean } = {
horas: false,
minutos: false,
diaSemana: false,
@@ -104,3 +103,93 @@ export function formatDateLocal(date: Date): string {
return `${yyyy}-${MM}-${dd}T${hh}:${mm}`;
}
/**
* Formatea un rango de fechas en español, optimizando la salida según si coinciden año, mes y día.
*
* @param {string} fechaInicio - Fecha de inicio en formato ISO 8601 (ej: '2025-08-13T18:44:00.000Z').
* @param {string} fechaFin - Fecha de fin en formato ISO 8601 (ej: '2025-08-15T18:44:00.000Z').
* @returns {string} Rango de fechas formateado en español.
* @throws {Error} Lanza un error si alguna de las fechas proporcionadas no es válida.
*
* @example
* formatearRangoFechas('2025-08-13T12:00:00.000Z', '2025-08-13T18:00:00.000Z');
* // Retorna: "el 13 de agosto del 2025 de 12:00 a 18:00"
*
* @example
* formatearRangoFechas('2025-08-13T18:44:00.000Z', '2025-08-15T18:44:00.000Z');
* // Retorna: "del 13 al 15 de agosto del 2025"
*
* @example
* formatearRangoFechas('2025-08-13T18:44:00.000Z', '2025-09-13T18:44:00.000Z');
* // Retorna: "del 13 de agosto al 13 de septiembre del 2025"
*
* @example
* formatearRangoFechas('2025-08-13T18:44:00.000Z', '2026-01-13T18:44:00.000Z');
* // Retorna: "del 13 de agosto del 2025 al 13 de enero del 2026"
*/
export function formatearRangoFechas(
fechaInicio: string,
fechaFin: string
): string {
const inicio = new Date(fechaInicio);
const fin = new Date(fechaFin);
if (isNaN(inicio.getTime())) {
throw new Error(`Fecha de inicio inválida: ${fechaInicio}`);
}
if (isNaN(fin.getTime())) {
throw new Error(`Fecha de fin inválida: ${fechaFin}`);
}
const meses = [
'enero',
'febrero',
'marzo',
'abril',
'mayo',
'junio',
'julio',
'agosto',
'septiembre',
'octubre',
'noviembre',
'diciembre',
];
const diaInicio = inicio.getUTCDate();
const mesInicio = meses[inicio.getUTCMonth()];
const añoInicio = inicio.getUTCFullYear();
const diaFin = fin.getUTCDate();
const mesFin = meses[fin.getUTCMonth()];
const añoFin = fin.getUTCFullYear();
// Si es el mismo día (año, mes y día iguales)
if (
añoInicio === añoFin &&
inicio.getUTCMonth() === fin.getUTCMonth() &&
diaInicio === diaFin
) {
const horaInicio = (inicio.getUTCHours() - 6 + 24) % 24;
const minutosInicio = inicio.getUTCMinutes().toString().padStart(2, '0');
const horaFin = (fin.getUTCHours() - 6 + 24) % 24;
const minutosFin = fin.getUTCMinutes().toString().padStart(2, '0');
return `el ${diaInicio} de ${mesInicio} del ${añoInicio} de ${horaInicio}:${minutosInicio} a ${horaFin}:${minutosFin}`;
}
// Si el año y mes son iguales pero días diferentes
if (añoInicio === añoFin && inicio.getUTCMonth() === fin.getUTCMonth()) {
return `del ${diaInicio} al ${diaFin} de ${mesInicio} del ${añoInicio}`;
}
// Si el año es igual pero el mes es diferente
if (añoInicio === añoFin) {
return `del ${diaInicio} de ${mesInicio} al ${diaFin} de ${mesFin} del ${añoInicio}`;
}
// Si tanto el año como el mes son diferentes
return `del ${diaInicio} de ${mesInicio} del ${añoInicio} al ${diaFin} de ${mesFin} del ${añoFin}`;
}
+25
View File
@@ -0,0 +1,25 @@
// slugify.ts
export function slugify(input: string, maxLen = 60) {
return input
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, maxLen);
}
// Combina slug + id (id al final facilita parsear con "último -")
export function toParam(name: string, id: string | number) {
return `${slugify(name)}-${id}`;
}
// Extrae { id, slug } de un param con formato "slug-123"
export function fromParam(param: string) {
const lastDash = param.lastIndexOf('-');
if (lastDash === -1) return { slug: param, id: null };
const slug = param.slice(0, lastDash);
const idStr = param.slice(lastDash + 1);
const id = /^\d+$/.test(idStr) ? Number(idStr) : null;
return { slug, id };
}