@arquimedes.co/eureka-forms 3.0.52-new-steps → 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.
Files changed (25) hide show
  1. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +1 -0
  2. package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.d.ts +4 -0
  3. package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.js +20 -258
  4. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.d.ts +5 -2
  5. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.js +116 -42
  6. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.module.css +587 -92
  7. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.d.ts +28 -0
  8. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.js +117 -0
  9. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.d.ts +1 -0
  10. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.js +49 -0
  11. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.d.ts +1 -0
  12. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.js +107 -0
  13. package/dist/Shared/ErkCalendar/CalendarFunctions.d.ts +28 -0
  14. package/dist/Shared/ErkCalendar/CalendarFunctions.js +121 -0
  15. package/dist/Shared/ErkCalendar/CalendarPopovers.d.ts +45 -0
  16. package/dist/Shared/ErkCalendar/CalendarPopovers.js +110 -0
  17. package/dist/Shared/ErkCalendar/ErkCalendar.d.ts +73 -0
  18. package/dist/Shared/ErkCalendar/ErkCalendar.js +293 -0
  19. package/dist/Shared/ErkCalendar/ErkCalendar.module.css +775 -0
  20. package/dist/Shared/ErkCalendar/ErkCalendar.test.d.ts +1 -0
  21. package/dist/Shared/ErkCalendar/ErkCalendar.test.js +171 -0
  22. package/dist/index.lib.d.ts +4 -2
  23. package/dist/index.lib.js +3 -1
  24. package/package.json +1 -1
  25. package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.module.css +0 -531
@@ -21,6 +21,7 @@ function MaterialBookingStep({ step, editable, fetchSlots }) {
21
21
  '--eureka-primary': formStyle.primaryColor,
22
22
  '--eureka-text': formStyle.textColor,
23
23
  '--eureka-bg': formStyle.standAloneBackgroundColor ?? '#fff',
24
+ '--booking-accent': formStyle.secondaryContrastColor,
24
25
  };
25
26
  const { ref, value, onChange, error } = useFormStep(step, {
26
27
  defaultValue: null,
@@ -1,3 +1,7 @@
1
1
  import { CalendarStepProps } from '../CalendarStep';
2
+ /**
3
+ * Conector del paso CALENDAR: enlaza el estado del formulario (`useFormStep`) y el
4
+ * theming del form con el componente reutilizable `ErkCalendar` (en Shared).
5
+ */
2
6
  declare function MaterialCalendarStep({ step, editable }: CalendarStepProps): JSX.Element;
3
7
  export default MaterialCalendarStep;
@@ -1,269 +1,31 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useContext, useState, useCallback, useRef, useMemo } from 'react';
3
- import { startOfWeek, endOfWeek, addWeeks, addDays, addMonths, startOfMonth, endOfMonth, isSameMonth, isSameDay, isToday, format, parse, startOfDay, } from 'date-fns';
4
- import { es } from 'date-fns/locale';
5
- import { Popover } from '@mui/material';
6
- import ArrowLeftIcon from '../../../Icons/ArrowLeftIcon';
7
- import ArrowRightIcon from '../../../Icons/ArrowRightIcon';
8
- import ViewWeekIcon from '../../../Icons/ViewWeekIcon';
9
- import ViewDayIcon from '../../../Icons/ViewDayIcon';
10
- import CalendarIcon from '../../../Icons/CalendarIcon';
11
- import AddIcon from '../../../Icons/AddIcon';
12
- import CloseIcon from '../../../Icons/CloseIcon';
13
- import DeleteIcon from '../../../Icons/DeleteIcon';
14
- import { CalendarEventCategory, CalendarDefaultView, } from '../../../@Types/CalendarFormStep';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useContext } from 'react';
15
3
  import { useFormStep } from '../../StepHooks';
16
4
  import FormContext from '../../../Contexts/FormContext';
17
5
  import { selectBreakPoint, useAppSelector } from '../../../hooks';
18
6
  import { calcStepWidth } from '../../StepFunctions';
19
- import styles from './MaterialCalendarStep.module.css';
20
- // ---------------------------------------------------------------------------
21
- // Constants
22
- // ---------------------------------------------------------------------------
23
- const CELL_HEIGHT = 48; // px per hour
24
- const WEEK_START = 1; // Monday
25
- const CATEGORY_COLORS = {
26
- [CalendarEventCategory.WORKING]: '#4CAF50',
27
- [CalendarEventCategory.PHYSICAL]: '#2196F3',
28
- [CalendarEventCategory.VIRTUAL]: '#9C27B0',
29
- [CalendarEventCategory.SPECIFIC]: '#FF9800',
30
- [CalendarEventCategory.MEETING]: '#E91E63',
31
- [CalendarEventCategory.BUSY]: '#607D8B',
32
- [CalendarEventCategory.CUSTOM]: '#795548',
33
- };
34
- const CATEGORY_LABELS = {
35
- [CalendarEventCategory.WORKING]: 'Trabajo',
36
- [CalendarEventCategory.PHYSICAL]: 'Presencial',
37
- [CalendarEventCategory.VIRTUAL]: 'Virtual',
38
- [CalendarEventCategory.SPECIFIC]: 'Específico',
39
- [CalendarEventCategory.MEETING]: 'Reunión',
40
- [CalendarEventCategory.BUSY]: 'Ocupado',
41
- [CalendarEventCategory.CUSTOM]: 'Personalizado',
42
- };
43
- // ---------------------------------------------------------------------------
44
- // Helpers
45
- // ---------------------------------------------------------------------------
46
- function parseTime(hhmm) {
47
- const [h, m] = hhmm.split(':').map(Number);
48
- return { h: h ?? 0, m: m ?? 0 };
49
- }
50
- function toMinutes(hhmm) {
51
- const { h, m } = parseTime(hhmm);
52
- return h * 60 + m;
53
- }
54
- function minutesToHHMM(mins) {
55
- const h = Math.floor(mins / 60);
56
- const m = mins % 60;
57
- return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
58
- }
59
- function isoToHHMM(iso) {
60
- // "2024-06-19T09:30:00" → "09:30"
61
- return iso.slice(11, 16);
62
- }
63
- function dateAndTimeToISO(date, hhmm) {
64
- const { h, m } = parseTime(hhmm);
65
- const d = new Date(date);
66
- d.setHours(h, m, 0, 0);
67
- return d.toISOString().slice(0, 19);
68
- }
69
- function isoToDate(iso) {
70
- // Treat as local time (no timezone shift)
71
- return parse(iso.slice(0, 19), 'yyyy-MM-dd\'T\'HH:mm:ss', new Date());
72
- }
73
- function newEventId() {
74
- return `evt-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
75
- }
76
- function EventBlock({ event, minHour, template, onDelete, readOnly }) {
77
- const startMins = toMinutes(isoToHHMM(event.start));
78
- const endMins = toMinutes(isoToHHMM(event.end));
79
- const top = (startMins - minHour * 60) * (CELL_HEIGHT / 60);
80
- const height = Math.max((endMins - startMins) * (CELL_HEIGHT / 60), 18);
81
- const color = template?.color ?? CATEGORY_COLORS[event.category] ?? '#607D8B';
82
- const label = event.title ?? template?.name ?? CATEGORY_LABELS[event.category];
83
- return (_jsxs("div", { className: styles.eventBlock, style: { top, height, backgroundColor: color }, title: `${label} ${isoToHHMM(event.start)}–${isoToHHMM(event.end)}`, children: [_jsx("span", { className: styles.eventLabel, children: label }), _jsxs("span", { className: styles.eventTime, children: [isoToHHMM(event.start), "\u2013", isoToHHMM(event.end)] }), !readOnly && (_jsx("button", { type: "button", className: styles.eventDeleteBtn, onClick: (e) => { e.stopPropagation(); onDelete(event.id); }, "aria-label": "Eliminar", children: _jsx(DeleteIcon, { size: 13 }) }))] }));
84
- }
85
- // ---------------------------------------------------------------------------
86
- // Main component
87
- // ---------------------------------------------------------------------------
7
+ import ErkCalendar from '../../../Shared/ErkCalendar/ErkCalendar';
8
+ /**
9
+ * Conector del paso CALENDAR: enlaza el estado del formulario (`useFormStep`) y el
10
+ * theming del form con el componente reutilizable `ErkCalendar` (en Shared).
11
+ */
88
12
  function MaterialCalendarStep({ step, editable }) {
89
13
  const currentBreakPoint = useAppSelector(selectBreakPoint);
90
- const { postview } = useAppSelector((state) => state.global);
91
- const form = useContext(FormContext);
14
+ const { postview, formStyle } = useAppSelector((state) => state.global);
15
+ const formCtx = useContext(FormContext);
92
16
  const isReadOnly = !editable || postview;
93
- const widthStyle = currentBreakPoint <= step.size ? '100%' : calcStepWidth(step.size, form.size);
94
- // ------------------------------------------------------------------
95
- // Resolve config
96
- // ------------------------------------------------------------------
97
- const minTimeStr = step.minTime ?? '00:00';
98
- const maxTimeStr = step.maxTime ?? '23:00';
99
- const minHour = parseTime(minTimeStr).h;
100
- const maxHour = parseTime(maxTimeStr).h + 1;
101
- const totalHours = maxHour - minHour;
102
- const showWeekends = step.showWeekends ?? true;
103
- const allowedCategories = (step.allowedCategories?.length ? step.allowedCategories : Object.values(CalendarEventCategory));
104
- const defaultViewMode = (step.defaultView ?? CalendarDefaultView.WEEK);
105
- // ------------------------------------------------------------------
106
- // Form value
107
- // ------------------------------------------------------------------
108
- const { value, onChange } = useFormStep(step, {
17
+ const widthStyle = currentBreakPoint <= step.size ? '100%' : calcStepWidth(step.size, formCtx.size);
18
+ const cssVars = {
19
+ width: widthStyle,
20
+ '--eureka-outline': formStyle.outlineColor,
21
+ '--eureka-primary': formStyle.primaryColor,
22
+ '--eureka-text': formStyle.textColor,
23
+ '--eureka-bg': formStyle.standAloneBackgroundColor ?? '#fff',
24
+ };
25
+ const { ref, value, onChange, error } = useFormStep(step, {
109
26
  defaultValue: null,
110
- rules: {
111
- required: step.required ? 'Este campo es obligatorio' : undefined,
112
- },
27
+ rules: { required: step.required ? 'Este campo es obligatorio' : undefined },
113
28
  });
114
- const events = useMemo(() => value?.events ?? [], [value?.events]);
115
- const setEvents = useCallback((updater) => {
116
- onChange({
117
- type: 'CALENDAR_VALUE',
118
- events: updater(events),
119
- timezone: value?.timezone,
120
- });
121
- }, [events, value?.timezone, onChange]);
122
- const deleteEvent = useCallback((id) => setEvents((prev) => prev.filter((e) => e.id !== id)), [setEvents]);
123
- // ------------------------------------------------------------------
124
- // Navigation state
125
- // ------------------------------------------------------------------
126
- const [viewMode, setViewMode] = useState(defaultViewMode);
127
- const [current, setCurrent] = useState(() => startOfDay(new Date()));
128
- const [createState, setCreate] = useState(null);
129
- // Create form
130
- const [newCategory, setNewCategory] = useState(allowedCategories[0] ?? CalendarEventCategory.WORKING);
131
- const [newTitle, setNewTitle] = useState('');
132
- const [newStart, setNewStart] = useState('09:00');
133
- const [newEnd, setNewEnd] = useState('10:00');
134
- const gridRef = useRef(null);
135
- // ------------------------------------------------------------------
136
- // Derived view dates
137
- // ------------------------------------------------------------------
138
- const weekStart = startOfWeek(current, { weekStartsOn: WEEK_START });
139
- const weekEnd = endOfWeek(current, { weekStartsOn: WEEK_START });
140
- const visibleDays = (() => {
141
- if (viewMode === 'day')
142
- return [current];
143
- const days = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
144
- return showWeekends ? days : days.filter((d) => d.getDay() !== 0 && d.getDay() !== 6);
145
- })();
146
- // ------------------------------------------------------------------
147
- // Navigate
148
- // ------------------------------------------------------------------
149
- const prev = () => {
150
- if (viewMode === 'month')
151
- setCurrent((d) => addMonths(d, -1));
152
- else if (viewMode === 'week')
153
- setCurrent((d) => addWeeks(d, -1));
154
- else
155
- setCurrent((d) => addDays(d, -1));
156
- };
157
- const next = () => {
158
- if (viewMode === 'month')
159
- setCurrent((d) => addMonths(d, 1));
160
- else if (viewMode === 'week')
161
- setCurrent((d) => addWeeks(d, 1));
162
- else
163
- setCurrent((d) => addDays(d, 1));
164
- };
165
- const navLabel = () => {
166
- if (viewMode === 'month')
167
- return format(current, 'MMMM yyyy', { locale: es });
168
- if (viewMode === 'week') {
169
- const s = format(weekStart, 'd MMM', { locale: es });
170
- const e = format(weekEnd, 'd MMM yyyy', { locale: es });
171
- return `${s} – ${e}`;
172
- }
173
- return format(current, 'EEEE d \'de\' MMMM yyyy', { locale: es });
174
- };
175
- // ------------------------------------------------------------------
176
- // Events per day (for time-grid views)
177
- // ------------------------------------------------------------------
178
- const eventsForDay = (day) => events.filter((e) => isSameDay(isoToDate(e.start), day));
179
- // ------------------------------------------------------------------
180
- // Create flow
181
- // ------------------------------------------------------------------
182
- const handleGridClick = (e, day) => {
183
- if (isReadOnly)
184
- return;
185
- const rect = (e.currentTarget).getBoundingClientRect();
186
- const relY = e.clientY - rect.top;
187
- const clickedMins = Math.round((relY / CELL_HEIGHT) * 60) * 30; // snap to 30min
188
- const startMins = minHour * 60 + clickedMins;
189
- const startHHMM = minutesToHHMM(Math.min(startMins, (maxHour - 1) * 60));
190
- const endHHMM = minutesToHHMM(Math.min(startMins + 60, maxHour * 60));
191
- setNewStart(startHHMM);
192
- setNewEnd(endHHMM);
193
- setNewCategory(allowedCategories[0] ?? CalendarEventCategory.WORKING);
194
- setNewTitle('');
195
- setCreate({ anchorEl: e.currentTarget, date: day, startMins });
196
- };
197
- const handleMonthDayClick = (day) => {
198
- setCurrent(day);
199
- setViewMode('day');
200
- };
201
- const handleCreate = () => {
202
- if (!createState)
203
- return;
204
- const startISO = dateAndTimeToISO(createState.date, newStart);
205
- const endISO = dateAndTimeToISO(createState.date, newEnd);
206
- const template = step.eventTemplates?.find((t) => t.category === newCategory);
207
- const evt = {
208
- id: newEventId(),
209
- category: newCategory,
210
- start: startISO,
211
- end: endISO,
212
- title: newTitle || undefined,
213
- eventTemplateId: template?.id,
214
- };
215
- setEvents((prev) => [...prev, evt]);
216
- setCreate(null);
217
- };
218
- // ------------------------------------------------------------------
219
- // Hour labels column
220
- // ------------------------------------------------------------------
221
- const hourLabels = Array.from({ length: totalHours }, (_, i) => minHour + i);
222
- // ------------------------------------------------------------------
223
- // Time-grid view (week / day)
224
- // ------------------------------------------------------------------
225
- const renderTimeGrid = () => (_jsxs("div", { className: styles.gridWrapper, children: [_jsxs("div", { className: styles.gridHeader, style: { gridTemplateColumns: `48px repeat(${visibleDays.length}, 1fr)` }, children: [_jsx("div", { className: styles.cornerCell }), visibleDays.map((day) => (_jsxs("div", { className: `${styles.dayHeader} ${isToday(day) ? styles.dayHeaderToday : ''}`, onClick: () => { setCurrent(day); setViewMode('day'); }, children: [_jsx("span", { className: styles.dayHeaderDow, children: format(day, 'EEE', { locale: es }) }), _jsx("span", { className: `${styles.dayHeaderNum} ${isToday(day) ? styles.todayCircle : ''}`, children: format(day, 'd') })] }, day.toISOString())))] }), _jsx("div", { className: styles.gridBody, ref: gridRef, children: _jsxs("div", { className: styles.gridInner, style: { gridTemplateColumns: `48px repeat(${visibleDays.length}, 1fr)` }, children: [_jsx("div", { className: styles.hourLabels, children: hourLabels.map((h) => (_jsx("div", { className: styles.hourLabel, style: { height: CELL_HEIGHT }, children: h > 0 && `${String(h).padStart(2, '0')}:00` }, h))) }), visibleDays.map((day) => (_jsxs("div", { className: styles.dayColumn, style: { height: totalHours * CELL_HEIGHT }, onClick: (e) => handleGridClick(e, day), children: [hourLabels.map((h) => (_jsx("div", { className: styles.hourLine, style: { top: (h - minHour) * CELL_HEIGHT } }, h))), eventsForDay(day).map((evt) => (_jsx(EventBlock, { event: evt, minHour: minHour, template: step.eventTemplates?.find((t) => t.category === evt.category), onDelete: deleteEvent, readOnly: isReadOnly }, evt.id)))] }, day.toISOString())))] }) })] }));
226
- // ------------------------------------------------------------------
227
- // Month view
228
- // ------------------------------------------------------------------
229
- const renderMonth = () => {
230
- const mStart = startOfMonth(current);
231
- const mEnd = endOfMonth(current);
232
- const gridStart = startOfWeek(mStart, { weekStartsOn: WEEK_START });
233
- const gridEnd = endOfWeek(mEnd, { weekStartsOn: WEEK_START });
234
- const days = [];
235
- let d = gridStart;
236
- while (d <= gridEnd) {
237
- days.push(d);
238
- d = addDays(d, 1);
239
- }
240
- const DOW_LABELS = ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'];
241
- return (_jsxs("div", { className: styles.monthGrid, children: [DOW_LABELS.map((lbl) => (_jsx("div", { className: styles.monthDowHeader, children: lbl }, lbl))), days.map((day) => {
242
- const dayEvts = eventsForDay(day);
243
- const outside = !isSameMonth(day, current);
244
- return (_jsxs("div", { className: [
245
- styles.monthCell,
246
- outside ? styles.monthCellOutside : '',
247
- isToday(day) ? styles.monthCellToday : '',
248
- ].filter(Boolean).join(' '), onClick: () => handleMonthDayClick(day), children: [_jsx("span", { className: `${styles.monthCellNum} ${isToday(day) ? styles.todayCircle : ''}`, children: format(day, 'd') }), dayEvts.slice(0, 3).map((evt) => {
249
- const color = CATEGORY_COLORS[evt.category] ?? '#607D8B';
250
- return (_jsx("div", { className: styles.monthEventDot, style: { backgroundColor: color }, children: evt.title ?? CATEGORY_LABELS[evt.category] }, evt.id));
251
- }), dayEvts.length > 3 && (_jsxs("div", { className: styles.monthMoreLabel, children: ["+", dayEvts.length - 3, " m\u00E1s"] }))] }, day.toISOString()));
252
- })] }));
253
- };
254
- // ------------------------------------------------------------------
255
- // Create popover
256
- // ------------------------------------------------------------------
257
- const needsTitle = newCategory === CalendarEventCategory.MEETING ||
258
- newCategory === CalendarEventCategory.CUSTOM;
259
- const renderCreatePopover = () => {
260
- if (!createState)
261
- return null;
262
- return (_jsxs(Popover, { open: true, anchorEl: createState.anchorEl, onClose: () => setCreate(null), anchorOrigin: { vertical: 'top', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, slotProps: { paper: { className: styles.popoverPaper } }, children: [_jsxs("div", { className: styles.popoverHeader, children: [_jsx("span", { className: styles.popoverTitle, children: "Nuevo evento" }), _jsx("button", { type: "button", className: styles.popoverClose, onClick: () => setCreate(null), children: _jsx(CloseIcon, { size: 16 }) })] }), _jsxs("div", { className: styles.popoverBody, children: [_jsx("div", { className: styles.popoverLabel, children: "Tipo" }), _jsx("div", { className: styles.categoryGrid, children: allowedCategories.map((cat) => (_jsxs("button", { type: "button", className: `${styles.categoryChip} ${newCategory === cat ? styles.categoryChipActive : ''}`, style: newCategory === cat ? { backgroundColor: CATEGORY_COLORS[cat], color: '#fff' } : {}, onClick: () => setNewCategory(cat), children: [_jsx("span", { className: styles.categoryDot, style: { backgroundColor: CATEGORY_COLORS[cat] } }), CATEGORY_LABELS[cat]] }, cat))) }), needsTitle && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.popoverLabel, children: "Nombre" }), _jsx("input", { className: styles.popoverInput, value: newTitle, onChange: (e) => setNewTitle(e.target.value), placeholder: "Ej: Standup, Visita cliente\u2026", autoFocus: true })] })), _jsxs("div", { className: styles.timeRow, children: [_jsxs("div", { children: [_jsx("div", { className: styles.popoverLabel, children: "Inicio" }), _jsx("input", { type: "time", className: styles.popoverInput, value: newStart, onChange: (e) => setNewStart(e.target.value) })] }), _jsx("span", { className: styles.timeDash, children: "\u2013" }), _jsxs("div", { children: [_jsx("div", { className: styles.popoverLabel, children: "Fin" }), _jsx("input", { type: "time", className: styles.popoverInput, value: newEnd, onChange: (e) => setNewEnd(e.target.value) })] })] }), _jsxs("button", { type: "button", className: styles.createBtn, style: { backgroundColor: CATEGORY_COLORS[newCategory] }, onClick: handleCreate, children: [_jsx(AddIcon, { size: 16 }), "Agregar bloque"] })] })] }));
263
- };
264
- // ------------------------------------------------------------------
265
- // Render
266
- // ------------------------------------------------------------------
267
- return (_jsxs("div", { className: styles.container, style: { width: widthStyle }, children: [_jsxs("div", { className: styles.toolbar, children: [_jsxs("div", { className: styles.viewSwitcher, children: [_jsx("button", { type: "button", className: `${styles.viewBtn} ${viewMode === 'day' ? styles.viewBtnActive : ''}`, onClick: () => setViewMode('day'), title: "D\u00EDa", children: _jsx(ViewDayIcon, { size: 18 }) }), _jsx("button", { type: "button", className: `${styles.viewBtn} ${viewMode === 'week' ? styles.viewBtnActive : ''}`, onClick: () => setViewMode('week'), title: "Semana", children: _jsx(ViewWeekIcon, { size: 18 }) }), _jsx("button", { type: "button", className: `${styles.viewBtn} ${viewMode === 'month' ? styles.viewBtnActive : ''}`, onClick: () => setViewMode('month'), title: "Mes", children: _jsx(CalendarIcon, { size: 18 }) })] }), _jsxs("div", { className: styles.navGroup, children: [_jsx("button", { type: "button", className: styles.navBtn, onClick: prev, children: _jsx(ArrowLeftIcon, { size: 20 }) }), _jsx("span", { className: styles.navLabel, children: navLabel() }), _jsx("button", { type: "button", className: styles.navBtn, onClick: next, children: _jsx(ArrowRightIcon, { size: 20 }) })] }), events.length > 0 && (_jsxs("span", { className: styles.eventBadge, children: [events.length, " evento", events.length !== 1 ? 's' : ''] })), !isReadOnly && viewMode !== 'month' && (_jsx("span", { className: styles.hintText, children: "Clic en el grid para agregar" }))] }), _jsx("div", { className: styles.calendarCard, children: viewMode === 'month' ? renderMonth() : renderTimeGrid() }), !isReadOnly && renderCreatePopover()] }));
29
+ return (_jsx("div", { ref: ref, style: cssVars, children: _jsx(ErkCalendar, { value: value, onChange: onChange, defaultView: step.defaultView, enabledViews: step.enabledViews, allowedCategories: step.allowedCategories, eventTemplates: step.eventTemplates, showWeekends: step.showWeekends, minTime: step.minTime, maxTime: step.maxTime, allowRecurring: step.allowRecurring, timezone: step.timezone, readOnly: isReadOnly, error: error?.message }) }));
268
30
  }
269
31
  export default MaterialCalendarStep;
@@ -21,8 +21,11 @@ export interface ErkBookingPickerProps {
21
21
  error?: string;
22
22
  }
23
23
  /**
24
- * Selector de cita reutilizable en una sola vista (persona · día · hora), con panel
25
- * de resumen a la izquierda y barra de confirmación. Réplica del diseño del mockup.
24
+ * Selector de cita: una tarjeta de entrada ("Agendar cita") abre el wizard en un modal —
25
+ * una decisión por pantalla (persona día hora confirmar), con panel izquierdo de
26
+ * resumen que se va llenando, breadcrumb navegable arriba y barra inferior Atrás / Siguiente.
27
+ * En el paso final el panel derecho desaparece y el resumen se expande en una columna
28
+ * centrada (cross-fade corto). Confirmar cierra el modal y deja el resumen compacto.
26
29
  */
27
30
  declare function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bufferMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, collectContact, fetchSlots, readOnly, error, }: ErkBookingPickerProps): JSX.Element;
28
31
  export default ErkBookingPicker;
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { Dialog } from '@mui/material';
3
4
  import { addDays, addMonths, format, isBefore, isSameDay, isSameMonth, startOfDay, startOfMonth, startOfWeek, } from 'date-fns';
4
5
  import { es } from 'date-fns/locale';
5
6
  import { BookingMode, PersonSelectionMode, } from '../../@Types/BookingFormStep';
7
+ import ErkButton from '../ErkButton/ErkButton';
6
8
  import ErkTextField from '../ErkTextField/ErkTextField';
7
9
  import styles from './ErkBookingPicker.module.css';
8
10
  const WEEK_STARTS_ON = 1;
@@ -12,6 +14,26 @@ const MODE_LABEL = {
12
14
  [BookingMode.VIRTUAL]: 'Virtual',
13
15
  [BookingMode.HYBRID]: 'Híbrida',
14
16
  };
17
+ const STEP_LABEL = {
18
+ person: 'Persona',
19
+ day: 'Día',
20
+ hour: 'Hora',
21
+ confirm: 'Confirmar',
22
+ };
23
+ const STEP_HINT = {
24
+ person: 'Elige con quién agendar',
25
+ day: 'Elige un día',
26
+ hour: 'Elige una hora libre',
27
+ confirm: 'Confirma tu cita',
28
+ };
29
+ /** Duración del fade al reestructurarse el layout (entrar/salir del paso final). */
30
+ const XFADE_MS = 160;
31
+ /** El CTA de confirmar usa el acento del form (secondaryContrastColor vía --booking-accent). */
32
+ const ACCENT_SX = {
33
+ backgroundColor: 'var(--booking-accent, #ee6c4d)',
34
+ '&:hover': { backgroundColor: 'var(--booking-accent, #ee6c4d)', filter: 'brightness(0.95)' },
35
+ '&:disabled': { backgroundColor: 'var(--booking-accent, #ee6c4d)', color: '#fff' },
36
+ };
15
37
  function fmtDate(date) {
16
38
  return format(date, 'yyyy-MM-dd');
17
39
  }
@@ -31,22 +53,31 @@ function capitalize(text) {
31
53
  return text.charAt(0).toUpperCase() + text.slice(1);
32
54
  }
33
55
  /**
34
- * Selector de cita reutilizable en una sola vista (persona · día · hora), con panel
35
- * de resumen a la izquierda y barra de confirmación. Réplica del diseño del mockup.
56
+ * Selector de cita: una tarjeta de entrada ("Agendar cita") abre el wizard en un modal —
57
+ * una decisión por pantalla (persona día hora confirmar), con panel izquierdo de
58
+ * resumen que se va llenando, breadcrumb navegable arriba y barra inferior Atrás / Siguiente.
59
+ * En el paso final el panel derecho desaparece y el resumen se expande en una columna
60
+ * centrada (cross-fade corto). Confirmar cierra el modal y deja el resumen compacto.
36
61
  */
37
62
  function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bufferMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, collectContact = false, fetchSlots, readOnly = false, error, }) {
38
63
  const needsPerson = !!personSelectionMode && personSelectionMode !== PersonSelectionMode.NONE;
39
64
  const personsOptional = personSelectionMode === PersonSelectionMode.OPTIONAL;
40
65
  const today = startOfDay(new Date());
66
+ const wizardKeys = useMemo(() => (needsPerson ? ['person', 'day', 'hour', 'confirm'] : ['day', 'hour', 'confirm']), [needsPerson]);
67
+ const finalIdx = wizardKeys.length - 1;
68
+ const [open, setOpen] = useState(false);
69
+ const [wizStep, setWizStep] = useState(0);
70
+ const [xfade, setXfade] = useState(false);
41
71
  const [person, setPerson] = useState(undefined);
42
72
  const [personTouched, setPersonTouched] = useState(false);
43
- const [date, setDate] = useState(today);
73
+ const [date, setDate] = useState(undefined);
44
74
  const [viewMonth, setViewMonth] = useState(() => startOfMonth(today));
45
75
  const [slot, setSlot] = useState(undefined);
46
76
  const [slots, setSlots] = useState([]);
47
77
  const [loading, setLoading] = useState(false);
48
78
  const [contactName, setContactName] = useState('');
49
79
  const [contactEmail, setContactEmail] = useState('');
80
+ const reduceMotion = useMemo(() => typeof window !== 'undefined' && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches, []);
50
81
  const monthCells = useMemo(() => {
51
82
  const first = startOfWeek(startOfMonth(viewMonth), { weekStartsOn: WEEK_STARTS_ON });
52
83
  return Array.from({ length: 42 }, (_, i) => addDays(first, i));
@@ -67,15 +98,41 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
67
98
  }
68
99
  }, [fetchSlots]);
69
100
  useEffect(() => {
70
- if (value)
71
- return;
72
- if (needsPerson && !personsOptional && !personTouched)
101
+ if (value || !date)
73
102
  return;
74
103
  void loadSlots(date, person?.id);
75
- }, [value, needsPerson, personsOptional, personTouched, date, person, loadSlots]);
104
+ }, [value, date, person, loadSlots]);
105
+ // Al entrar/salir del paso final el layout se reestructura; el reacomodo
106
+ // ocurre mientras la tarjeta está invisible (fade corto en dos fases).
107
+ const goToStep = (target) => {
108
+ const crossesFinal = (wizStep === finalIdx) !== (target === finalIdx);
109
+ if (!crossesFinal || reduceMotion) {
110
+ setWizStep(target);
111
+ return;
112
+ }
113
+ setXfade(true);
114
+ window.setTimeout(() => {
115
+ setWizStep(target);
116
+ requestAnimationFrame(() => requestAnimationFrame(() => setXfade(false)));
117
+ }, XFADE_MS);
118
+ };
119
+ const currentKey = wizardKeys[wizStep];
120
+ const canAdvance = (() => {
121
+ switch (currentKey) {
122
+ case 'person':
123
+ return personsOptional ? personTouched : !!person;
124
+ case 'day':
125
+ return !!date;
126
+ case 'hour':
127
+ return !!slot;
128
+ default:
129
+ return true;
130
+ }
131
+ })();
76
132
  const confirm = () => {
77
- if (!slot)
133
+ if (!slot || !date)
78
134
  return;
135
+ setOpen(false);
79
136
  onChange({
80
137
  type: 'BOOKING_VALUE',
81
138
  slotId: slot.id,
@@ -91,8 +148,10 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
91
148
  };
92
149
  const reset = () => {
93
150
  onChange(null);
151
+ setWizStep(0);
94
152
  setPerson(undefined);
95
153
  setPersonTouched(false);
154
+ setDate(undefined);
96
155
  setSlot(undefined);
97
156
  setContactName('');
98
157
  setContactEmail('');
@@ -102,9 +161,9 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
102
161
  setPersonTouched(true);
103
162
  setSlot(undefined);
104
163
  };
105
- // Read-only / already booked → compact summary
164
+ // Read-only / already booked → compact summary with the same card aesthetic
106
165
  if (readOnly || value) {
107
- return (_jsx("div", { className: styles.container, children: _jsxs("div", { className: styles.booked, children: [_jsx("div", { className: styles.bookedIcon, children: "\u2713" }), _jsxs("div", { className: styles.bookedInfo, children: [_jsx("span", { className: styles.bookedLabel, children: label }), _jsx("span", { className: styles.bookedMain, children: value
166
+ return (_jsx("div", { className: styles.container, children: _jsxs("div", { className: styles.entry, children: [_jsx("div", { className: styles.bookedIcon, children: "\u2713" }), _jsxs("div", { className: styles.entryInfo, children: [_jsx("span", { className: styles.entryLabel, children: label }), _jsx("span", { className: styles.bookedMain, children: value
108
167
  ? [
109
168
  value.personName,
110
169
  capitalize(longDate(new Date(value.date + 'T00:00:00'))),
@@ -112,38 +171,53 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
112
171
  ]
113
172
  .filter(Boolean)
114
173
  .join(' · ')
115
- : 'Sin cita' })] }), value && !readOnly && (_jsx("button", { type: "button", className: styles.changeBtn, onClick: reset, children: "Cambiar" }))] }) }));
174
+ : 'Sin cita' })] }), value && !readOnly && _jsx(ErkButton, { text: "Cambiar", variant: "outlined", onClick: reset })] }) }));
116
175
  }
117
- // Stepper state
118
- const steps = [...(needsPerson ? ['Persona'] : []), 'Día y hora', 'Confirmar'];
119
- const current = needsPerson && !personTouched ? 0 : slot ? steps.length - 1 : needsPerson ? 1 : 0;
120
- return (_jsxs("div", { className: styles.container, children: [_jsxs("div", { className: styles.card, children: [_jsxs("div", { className: styles.book, children: [_jsxs("aside", { className: styles.who, children: [_jsxs("div", { className: styles.brand, children: [_jsx("div", { className: styles.avatar, children: person ? initials(person.name) : '📅' }), _jsxs("div", { children: [_jsx("h2", { className: styles.brandName, children: person?.name ?? label }), _jsx("p", { className: styles.brandRole, children: person?.role ?? description ?? 'Agenda tu cita' }), person && (_jsx("button", { type: "button", className: styles.change, onClick: () => pickPerson(undefined), children: "Cambiar persona" }))] })] }), _jsxs("div", { className: styles.meta, children: [_jsx("span", { className: styles.metaLbl, children: "Detalles" }), _jsxs("div", { className: styles.row, children: [_jsx("span", { className: styles.ic, children: "\u25F7" }), durationMinutes, " min", bufferMinutes ? ` · buffer ${bufferMinutes} min` : ''] }), bookingMode && (_jsxs("div", { className: styles.row, children: [_jsx("span", { className: styles.ic, children: "\u25F1" }), _jsx("span", { className: styles.pill, children: MODE_LABEL[bookingMode] })] }))] }), !!temporaryLockMinutes && (_jsxs("div", { className: styles.lock, children: ["\u23F3 Reservaremos el horario ", _jsxs("b", { children: [temporaryLockMinutes, " min"] }), " mientras confirmas."] }))] }), _jsxs("div", { className: styles.right, children: [_jsx("div", { className: styles.stepper, children: steps.map((s, i) => (_jsxs("span", { className: styles.stepGroup, children: [_jsxs("span", { className: i < current
121
- ? `${styles.stepItem} ${styles.stepDone}`
122
- : i === current
123
- ? `${styles.stepItem} ${styles.stepOn}`
124
- : styles.stepItem, children: [_jsx("span", { className: styles.stepNum, children: i < current ? '' : i + 1 }), s] }), i < steps.length - 1 && _jsx("span", { className: styles.stepSep })] }, s))) }), needsPerson && (_jsxs(_Fragment, { children: [_jsx("p", { className: styles.blockLbl, children: "\u00BFCon qui\u00E9n quieres agendar?" }), _jsxs("div", { className: styles.persons, children: [(persons ?? []).map((p) => (_jsxs("button", { type: "button", className: person?.id === p.id ? `${styles.pcard} ${styles.pcardOn}` : styles.pcard, onClick: () => pickPerson(p), children: [_jsx("span", { className: styles.pav, children: initials(p.name) }), _jsxs("span", { className: styles.pinfo, children: [_jsx("span", { className: styles.pn, children: p.name }), p.role && _jsx("span", { className: styles.pr, children: p.role })] })] }, p.id))), personsOptional && (_jsxs("button", { type: "button", className: personTouched && !person
125
- ? `${styles.pcard} ${styles.pcardAny} ${styles.pcardOn}`
126
- : `${styles.pcard} ${styles.pcardAny}`, onClick: () => pickPerson(undefined), children: [_jsx("span", { className: styles.pav, children: "\u2726" }), _jsxs("span", { className: styles.pinfo, children: [_jsx("span", { className: styles.pn, children: "Cualquiera" }), _jsx("span", { className: styles.pr, children: "El sistema asigna" })] })] }))] })] })), _jsxs("div", { className: styles.pick, children: [_jsxs("div", { children: [_jsx("p", { className: styles.blockLbl, children: "Elige un d\u00EDa" }), _jsxs("div", { className: styles.monthHead, children: [_jsx("span", { className: styles.monthTitle, children: capitalize(format(viewMonth, 'MMMM yyyy', { locale: es })) }), _jsxs("span", { className: styles.monthNav, children: [_jsx("button", { type: "button", onClick: () => setViewMonth((m) => addMonths(m, -1)), children: "\u2039" }), _jsx("button", { type: "button", onClick: () => setViewMonth((m) => addMonths(m, 1)), children: "\u203A" })] })] }), _jsxs("div", { className: styles.mgrid, children: [DOW.map((d, i) => (_jsx("span", { className: styles.mdow, children: d }, i))), monthCells.map((d) => {
127
- const outside = !isSameMonth(d, viewMonth);
128
- const past = isBefore(d, today);
129
- const selectable = !outside && !past;
130
- const cls = isSameDay(d, date)
131
- ? `${styles.mday} ${styles.mdaySel}`
132
- : selectable
133
- ? `${styles.mday} ${styles.mdayFree}`
134
- : `${styles.mday} ${styles.mdayNone}`;
135
- return (_jsx("button", { type: "button", disabled: !selectable, className: cls, onClick: () => {
136
- setDate(d);
137
- setSlot(undefined);
138
- }, children: format(d, 'd') }, d.toISOString()));
139
- })] })] }), _jsxs("div", { children: [_jsx("p", { className: styles.blockLbl, children: "Horarios libres" }), _jsxs("div", { className: styles.slotsHead, children: [_jsx("b", { children: capitalize(longDate(date)) }), " \u00B7 ", durationMinutes, " min"] }), loading ? (_jsx("div", { className: styles.empty, children: "Buscando horarios\u2026" })) : slots.length === 0 ? (_jsx("div", { className: styles.empty, children: "No hay horarios disponibles ese d\u00EDa." })) : (_jsx("div", { className: styles.slots, children: slots.map((s) => (_jsx("button", { type: "button", disabled: !s.available, className: slot?.id === s.id
140
- ? `${styles.slot} ${styles.slotSel}`
141
- : s.available
142
- ? styles.slot
143
- : `${styles.slot} ${styles.slotTaken}`, onClick: () => s.available && setSlot(s), children: s.startTime }, s.id))) }))] })] })] })] }), _jsxs("div", { className: styles.confirm, children: [_jsx("div", { className: styles.selSummary, children: slot
144
- ? [person?.name, capitalize(longDate(date)), `${slot.startTime}–${slot.endTime}`]
145
- .filter(Boolean)
146
- .join(' · ')
147
- : 'Elige día y hora' }), collectContact && (_jsxs("div", { className: styles.cfields, children: [_jsx(ErkTextField, { label: "Nombre", value: contactName, onChange: setContactName }), _jsx(ErkTextField, { label: "Correo", value: contactEmail, onChange: setContactEmail })] })), _jsx("button", { type: "button", className: styles.cta, disabled: !slot, onClick: confirm, children: "Confirmar cita" })] })] }), error && _jsx("div", { className: styles.err, children: error })] }));
176
+ const isFinal = wizStep === finalIdx;
177
+ const personLabel = person?.name ?? (personTouched && personsOptional ? 'Cualquiera' : undefined);
178
+ const dayText = date ? capitalize(longDate(date)) : undefined;
179
+ const hourText = slot ? `${slot.startTime} ${slot.endTime}` : undefined;
180
+ const summaryRows = [
181
+ ...(needsPerson ? [{ key: 'person', placeholder: 'Persona', text: personLabel }] : []),
182
+ { key: 'day', placeholder: 'Día', text: dayText },
183
+ { key: 'hour', placeholder: 'Hora', text: hourText },
184
+ ];
185
+ const navParts = [];
186
+ if (personLabel)
187
+ navParts.push(_jsx("b", { children: personLabel }, "p"));
188
+ if (dayText)
189
+ navParts.push(_jsx("span", { children: dayText }, "d"));
190
+ if (hourText)
191
+ navParts.push(_jsx("span", { className: styles.navHl, children: hourText }, "h"));
192
+ return (_jsxs("div", { className: styles.container, children: [_jsxs("div", { className: styles.entry, children: [_jsx("div", { className: styles.avatar, children: "\uD83D\uDCC5" }), _jsxs("div", { className: styles.entryInfo, children: [_jsx("span", { className: styles.entryLabel, children: label }), _jsx("span", { className: styles.entrySub, children: description ?? 'Agenda tu cita' })] }), _jsx(ErkButton, { text: "Agendar cita", onClick: () => setOpen(true) })] }), _jsx(Dialog, { open: open, onClose: () => setOpen(false), maxWidth: "md", fullWidth: true, slotProps: { paper: { style: { borderRadius: 16, background: 'transparent', boxShadow: 'none' } } }, children: _jsxs("div", { className: `${styles.card}${isFinal ? ` ${styles.cardFinal}` : ''}${xfade ? ` ${styles.xfade}` : ''}`, children: [_jsxs("div", { className: isFinal ? `${styles.book} ${styles.bookFinal}` : styles.book, children: [_jsxs("aside", { className: styles.who, children: [_jsxs("div", { className: styles.brand, children: [_jsx("div", { className: styles.avatar, children: person ? initials(person.name) : '📅' }), _jsxs("div", { children: [_jsx("h2", { className: styles.brandName, children: person?.name ?? label }), _jsx("p", { className: styles.brandRole, children: person?.role ?? description ?? 'Agenda tu cita' })] })] }), _jsxs("div", { className: styles.meta, children: [_jsxs("div", { className: styles.row, children: [_jsx("span", { className: styles.ic, children: "\u25F7" }), durationMinutes, " min", bufferMinutes ? ` · buffer ${bufferMinutes} min` : ''] }), bookingMode && (_jsxs("div", { className: styles.row, children: [_jsx("span", { className: styles.ic, children: "\u25F1" }), _jsx("span", { className: styles.pill, children: MODE_LABEL[bookingMode] })] }))] }), _jsx("div", { className: styles.sum, "aria-live": "polite", children: summaryRows.map((r) => (_jsxs("div", { className: r.text ? `${styles.srow} ${styles.srowOn}` : styles.srow, children: [_jsx("span", { className: styles.dot, children: "\u2713" }), _jsx("span", { children: r.text ?? r.placeholder })] }, r.key))) }), isFinal && collectContact && (_jsxs("div", { className: styles.cfields, children: [_jsx(ErkTextField, { label: "Nombre", value: contactName, onChange: setContactName }), _jsx(ErkTextField, { label: "Correo", value: contactEmail, onChange: setContactEmail })] })), !!temporaryLockMinutes && (_jsxs("div", { className: styles.lock, children: ["\u23F3 Reservaremos el horario ", _jsxs("b", { children: [temporaryLockMinutes, " min"] }), " mientras confirmas."] }))] }), _jsxs("div", { className: styles.right, children: [_jsx("nav", { className: styles.stepper, "aria-label": "Progreso", children: wizardKeys.map((key, i) => (_jsxs("span", { className: styles.stepGroup, children: [_jsxs("button", { type: "button", disabled: i >= wizStep, onClick: () => goToStep(i), className: i < wizStep
193
+ ? `${styles.stepItem} ${styles.stepDone}`
194
+ : i === wizStep
195
+ ? `${styles.stepItem} ${styles.stepOn}`
196
+ : styles.stepItem, children: [_jsx("span", { className: styles.stepNum, children: i < wizStep ? '✓' : i + 1 }), STEP_LABEL[key]] }), i < wizardKeys.length - 1 && _jsx("span", { className: styles.stepSep })] }, key))) }), currentKey === 'person' && (_jsxs("section", { className: styles.pane, children: [_jsx("h3", { className: styles.q, children: "\u00BFCon qui\u00E9n quieres agendar?" }), _jsx("p", { className: styles.qsub, children: personsOptional
197
+ ? 'Elige una persona o deja que el sistema asigne a quien esté disponible.'
198
+ : 'Elige la persona con la que quieres tu cita.' }), _jsxs("div", { className: styles.persons, children: [(persons ?? []).map((p) => (_jsxs("button", { type: "button", className: person?.id === p.id ? `${styles.pcard} ${styles.pcardOn}` : styles.pcard, onClick: () => pickPerson(p), children: [_jsx("span", { className: styles.pav, children: initials(p.name) }), _jsxs("span", { className: styles.pinfo, children: [_jsx("span", { className: styles.pn, children: p.name }), p.role && _jsx("span", { className: styles.pr, children: p.role })] }), _jsx("span", { className: styles.chk, children: "\u2713" })] }, p.id))), personsOptional && (_jsxs("button", { type: "button", className: personTouched && !person
199
+ ? `${styles.pcard} ${styles.pcardAny} ${styles.pcardOn}`
200
+ : `${styles.pcard} ${styles.pcardAny}`, onClick: () => pickPerson(undefined), children: [_jsx("span", { className: styles.pav, children: "\u2726" }), _jsxs("span", { className: styles.pinfo, children: [_jsx("span", { className: styles.pn, children: "Cualquiera" }), _jsx("span", { className: styles.pr, children: "El sistema asigna" })] }), _jsx("span", { className: styles.chk, children: "\u2713" })] }))] })] }, "person")), currentKey === 'day' && (_jsxs("section", { className: styles.pane, children: [_jsx("h3", { className: styles.q, children: "Elige un d\u00EDa" }), _jsx("p", { className: styles.qsub, children: "Solo se muestran fechas a partir de hoy." }), _jsxs("div", { className: styles.monthWrap, children: [_jsxs("div", { className: styles.monthHead, children: [_jsx("span", { className: styles.monthTitle, children: capitalize(format(viewMonth, 'MMMM yyyy', { locale: es })) }), _jsxs("span", { className: styles.monthNav, children: [_jsx("button", { type: "button", "aria-label": "Mes anterior", onClick: () => setViewMonth((m) => addMonths(m, -1)), children: "\u2039" }), _jsx("button", { type: "button", "aria-label": "Mes siguiente", onClick: () => setViewMonth((m) => addMonths(m, 1)), children: "\u203A" })] })] }), _jsxs("div", { className: styles.mgrid, children: [DOW.map((d, i) => (_jsx("span", { className: styles.mdow, children: d }, i))), monthCells.map((d) => {
201
+ const outside = !isSameMonth(d, viewMonth);
202
+ const past = isBefore(d, today);
203
+ const selectable = !outside && !past;
204
+ const cls = date && isSameDay(d, date)
205
+ ? `${styles.mday} ${styles.mdaySel}`
206
+ : selectable
207
+ ? `${styles.mday} ${styles.mdayFree}`
208
+ : `${styles.mday} ${styles.mdayNone}`;
209
+ return (_jsx("button", { type: "button", disabled: !selectable, className: cls, onClick: () => {
210
+ setDate(d);
211
+ setSlot(undefined);
212
+ }, children: format(d, 'd') }, d.toISOString()));
213
+ })] })] })] }, "day")), currentKey === 'hour' && (_jsxs("section", { className: styles.pane, children: [_jsx("h3", { className: styles.q, children: "Elige una hora" }), _jsxs("p", { className: styles.qsub, children: [dayText, " \u00B7 citas de ", durationMinutes, " min"] }), loading ? (_jsx("div", { className: styles.empty, children: "Buscando horarios\u2026" })) : slots.length === 0 ? (_jsx("div", { className: styles.empty, children: "No hay horarios disponibles ese d\u00EDa." })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.slots, children: slots.map((s) => (_jsx("button", { type: "button", disabled: !s.available, className: slot?.id === s.id
214
+ ? `${styles.slot} ${styles.slotSel}`
215
+ : s.available
216
+ ? styles.slot
217
+ : `${styles.slot} ${styles.slotTaken}`, onClick: () => s.available && setSlot(s), children: s.startTime }, s.id))) }), _jsx("p", { className: styles.takenNote, children: "Tachados = ocupados por otro evento o cita." })] }))] }, "hour")), currentKey === 'confirm' && (_jsxs("section", { className: `${styles.pane} ${styles.paneFinal}`, children: [_jsx("h3", { className: styles.q, children: "Revisa y confirma tu cita" }), _jsx("p", { className: styles.qsub, children: collectContact
218
+ ? 'Si todo está bien, completa tus datos y confirma.'
219
+ : 'Si todo está bien, confirma tu cita.' })] }, "confirm"))] })] }), _jsxs("div", { className: styles.navbar, children: [_jsx("div", { className: styles.navSummary, children: isFinal || !canAdvance || navParts.length === 0
220
+ ? STEP_HINT[currentKey]
221
+ : navParts.reduce((acc, part, i) => (i === 0 ? [part] : [...acc, _jsx("span", { children: " \u00B7 " }, `s${i}`), part]), []) }), wizStep > 0 && (_jsx(ErkButton, { text: "Atr\u00E1s", variant: "outlined", onClick: () => goToStep(wizStep - 1) })), isFinal ? (_jsx(ErkButton, { text: "Confirmar cita", sx: ACCENT_SX, disabled: !slot, onClick: confirm })) : (_jsx(ErkButton, { text: "Siguiente", disabled: !canAdvance, onClick: () => goToStep(wizStep + 1) }))] })] }) }), error && _jsx("div", { className: styles.err, children: error })] }));
148
222
  }
149
223
  export default ErkBookingPicker;