@arquimedes.co/eureka-forms 3.0.52-test → 3.0.53-new-steps
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/@Types/AvailabilityFormStep.d.ts +48 -0
- package/dist/@Types/AvailabilityFormStep.js +24 -0
- package/dist/@Types/BankAccountFormStep.d.ts +67 -0
- package/dist/@Types/BankAccountFormStep.js +14 -0
- package/dist/@Types/BookingFormStep.d.ts +166 -0
- package/dist/@Types/BookingFormStep.js +44 -0
- package/dist/@Types/CalendarFormStep.d.ts +192 -0
- package/dist/@Types/CalendarFormStep.js +48 -0
- package/dist/@Types/CommunicationChannelFormStep.d.ts +51 -0
- package/dist/@Types/CommunicationChannelFormStep.js +15 -0
- package/dist/@Types/ConsentFormStep.d.ts +36 -0
- package/dist/@Types/ConsentFormStep.js +8 -0
- package/dist/@Types/EntityFormFormStep.d.ts +66 -0
- package/dist/@Types/EntityFormFormStep.js +20 -0
- package/dist/@Types/EventFormStep.d.ts +52 -0
- package/dist/@Types/EventFormStep.js +13 -0
- package/dist/@Types/ExternalReferenceFormStep.d.ts +74 -0
- package/dist/@Types/ExternalReferenceFormStep.js +20 -0
- package/dist/@Types/FormStep.d.ts +7 -2
- package/dist/@Types/FormStep.js +2 -1
- package/dist/@Types/LocationFormStep.d.ts +159 -0
- package/dist/@Types/LocationFormStep.js +46 -0
- package/dist/@Types/NumericFormSteps.d.ts +108 -0
- package/dist/@Types/NumericFormSteps.js +20 -0
- package/dist/@Types/PredefinedSelectorFormStep.d.ts +51 -0
- package/dist/@Types/PredefinedSelectorFormStep.js +39 -0
- package/dist/@Types/StepRef.d.ts +135 -0
- package/dist/@Types/StepRef.js +49 -0
- package/dist/FormSteps/BookingStep/BookingStep.d.ts +17 -0
- package/dist/FormSteps/BookingStep/BookingStep.js +14 -0
- package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.d.ts +7 -0
- package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +32 -0
- package/dist/FormSteps/CalendarStep/CalendarStep.d.ts +7 -0
- package/dist/FormSteps/CalendarStep/CalendarStep.js +14 -0
- package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.d.ts +7 -0
- package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.js +31 -0
- package/dist/FormSteps/LocationStep/LocationStep.d.ts +10 -0
- package/dist/FormSteps/LocationStep/LocationStep.js +14 -0
- package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.d.ts +3 -0
- package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.js +47 -0
- package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.module.css +18 -0
- package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.d.ts +3 -0
- package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.js +168 -0
- package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.module.css +94 -0
- package/dist/FormSteps/NumberStep/NumberStep.d.ts +10 -0
- package/dist/FormSteps/NumberStep/NumberStep.js +14 -0
- package/dist/FormSteps/Step.js +16 -0
- package/dist/FormSteps/StepFunctions.js +14 -0
- package/dist/FormSteps/StepFunctions.test.js +54 -0
- package/dist/Icons/AddIcon.d.ts +3 -0
- package/dist/Icons/AddIcon.js +7 -0
- package/dist/Icons/EditIcon.d.ts +3 -0
- package/dist/Icons/EditIcon.js +7 -0
- package/dist/Icons/RemoveIcon.d.ts +3 -0
- package/dist/Icons/RemoveIcon.js +7 -0
- package/dist/Icons/ViewDayIcon.d.ts +3 -0
- package/dist/Icons/ViewDayIcon.js +7 -0
- package/dist/Icons/ViewWeekIcon.d.ts +3 -0
- package/dist/Icons/ViewWeekIcon.js +7 -0
- package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.d.ts +19 -0
- package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.js +80 -0
- package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.module.css +82 -0
- package/dist/Shared/ErkAddressAutocomplete/googlePlaces.d.ts +29 -0
- package/dist/Shared/ErkAddressAutocomplete/googlePlaces.js +139 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.d.ts +31 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.js +223 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.module.css +608 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.d.ts +28 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.js +117 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.d.ts +1 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.js +49 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.d.ts +1 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.js +107 -0
- package/dist/Shared/ErkCalendar/CalendarFunctions.d.ts +28 -0
- package/dist/Shared/ErkCalendar/CalendarFunctions.js +121 -0
- package/dist/Shared/ErkCalendar/CalendarPopovers.d.ts +45 -0
- package/dist/Shared/ErkCalendar/CalendarPopovers.js +110 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.d.ts +73 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.js +293 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.module.css +775 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.test.d.ts +1 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.test.js +171 -0
- package/dist/Shared/ErkDatePicker/ErkDatePicker.js +1 -3
- package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.d.ts +2 -3
- package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.js +1 -2
- package/dist/Shared/ErkLocationField/ErkLocationField.d.ts +23 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.js +73 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.module.css +29 -0
- package/dist/Shared/ErkLocationField/addressFields.d.ts +12 -0
- package/dist/Shared/ErkLocationField/addressFields.js +34 -0
- package/dist/Shared/ErkLocationField/locationRestrictions.d.ts +6 -0
- package/dist/Shared/ErkLocationField/locationRestrictions.js +51 -0
- package/dist/Shared/ErkLocationInput/ErkLocationInput.d.ts +15 -0
- package/dist/Shared/ErkLocationInput/ErkLocationInput.js +502 -0
- package/dist/Shared/ErkLocationInput/ErkLocationInput.module.css +115 -0
- package/dist/Shared/ErkNumberField/ErkNumberField.d.ts +42 -0
- package/dist/Shared/ErkNumberField/ErkNumberField.js +170 -0
- package/dist/constants/FormStepTypes.d.ts +5 -1
- package/dist/constants/FormStepTypes.js +4 -0
- package/dist/index.lib.d.ts +4 -2
- package/dist/index.lib.js +3 -1
- package/package.json +2 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { CalendarDefaultView, CalendarEvent, CalendarEventCategory, CalendarEventTemplate, CalendarValue } from '../../@Types/CalendarFormStep';
|
|
2
|
+
/**
|
|
3
|
+
* Fuente adicional de eventos que se superpone al calendario propio —
|
|
4
|
+
* eventos a nivel de organización, de área, de proyecto, etc.
|
|
5
|
+
* Se muestran en solo lectura (se gestionan en su propio nivel) y se pueden
|
|
6
|
+
* ocultar/mostrar desde el sidebar ("Compartidos").
|
|
7
|
+
*/
|
|
8
|
+
export interface CalendarSource {
|
|
9
|
+
id: string;
|
|
10
|
+
/** Nombre visible en el sidebar (ej: "Organización", "Área — Soporte"). */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Color con el que se pintan sus eventos (si el evento no trae uno propio). */
|
|
13
|
+
color?: string;
|
|
14
|
+
events: CalendarEvent[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Disponibilidad por defecto cuando un día no tiene ventanas explícitas —
|
|
18
|
+
* el sistema de días hábiles del app (semana laboral menos festivos, como
|
|
19
|
+
* `WorkingDaysCache`).
|
|
20
|
+
*/
|
|
21
|
+
export interface BusinessDaysConfig {
|
|
22
|
+
/** Hora de inicio del horario hábil (HH:mm). */
|
|
23
|
+
start: string;
|
|
24
|
+
/** Hora de fin del horario hábil (HH:mm). */
|
|
25
|
+
end: string;
|
|
26
|
+
/** Días hábiles de la semana (0=Dom … 6=Sáb). @default [1,2,3,4,5] */
|
|
27
|
+
days?: number[];
|
|
28
|
+
/** Fechas no hábiles (festivos) `YYYY-MM-DD` — ej. del WorkingDaysCache. */
|
|
29
|
+
holidays?: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface ErkCalendarProps {
|
|
32
|
+
value: CalendarValue | null;
|
|
33
|
+
onChange: (value: CalendarValue | null) => void;
|
|
34
|
+
/** Vista inicial. @default WEEK */
|
|
35
|
+
defaultView?: CalendarDefaultView;
|
|
36
|
+
/** Vistas disponibles en el switcher. @default day/week/month */
|
|
37
|
+
enabledViews?: CalendarDefaultView[];
|
|
38
|
+
/** Categorías que se pueden crear; vacío/undefined = todas. */
|
|
39
|
+
allowedCategories?: CalendarEventCategory[];
|
|
40
|
+
/** Plantillas de la organización (las `bookable` son tipos de espacio agendable). */
|
|
41
|
+
eventTemplates?: CalendarEventTemplate[];
|
|
42
|
+
/** Fuentes compartidas (organización, área, proyecto…) superpuestas en solo lectura. */
|
|
43
|
+
sources?: CalendarSource[];
|
|
44
|
+
/** Disponibilidad por defecto (días hábiles) cuando no hay ventanas explícitas. */
|
|
45
|
+
businessDays?: BusinessDaysConfig;
|
|
46
|
+
/** @default true */
|
|
47
|
+
showWeekends?: boolean;
|
|
48
|
+
/** Hora mínima visible (HH:mm). @default "00:00" */
|
|
49
|
+
minTime?: string;
|
|
50
|
+
/** Hora máxima visible (HH:mm). @default "23:00" */
|
|
51
|
+
maxTime?: string;
|
|
52
|
+
/** Permite recurrencia (RRULE diaria/semanal/mensual). @default true */
|
|
53
|
+
allowRecurring?: boolean;
|
|
54
|
+
/** Permite eventos de todo el día. @default true */
|
|
55
|
+
allowAllDay?: boolean;
|
|
56
|
+
/** Zona horaria que se guarda junto a los eventos. */
|
|
57
|
+
timezone?: string;
|
|
58
|
+
readOnly?: boolean;
|
|
59
|
+
error?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Calendario completo reutilizable (vistas día/semana/mes/agenda) con sidebar:
|
|
63
|
+
* mini-calendario, creación rápida y listas de disponibilidad/eventos con
|
|
64
|
+
* visibilidad por categoría o plantilla.
|
|
65
|
+
*
|
|
66
|
+
* Las categorías de disponibilidad (laboral/presencial/virtual/específico) se
|
|
67
|
+
* pintan como franja rayada de fondo — son los espacios donde un booking puede
|
|
68
|
+
* agendar; si un día no tiene ventanas explícitas, `businessDays` pinta la
|
|
69
|
+
* disponibilidad por defecto del sistema de días hábiles. Los eventos usan
|
|
70
|
+
* ISO 8601 + RRULE (RFC 5545), el mismo estándar de Google/Outlook.
|
|
71
|
+
*/
|
|
72
|
+
declare function ErkCalendar({ value, onChange, defaultView, enabledViews, allowedCategories, eventTemplates, sources, businessDays, showWeekends, minTime, maxTime, allowRecurring, allowAllDay, timezone, readOnly, error, }: ErkCalendarProps): JSX.Element;
|
|
73
|
+
export default ErkCalendar;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
3
|
+
import { addDays, addMonths, addWeeks, endOfMonth, endOfWeek, format, isSameDay, isSameMonth, isToday, startOfDay, startOfMonth, startOfWeek, } from 'date-fns';
|
|
4
|
+
import { es } from 'date-fns/locale';
|
|
5
|
+
import ArrowLeftIcon from '../../Icons/ArrowLeftIcon';
|
|
6
|
+
import ArrowRightIcon from '../../Icons/ArrowRightIcon';
|
|
7
|
+
import { CalendarDefaultView, CalendarEventCategory, } from '../../@Types/CalendarFormStep';
|
|
8
|
+
import ErkButton from '../ErkButton/ErkButton';
|
|
9
|
+
import { CATEGORY_COLORS, CATEGORY_LABELS, eventColor, eventLabel, isAvailability, isoToHHMM, minutesToHHMM, occursOnDay, parseTime, toMinutes, } from './CalendarFunctions';
|
|
10
|
+
import { CalendarCreatePopover, CalendarDetailPopover, } from './CalendarPopovers';
|
|
11
|
+
import styles from './ErkCalendar.module.css';
|
|
12
|
+
const CELL_HEIGHT = 56; // px por hora
|
|
13
|
+
const WEEK_START = 1; // lunes
|
|
14
|
+
const AGENDA_DAYS = 30;
|
|
15
|
+
const AVAILABILITY_CATS = [
|
|
16
|
+
CalendarEventCategory.WORKING,
|
|
17
|
+
CalendarEventCategory.PHYSICAL,
|
|
18
|
+
CalendarEventCategory.VIRTUAL,
|
|
19
|
+
];
|
|
20
|
+
const EVENT_CATS = [
|
|
21
|
+
CalendarEventCategory.MEETING,
|
|
22
|
+
CalendarEventCategory.BUSY,
|
|
23
|
+
CalendarEventCategory.CUSTOM,
|
|
24
|
+
];
|
|
25
|
+
const VIEW_LABELS = {
|
|
26
|
+
[CalendarDefaultView.DAY]: 'Día',
|
|
27
|
+
[CalendarDefaultView.WEEK]: 'Semana',
|
|
28
|
+
[CalendarDefaultView.MONTH]: 'Mes',
|
|
29
|
+
[CalendarDefaultView.AGENDA]: 'Agenda',
|
|
30
|
+
};
|
|
31
|
+
function capitalize(text) {
|
|
32
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
33
|
+
}
|
|
34
|
+
/** Clave de visibilidad de un evento: su plantilla, o su categoría. */
|
|
35
|
+
function visibilityKey(event) {
|
|
36
|
+
return event.eventTemplateId ? `tpl:${event.eventTemplateId}` : `cat:${event.category}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Calendario completo reutilizable (vistas día/semana/mes/agenda) con sidebar:
|
|
40
|
+
* mini-calendario, creación rápida y listas de disponibilidad/eventos con
|
|
41
|
+
* visibilidad por categoría o plantilla.
|
|
42
|
+
*
|
|
43
|
+
* Las categorías de disponibilidad (laboral/presencial/virtual/específico) se
|
|
44
|
+
* pintan como franja rayada de fondo — son los espacios donde un booking puede
|
|
45
|
+
* agendar; si un día no tiene ventanas explícitas, `businessDays` pinta la
|
|
46
|
+
* disponibilidad por defecto del sistema de días hábiles. Los eventos usan
|
|
47
|
+
* ISO 8601 + RRULE (RFC 5545), el mismo estándar de Google/Outlook.
|
|
48
|
+
*/
|
|
49
|
+
function ErkCalendar({ value, onChange, defaultView = CalendarDefaultView.WEEK, enabledViews, allowedCategories, eventTemplates, sources, businessDays, showWeekends = true, minTime = '00:00', maxTime = '23:00', allowRecurring = true, allowAllDay = true, timezone, readOnly = false, error, }) {
|
|
50
|
+
const views = useMemo(() => enabledViews?.length
|
|
51
|
+
? enabledViews
|
|
52
|
+
: [CalendarDefaultView.DAY, CalendarDefaultView.WEEK, CalendarDefaultView.MONTH], [enabledViews]);
|
|
53
|
+
const categories = useMemo(() => allowedCategories?.length ? allowedCategories : Object.values(CalendarEventCategory), [allowedCategories]);
|
|
54
|
+
const availabilityCategories = AVAILABILITY_CATS.filter((c) => categories.includes(c));
|
|
55
|
+
const eventCategories = EVENT_CATS.filter((c) => categories.includes(c));
|
|
56
|
+
const bookableTemplates = eventTemplates?.filter((t) => t.bookable) ?? [];
|
|
57
|
+
const otherTemplates = eventTemplates?.filter((t) => !t.bookable) ?? [];
|
|
58
|
+
const [view, setView] = useState(views.includes(defaultView) ? defaultView : views[0]);
|
|
59
|
+
const [current, setCurrent] = useState(() => startOfDay(new Date()));
|
|
60
|
+
const [miniMonth, setMiniMonth] = useState(() => startOfMonth(new Date()));
|
|
61
|
+
const [hidden, setHidden] = useState(new Set());
|
|
62
|
+
const [createState, setCreateState] = useState(null);
|
|
63
|
+
const [detailState, setDetailState] = useState(null);
|
|
64
|
+
const events = value?.events ?? [];
|
|
65
|
+
const minHour = parseTime(minTime).h;
|
|
66
|
+
const maxHour = parseTime(maxTime).h + 1;
|
|
67
|
+
const hours = Array.from({ length: maxHour - minHour }, (_, i) => minHour + i);
|
|
68
|
+
// ------------------------------------------------------------------
|
|
69
|
+
// Value updates
|
|
70
|
+
// ------------------------------------------------------------------
|
|
71
|
+
const setEvents = useCallback((next) => {
|
|
72
|
+
onChange({ type: 'CALENDAR_VALUE', events: next, timezone: value?.timezone ?? timezone });
|
|
73
|
+
}, [onChange, value?.timezone, timezone]);
|
|
74
|
+
const addEvent = (event) => {
|
|
75
|
+
setEvents([...events, event]);
|
|
76
|
+
setCreateState(null);
|
|
77
|
+
};
|
|
78
|
+
const deleteEvent = (id) => {
|
|
79
|
+
setEvents(events.filter((e) => e.id !== id));
|
|
80
|
+
setDetailState(null);
|
|
81
|
+
};
|
|
82
|
+
// ------------------------------------------------------------------
|
|
83
|
+
// Visibility (sidebar toggles)
|
|
84
|
+
// ------------------------------------------------------------------
|
|
85
|
+
const toggleVisibility = (key) => {
|
|
86
|
+
setHidden((prev) => {
|
|
87
|
+
const next = new Set(prev);
|
|
88
|
+
if (next.has(key))
|
|
89
|
+
next.delete(key);
|
|
90
|
+
else
|
|
91
|
+
next.add(key);
|
|
92
|
+
return next;
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
const eventsForDay = (day) => [
|
|
96
|
+
...events
|
|
97
|
+
.filter((e) => occursOnDay(e, day) && !hidden.has(visibilityKey(e)))
|
|
98
|
+
.map((event) => ({ event, own: true })),
|
|
99
|
+
...(sources ?? [])
|
|
100
|
+
.filter((s) => !hidden.has(`src:${s.id}`))
|
|
101
|
+
.flatMap((s) => s.events
|
|
102
|
+
.filter((e) => occursOnDay(e, day))
|
|
103
|
+
.map((e) => ({
|
|
104
|
+
event: !e.color && s.color ? { ...e, color: s.color } : e,
|
|
105
|
+
own: false,
|
|
106
|
+
}))),
|
|
107
|
+
];
|
|
108
|
+
// ------------------------------------------------------------------
|
|
109
|
+
// Business days fallback
|
|
110
|
+
// ------------------------------------------------------------------
|
|
111
|
+
const businessFallbackFor = (day, dayEvents) => {
|
|
112
|
+
if (!businessDays)
|
|
113
|
+
return false;
|
|
114
|
+
if (dayEvents.some(({ event }) => isAvailability(event.category)))
|
|
115
|
+
return false;
|
|
116
|
+
const days = businessDays.days ?? [1, 2, 3, 4, 5];
|
|
117
|
+
if (!days.includes(day.getDay()))
|
|
118
|
+
return false;
|
|
119
|
+
return !businessDays.holidays?.includes(format(day, 'yyyy-MM-dd'));
|
|
120
|
+
};
|
|
121
|
+
// ------------------------------------------------------------------
|
|
122
|
+
// Navigation
|
|
123
|
+
// ------------------------------------------------------------------
|
|
124
|
+
const weekStart = startOfWeek(current, { weekStartsOn: WEEK_START });
|
|
125
|
+
const weekEnd = endOfWeek(current, { weekStartsOn: WEEK_START });
|
|
126
|
+
const visibleDays = useMemo(() => {
|
|
127
|
+
if (view === CalendarDefaultView.DAY)
|
|
128
|
+
return [current];
|
|
129
|
+
const start = startOfWeek(current, { weekStartsOn: WEEK_START });
|
|
130
|
+
const days = Array.from({ length: 7 }, (_, i) => addDays(start, i));
|
|
131
|
+
return showWeekends ? days : days.filter((d) => d.getDay() !== 0 && d.getDay() !== 6);
|
|
132
|
+
}, [view, current, showWeekends]);
|
|
133
|
+
const shift = (dir) => {
|
|
134
|
+
if (view === CalendarDefaultView.MONTH)
|
|
135
|
+
setCurrent((d) => addMonths(d, dir));
|
|
136
|
+
else if (view === CalendarDefaultView.DAY)
|
|
137
|
+
setCurrent((d) => addDays(d, dir));
|
|
138
|
+
else
|
|
139
|
+
setCurrent((d) => addWeeks(d, dir));
|
|
140
|
+
};
|
|
141
|
+
const goToDay = (day) => {
|
|
142
|
+
setCurrent(day);
|
|
143
|
+
setMiniMonth(startOfMonth(day));
|
|
144
|
+
};
|
|
145
|
+
const rangeLabel = (() => {
|
|
146
|
+
if (view === CalendarDefaultView.MONTH)
|
|
147
|
+
return capitalize(format(current, 'MMMM yyyy', { locale: es }));
|
|
148
|
+
if (view === CalendarDefaultView.DAY)
|
|
149
|
+
return capitalize(format(current, 'EEEE d MMM', { locale: es }));
|
|
150
|
+
if (view === CalendarDefaultView.AGENDA)
|
|
151
|
+
return `Próximos ${AGENDA_DAYS} días`;
|
|
152
|
+
return `${format(weekStart, 'd MMM', { locale: es })} – ${format(weekEnd, 'd MMM', { locale: es })}`;
|
|
153
|
+
})();
|
|
154
|
+
// ------------------------------------------------------------------
|
|
155
|
+
// Create flow
|
|
156
|
+
// ------------------------------------------------------------------
|
|
157
|
+
const openCreateAt = (anchorEl, day, startMins) => {
|
|
158
|
+
if (readOnly)
|
|
159
|
+
return;
|
|
160
|
+
const clamped = Math.max(minHour * 60, Math.min(startMins, (maxHour - 1) * 60));
|
|
161
|
+
setCreateState({
|
|
162
|
+
anchorEl,
|
|
163
|
+
date: day,
|
|
164
|
+
startHHMM: minutesToHHMM(clamped),
|
|
165
|
+
endHHMM: minutesToHHMM(Math.min(clamped + 60, maxHour * 60)),
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
const handleGridClick = (e, day) => {
|
|
169
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
170
|
+
const relY = e.clientY - rect.top;
|
|
171
|
+
const snapped = Math.round((relY / CELL_HEIGHT) * 2) * 30; // snap a 30 min
|
|
172
|
+
openCreateAt(e.currentTarget, day, minHour * 60 + snapped);
|
|
173
|
+
};
|
|
174
|
+
const handleNewEvent = (e) => {
|
|
175
|
+
openCreateAt(e.currentTarget, current, 9 * 60);
|
|
176
|
+
};
|
|
177
|
+
const openDetail = (e, event, own) => {
|
|
178
|
+
e.stopPropagation();
|
|
179
|
+
setDetailState({ anchorEl: e.currentTarget, event, own });
|
|
180
|
+
};
|
|
181
|
+
// ------------------------------------------------------------------
|
|
182
|
+
// Sidebar
|
|
183
|
+
// ------------------------------------------------------------------
|
|
184
|
+
const renderSidebarItem = (key, color, label) => (_jsxs("button", { type: "button", className: hidden.has(key) ? `${styles.sideItem} ${styles.sideItemOff}` : styles.sideItem, onClick: () => toggleVisibility(key), title: hidden.has(key) ? 'Mostrar' : 'Ocultar', children: [_jsx("span", { className: styles.catDot, style: { backgroundColor: color } }), label] }, key));
|
|
185
|
+
const renderMiniCalendar = () => {
|
|
186
|
+
const gridStart = startOfWeek(miniMonth, { weekStartsOn: WEEK_START });
|
|
187
|
+
const cells = Array.from({ length: 42 }, (_, i) => addDays(gridStart, i));
|
|
188
|
+
return (_jsxs("div", { className: styles.miniWrap, children: [_jsxs("div", { className: styles.miniHead, children: [_jsx("span", { className: styles.miniTitle, children: capitalize(format(miniMonth, 'MMMM yyyy', { locale: es })) }), _jsxs("span", { className: styles.miniNav, children: [_jsx("button", { type: "button", "aria-label": "Mes anterior", onClick: () => setMiniMonth((m) => addMonths(m, -1)), children: "\u2039" }), _jsx("button", { type: "button", "aria-label": "Mes siguiente", onClick: () => setMiniMonth((m) => addMonths(m, 1)), children: "\u203A" })] })] }), _jsxs("div", { className: styles.miniGrid, children: [['L', 'M', 'M', 'J', 'V', 'S', 'D'].map((d, i) => (_jsx("span", { className: styles.miniDow, children: d }, i))), cells.map((day) => {
|
|
189
|
+
const cls = [
|
|
190
|
+
styles.miniDay,
|
|
191
|
+
!isSameMonth(day, miniMonth) ? styles.miniDayMut : '',
|
|
192
|
+
isToday(day) ? styles.miniDayToday : '',
|
|
193
|
+
!isToday(day) && isSameDay(day, current) ? styles.miniDaySel : '',
|
|
194
|
+
]
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
.join(' ');
|
|
197
|
+
return (_jsx("button", { type: "button", className: cls, onClick: () => goToDay(day), children: format(day, 'd') }, day.toISOString()));
|
|
198
|
+
})] })] }));
|
|
199
|
+
};
|
|
200
|
+
const renderSidebar = () => (_jsxs("aside", { className: styles.side, children: [!readOnly && (_jsx(ErkButton, { text: "+ Nuevo evento", sx: { width: '100%' }, onClick: handleNewEvent })), renderMiniCalendar(), (availabilityCategories.length > 0 || bookableTemplates.length > 0) && (_jsxs("div", { className: styles.sideList, children: [_jsx("span", { className: styles.sideLbl, children: "Disponibilidad" }), availabilityCategories.map((cat) => renderSidebarItem(`cat:${cat}`, CATEGORY_COLORS[cat], CATEGORY_LABELS[cat])), bookableTemplates.map((t) => renderSidebarItem(`tpl:${t.id}`, t.color ?? CATEGORY_COLORS[t.category], t.name))] })), (eventCategories.length > 0 || otherTemplates.length > 0) && (_jsxs("div", { className: styles.sideList, children: [_jsx("span", { className: styles.sideLbl, children: "Eventos" }), eventCategories.map((cat) => renderSidebarItem(`cat:${cat}`, CATEGORY_COLORS[cat], CATEGORY_LABELS[cat])), otherTemplates.map((t) => renderSidebarItem(`tpl:${t.id}`, t.color ?? CATEGORY_COLORS[t.category], t.name))] })), sources && sources.length > 0 && (_jsxs("div", { className: styles.sideList, children: [_jsx("span", { className: styles.sideLbl, children: "Compartidos" }), sources.map((s) => renderSidebarItem(`src:${s.id}`, s.color ?? '#98a2b3', s.name))] }))] }));
|
|
201
|
+
// ------------------------------------------------------------------
|
|
202
|
+
// Time grid (day / week)
|
|
203
|
+
// ------------------------------------------------------------------
|
|
204
|
+
const renderTimeGrid = () => {
|
|
205
|
+
const allDayByDay = visibleDays.map((day) => eventsForDay(day).filter(({ event }) => event.isAllDay));
|
|
206
|
+
const hasAllDay = allDayByDay.some((list) => list.length > 0);
|
|
207
|
+
return (_jsxs("div", { className: styles.cal, children: [_jsxs("div", { className: styles.calHead, style: { gridTemplateColumns: `54px repeat(${visibleDays.length}, 1fr)` }, children: [_jsx("div", { className: styles.gutter }), visibleDays.map((day) => (_jsxs("button", { type: "button", className: styles.dayHead, onClick: () => {
|
|
208
|
+
goToDay(day);
|
|
209
|
+
setView(CalendarDefaultView.DAY);
|
|
210
|
+
}, children: [_jsx("span", { className: styles.dayHeadDow, children: format(day, 'EEE', { locale: es }) }), _jsx("span", { className: isToday(day)
|
|
211
|
+
? `${styles.dayHeadNum} ${styles.dayHeadToday}`
|
|
212
|
+
: styles.dayHeadNum, children: format(day, 'd') })] }, day.toISOString())))] }), hasAllDay && (_jsxs("div", { className: styles.allDayRow, style: { gridTemplateColumns: `54px repeat(${visibleDays.length}, 1fr)` }, children: [_jsx("div", { className: styles.allDayLbl, children: "D\u00EDa" }), allDayByDay.map((list, i) => (_jsx("div", { className: styles.allDayCell, children: list.map(({ event, own }) => (_jsx("button", { type: "button", className: styles.allDayChip, style: { backgroundColor: eventColor(event, eventTemplates) }, onClick: (e) => openDetail(e, event, own), children: eventLabel(event, eventTemplates) }, `${own ? 'own' : 'src'}-${event.id}`))) }, visibleDays[i].toISOString())))] })), _jsxs("div", { className: styles.grid, style: { gridTemplateColumns: `54px repeat(${visibleDays.length}, 1fr)` }, children: [_jsx("div", { className: styles.timeCol, children: hours.map((h) => (_jsx("div", { className: styles.hourLabel, style: { height: CELL_HEIGHT }, children: h > 0 ? `${h}:00` : '' }, h))) }), visibleDays.map((day) => {
|
|
213
|
+
const dayEvents = eventsForDay(day).filter(({ event }) => !event.isAllDay);
|
|
214
|
+
const fallback = businessFallbackFor(day, dayEvents);
|
|
215
|
+
return (_jsxs("div", { className: styles.dayCol, style: { height: hours.length * CELL_HEIGHT }, onClick: (e) => handleGridClick(e, day), children: [hours.map((h) => (_jsx("div", { className: styles.hourCell, style: { height: CELL_HEIGHT } }, h))), fallback && businessDays && (_jsx("div", { className: `${styles.avail} ${styles.availFallback}`, style: {
|
|
216
|
+
top: (toMinutes(businessDays.start) - minHour * 60) *
|
|
217
|
+
(CELL_HEIGHT / 60),
|
|
218
|
+
height: (toMinutes(businessDays.end) -
|
|
219
|
+
toMinutes(businessDays.start)) *
|
|
220
|
+
(CELL_HEIGHT / 60),
|
|
221
|
+
}, children: _jsx("span", { className: styles.availTag, children: "D\u00EDas h\u00E1biles" }) })), dayEvents.map(({ event, own }) => {
|
|
222
|
+
const key = `${own ? 'own' : 'src'}-${event.id}`;
|
|
223
|
+
const top = (toMinutes(isoToHHMM(event.start)) - minHour * 60) *
|
|
224
|
+
(CELL_HEIGHT / 60);
|
|
225
|
+
const height = Math.max((toMinutes(isoToHHMM(event.end)) -
|
|
226
|
+
toMinutes(isoToHHMM(event.start))) *
|
|
227
|
+
(CELL_HEIGHT / 60), 20);
|
|
228
|
+
const color = eventColor(event, eventTemplates);
|
|
229
|
+
if (isAvailability(event.category)) {
|
|
230
|
+
return (_jsx("div", { className: styles.avail, style: {
|
|
231
|
+
top,
|
|
232
|
+
height,
|
|
233
|
+
borderLeftColor: color,
|
|
234
|
+
backgroundImage: `repeating-linear-gradient(-45deg, ${color}1a, ${color}1a 6px, ${color}0d 6px, ${color}0d 12px)`,
|
|
235
|
+
}, onClick: (e) => openDetail(e, event, own), children: _jsx("span", { className: styles.availTag, style: { color }, children: eventLabel(event, eventTemplates) }) }, key));
|
|
236
|
+
}
|
|
237
|
+
const busy = event.category === CalendarEventCategory.BUSY;
|
|
238
|
+
return (_jsxs("div", { className: busy ? `${styles.event} ${styles.eventBusy}` : styles.event, style: busy
|
|
239
|
+
? { top, height }
|
|
240
|
+
: { top, height, backgroundColor: color }, onClick: (e) => openDetail(e, event, own), children: [_jsx("span", { className: styles.eventTitle, children: eventLabel(event, eventTemplates) }), _jsxs("span", { className: styles.eventTime, children: [isoToHHMM(event.start), " \u2013 ", isoToHHMM(event.end)] })] }, key));
|
|
241
|
+
})] }, day.toISOString()));
|
|
242
|
+
})] })] }));
|
|
243
|
+
};
|
|
244
|
+
// ------------------------------------------------------------------
|
|
245
|
+
// Month view
|
|
246
|
+
// ------------------------------------------------------------------
|
|
247
|
+
const renderMonth = () => {
|
|
248
|
+
const gridStart = startOfWeek(startOfMonth(current), { weekStartsOn: WEEK_START });
|
|
249
|
+
const gridEnd = endOfWeek(endOfMonth(current), { weekStartsOn: WEEK_START });
|
|
250
|
+
const days = [];
|
|
251
|
+
for (let d = gridStart; d <= gridEnd; d = addDays(d, 1))
|
|
252
|
+
days.push(d);
|
|
253
|
+
const dows = ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'];
|
|
254
|
+
return (_jsxs("div", { className: styles.monthGrid, children: [dows.map((lbl) => (_jsx("div", { className: styles.monthDow, children: lbl }, lbl))), days.map((day) => {
|
|
255
|
+
const dayEvents = eventsForDay(day);
|
|
256
|
+
return (_jsxs("button", { type: "button", className: isSameMonth(day, current)
|
|
257
|
+
? styles.monthCell
|
|
258
|
+
: `${styles.monthCell} ${styles.monthCellOut}`, onClick: () => {
|
|
259
|
+
goToDay(day);
|
|
260
|
+
setView(CalendarDefaultView.DAY);
|
|
261
|
+
}, children: [_jsx("span", { className: isToday(day)
|
|
262
|
+
? `${styles.monthNum} ${styles.dayHeadToday}`
|
|
263
|
+
: styles.monthNum, children: format(day, 'd') }), dayEvents.slice(0, 3).map(({ event, own }) => (_jsx("span", { className: styles.monthEvent, style: { backgroundColor: eventColor(event, eventTemplates) }, children: eventLabel(event, eventTemplates) }, `${own ? 'own' : 'src'}-${event.id}`))), dayEvents.length > 3 && (_jsxs("span", { className: styles.monthMore, children: ["+", dayEvents.length - 3, " m\u00E1s"] }))] }, day.toISOString()));
|
|
264
|
+
})] }));
|
|
265
|
+
};
|
|
266
|
+
// ------------------------------------------------------------------
|
|
267
|
+
// Agenda view
|
|
268
|
+
// ------------------------------------------------------------------
|
|
269
|
+
const renderAgenda = () => {
|
|
270
|
+
const days = Array.from({ length: AGENDA_DAYS }, (_, i) => addDays(current, i));
|
|
271
|
+
const withEvents = days
|
|
272
|
+
.map((day) => ({ day, dayEvents: eventsForDay(day) }))
|
|
273
|
+
.filter(({ dayEvents }) => dayEvents.length > 0);
|
|
274
|
+
if (withEvents.length === 0) {
|
|
275
|
+
return (_jsxs("div", { className: styles.agendaEmpty, children: ["Sin eventos en los pr\u00F3ximos ", AGENDA_DAYS, " d\u00EDas."] }));
|
|
276
|
+
}
|
|
277
|
+
return (_jsx("div", { className: styles.agenda, children: withEvents.map(({ day, dayEvents }) => (_jsxs("div", { className: styles.agendaDay, children: [_jsx("div", { className: styles.agendaDayLabel, children: capitalize(format(day, 'EEEE d MMM', { locale: es })) }), dayEvents
|
|
278
|
+
.slice()
|
|
279
|
+
.sort((a, b) => isoToHHMM(a.event.start).localeCompare(isoToHHMM(b.event.start)))
|
|
280
|
+
.map(({ event, own }) => (_jsxs("button", { type: "button", className: styles.agendaRow, onClick: (e) => openDetail(e, event, own), children: [_jsx("span", { className: styles.catDot, style: { backgroundColor: eventColor(event, eventTemplates) } }), _jsx("span", { className: styles.agendaTitle, children: eventLabel(event, eventTemplates) }), _jsx("span", { className: styles.agendaTime, children: event.isAllDay
|
|
281
|
+
? 'Todo el día'
|
|
282
|
+
: `${isoToHHMM(event.start)} – ${isoToHHMM(event.end)}` })] }, `${own ? 'own' : 'src'}-${event.id}-${day.toISOString()}`)))] }, day.toISOString()))) }));
|
|
283
|
+
};
|
|
284
|
+
// ------------------------------------------------------------------
|
|
285
|
+
// Render
|
|
286
|
+
// ------------------------------------------------------------------
|
|
287
|
+
return (_jsxs("div", { className: styles.container, children: [_jsxs("div", { className: styles.app, children: [renderSidebar(), _jsxs("section", { className: styles.main, children: [_jsxs("div", { className: styles.toolbar, children: [_jsx(ErkButton, { text: "Hoy", variant: "outlined", onClick: () => goToDay(startOfDay(new Date())) }), _jsxs("div", { className: styles.navBtns, children: [_jsx("button", { type: "button", "aria-label": "Anterior", onClick: () => shift(-1), children: _jsx(ArrowLeftIcon, {}) }), _jsx("button", { type: "button", "aria-label": "Siguiente", onClick: () => shift(1), children: _jsx(ArrowRightIcon, {}) })] }), _jsx("span", { className: styles.range, children: rangeLabel }), _jsx("span", { className: styles.spacer }), views.length > 1 && (_jsx("div", { className: styles.views, children: views.map((v) => (_jsx("button", { type: "button", className: view === v ? styles.viewOn : undefined, onClick: () => setView(v), children: VIEW_LABELS[v] }, v))) }))] }), view === CalendarDefaultView.MONTH
|
|
288
|
+
? renderMonth()
|
|
289
|
+
: view === CalendarDefaultView.AGENDA
|
|
290
|
+
? renderAgenda()
|
|
291
|
+
: renderTimeGrid()] })] }), createState && (_jsx(CalendarCreatePopover, { state: createState, availabilityCategories: availabilityCategories, eventCategories: eventCategories, eventTemplates: eventTemplates, allowRecurring: allowRecurring, allowAllDay: allowAllDay, onClose: () => setCreateState(null), onCreate: addEvent })), detailState && (_jsx(CalendarDetailPopover, { state: detailState, eventTemplates: eventTemplates, readOnly: readOnly || !detailState.own, onClose: () => setDetailState(null), onDelete: deleteEvent })), error && _jsx("div", { className: styles.err, children: error })] }));
|
|
292
|
+
}
|
|
293
|
+
export default ErkCalendar;
|