@arquimedes.co/eureka-forms 3.0.72 → 3.0.73

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.
@@ -1,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
3
3
  import { Dialog } from '@mui/material';
4
4
  import { addDays, addMonths, format, isBefore, isSameDay, isSameMonth, startOfDay, startOfMonth, startOfWeek, } from 'date-fns';
5
5
  import { es } from 'date-fns/locale';
@@ -7,51 +7,92 @@ import { BookingMode, PersonSelectionMode, } from '../../@Types/BookingFormStep'
7
7
  import ErkButton from '../ErkButton/ErkButton';
8
8
  import ErkTextField from '../ErkTextField/ErkTextField';
9
9
  import { browserTimeZone, clockIn, isValidEmail, nonEmpty, zoneLabel } from '../../Services/BookingService';
10
+ import ArrowLeftIcon from '../../Icons/ArrowLeftIcon';
11
+ import ArrowRightIcon from '../../Icons/ArrowRightIcon';
12
+ import CalendarIcon from '../../Icons/CalendarIcon';
13
+ import CheckCircleIcon from '../../Icons/CheckCircleIcon';
14
+ import CheckIcon from '../../Icons/CheckIcon';
15
+ import ClockIcon from '../../Icons/ClockIcon';
16
+ import CloseIcon from '../../Icons/CloseIcon';
17
+ import EmailIcon from '../../Icons/EmailIcon';
18
+ import GroupIcon from '../../Icons/GroupIcon';
19
+ import InfoIcon from '../../Icons/InfoIcon';
20
+ import LocationIcon from '../../Icons/LocationIcon';
21
+ import PersonIcon from '../../Icons/PersonIcon';
22
+ import VideoIcon from '../../Icons/VideoIcon';
10
23
  import styles from './ErkBookingPicker.module.css';
11
24
  const WEEK_STARTS_ON = 1;
12
- const DOW = ['L', 'M', 'M', 'J', 'V', 'S', 'D'];
25
+ const DOW = ['L', 'M', 'X', 'J', 'V', 'S', 'D'];
13
26
  const MODE_LABEL = {
14
27
  [BookingMode.IN_PERSON]: 'Presencial',
15
28
  [BookingMode.VIRTUAL]: 'Virtual',
16
29
  [BookingMode.HYBRID]: 'Híbrida',
17
30
  };
31
+ /** Cómo se nombra la modalidad en el resumen del paso final. */
32
+ const MODE_SUMMARY = {
33
+ [BookingMode.IN_PERSON]: { title: 'Presencial' },
34
+ [BookingMode.VIRTUAL]: { title: 'Reunión virtual', sub: 'Recibirás el enlace en tu correo' },
35
+ [BookingMode.HYBRID]: { title: 'Presencial o virtual' },
36
+ };
18
37
  const STEP_LABEL = {
19
- type: 'Tipo',
38
+ type: 'Tipo de atención',
20
39
  person: 'Persona',
21
- day: 'Día',
22
- hour: 'Hora',
23
- confirm: 'Reservar',
40
+ date: 'Día y hora',
24
41
  };
42
+ /** Lo que falta en el paso en pantalla, en la barra inferior. */
25
43
  const STEP_HINT = {
26
- type: 'Elige qué necesitas',
27
- person: 'Elige con quién agendar',
28
- day: 'Elige un día',
29
- hour: 'Elige una hora libre',
30
- confirm: 'Reserva tu horario',
44
+ type: 'Elige qué necesitas.',
45
+ person: 'Elige con quién agendar.',
46
+ date: 'Elige un día.',
47
+ };
48
+ /** Lo que sigue, cuando el paso en pantalla ya está completo. */
49
+ const NEXT_HINT = {
50
+ person: 'Ahora elige con quién agendar.',
51
+ date: 'Ahora elige el día y la hora.',
31
52
  };
32
53
  const GENERIC_SLOTS_ERROR = 'No pudimos cargar los horarios de este día.';
33
54
  const GENERIC_DAYS_ERROR = 'No pudimos cargar los días disponibles.';
34
55
  const NO_EMAIL_HINT = 'Sin correo no podremos enviarte la confirmación.';
35
- /** Duración del fade al reestructurarse el layout (entrar/salir del paso final). */
36
- const XFADE_MS = 160;
56
+ /** Los botones de la barra inferior miden 40px, como en el diseño. */
57
+ const NAV_SX = { height: 40, padding: '0 18px' };
37
58
  /** El CTA de confirmar usa el acento del form (secondaryContrastColor vía --booking-accent). */
38
59
  const ACCENT_SX = {
60
+ ...NAV_SX,
61
+ padding: '0 22px',
39
62
  backgroundColor: 'var(--booking-accent, #ee6c4d)',
40
63
  '&:hover': { backgroundColor: 'var(--booking-accent, #ee6c4d)', filter: 'brightness(0.95)' },
41
64
  '&:disabled': { backgroundColor: 'var(--booking-accent, #ee6c4d)', color: '#fff' },
42
65
  };
66
+ /** Un botón con borde deshabilitado se queda con borde (ErkButton lo rellenaría). */
67
+ const OUTLINED_DISABLED = { '&:disabled': { backgroundColor: 'transparent', color: 'primary.main' } };
68
+ /** El modal no pone marco: la tarjeta es el marco, del mismo tamaño en todos los pasos. */
69
+ const PAPER_SX = {
70
+ margin: '16px',
71
+ maxWidth: 'none',
72
+ maxHeight: 'none',
73
+ background: 'transparent',
74
+ boxShadow: 'none',
75
+ borderRadius: '16px',
76
+ overflow: 'visible',
77
+ '@media (max-width: 760px)': { margin: 0, width: '100%', borderRadius: 0 },
78
+ };
43
79
  const BOOKING_COPY = {
44
- open: 'Agendar cita',
80
+ open: 'Elegir horario',
45
81
  confirm: 'Reservar',
46
82
  confirming: 'Reservando…',
47
83
  title: 'Revisa y reserva tu horario',
48
- note: 'La cita se confirma cuando envíes el formulario.',
84
+ note: 'La cita queda confirmada cuando envíes el formulario.',
49
85
  };
50
86
  function fmtDate(date) {
51
87
  return format(date, 'yyyy-MM-dd');
52
88
  }
53
- function longDate(date) {
54
- return format(date, 'EEEE d MMM', { locale: es });
89
+ /** "Viernes 30 de octubre". */
90
+ function longDay(date) {
91
+ return capitalize(format(date, "EEEE d 'de' MMMM", { locale: es }));
92
+ }
93
+ /** "Vie 30 oct". */
94
+ function shortDay(date) {
95
+ return capitalize(format(date, 'EEE d MMM', { locale: es }));
55
96
  }
56
97
  function fullDate(date) {
57
98
  return format(date, "EEEE d 'de' MMMM 'de' yyyy", { locale: es });
@@ -59,15 +100,6 @@ function fullDate(date) {
59
100
  function errorText(error, fallback) {
60
101
  return error instanceof Error && error.message ? error.message : fallback;
61
102
  }
62
- /** Mañana hasta el mediodía, tarde desde ahí, en el reloj en que se muestran las horas. */
63
- function groupSlots(slots) {
64
- const morning = slots.filter((s) => s.startTime < '12:00');
65
- const afternoon = slots.filter((s) => s.startTime >= '12:00');
66
- return [
67
- { label: 'Mañana', slots: morning },
68
- { label: 'Tarde', slots: afternoon },
69
- ].filter((group) => group.slots.length > 0);
70
- }
71
103
  function initials(name) {
72
104
  return name
73
105
  .split(' ')
@@ -80,15 +112,52 @@ function initials(name) {
80
112
  function capitalize(text) {
81
113
  return text.charAt(0).toUpperCase() + text.slice(1);
82
114
  }
115
+ /** The theme the host sets as CSS variables; the modal lives in a portal and has to carry them itself. */
116
+ const THEME_VARS = [
117
+ '--eureka-primary',
118
+ '--eureka-text',
119
+ '--eureka-outline',
120
+ '--eureka-bg',
121
+ '--eureka-description',
122
+ '--eureka-error',
123
+ '--booking-accent',
124
+ ];
125
+ function themeOf(element) {
126
+ if (!element)
127
+ return {};
128
+ const computed = window.getComputedStyle(element);
129
+ const vars = {};
130
+ for (const name of THEME_VARS) {
131
+ const value = computed.getPropertyValue(name).trim();
132
+ if (value)
133
+ vars[name] = value;
134
+ }
135
+ return vars;
136
+ }
137
+ function cx(...names) {
138
+ return names.filter(Boolean).join(' ');
139
+ }
140
+ function ModeIcon({ mode, size }) {
141
+ return mode === BookingMode.IN_PERSON ? _jsx(LocationIcon, { size: size }) : _jsx(VideoIcon, { size: size });
142
+ }
143
+ /** Una opción de los pasos de tipo y de persona. */
144
+ function OptionCard({ selected, dashed = false, avatar, title, subtitle, aside, onClick, }) {
145
+ return (_jsxs("button", { type: "button", "aria-pressed": selected, className: cx(styles.option, dashed && styles.optionAny, selected && styles.optionOn), onClick: onClick, children: [avatar, _jsxs("span", { className: styles.optionText, children: [_jsx("span", { className: styles.optionTitle, children: title }), subtitle && _jsx("span", { className: styles.optionSub, children: subtitle })] }), aside && _jsx("span", { className: styles.optionAside, children: aside }), selected && _jsx(CheckCircleIcon, { size: 20, className: styles.optionCheck })] }));
146
+ }
147
+ /** Una fila del resumen "Tu cita" del paso final. */
148
+ function SummaryRow({ icon, title, sub, change, onChange, }) {
149
+ return (_jsxs("div", { className: styles.sumRow, children: [_jsx("span", { className: styles.sumIcon, children: icon }), _jsxs("span", { className: styles.sumText, children: [_jsx("span", { className: styles.sumTitle, children: title }), sub && _jsx("span", { className: styles.sumSub, children: sub })] }), onChange && (_jsx("button", { type: "button", className: styles.sumChange, "aria-label": change, onClick: onChange, children: "Cambiar" }))] }));
150
+ }
83
151
  /**
84
- * Selector de cita: una tarjeta de entrada ("Agendar cita") abre el wizard en un modal —
85
- * una decisión por pantalla (persona → día → hora → confirmar), con panel izquierdo de
86
- * resumen que se va llenando, breadcrumb navegable arriba y barra inferior Atrás / Siguiente.
87
- * En el paso final el panel derecho desaparece y el resumen se expande en una columna
88
- * centrada (cross-fade corto). "Reservar" retira el horario de la oferta por unos minutos, cierra el
89
- * modal y deja el resumen compacto; la cita se confirma cuando se envía el formulario.
152
+ * Selector de cita. En el formulario es un control suelto como el de archivos: etiqueta, descripción y
153
+ * el botón "Elegir horario"; con un horario elegido, una fila con el resumen y "Cambiar".
154
+ *
155
+ * El botón abre el wizard en un modal de tamaño fijo — una decisión por pantalla ([tipo] → [persona] →
156
+ * día y hora → reservar), con stepper navegable arriba y barra inferior con lo elegido y Atrás /
157
+ * Siguiente. "Reservar" retira el horario de la oferta por unos minutos, cierra el modal y deja el
158
+ * resumen; la cita se confirma cuando se envía el formulario.
90
159
  */
91
- function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, eventTemplates, timeZone, knownContact, askEmail = false, lapsed, fetchSlots, fetchDays, holdSlot, disabled = false, withoutContact = false, copy: copyOverrides, readOnly = false, error, loadError, onRetryLoad, }) {
160
+ function ErkBookingPicker({ value, onChange, label, description, required = false, durationMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, eventTemplates, timeZone, knownContact, askEmail = false, lapsed, fetchSlots, fetchDays, holdSlot, disabled = false, withoutContact = false, copy: copyOverrides, readOnly = false, error, loadError, onRetryLoad, }) {
92
161
  const copy = { ...BOOKING_COPY, ...copyOverrides };
93
162
  const needsPerson = !!personSelectionMode && personSelectionMode !== PersonSelectionMode.NONE;
94
163
  const personsOptional = personSelectionMode === PersonSelectionMode.OPTIONAL;
@@ -97,12 +166,14 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
97
166
  const wizardKeys = useMemo(() => [
98
167
  ...(needsType ? ['type'] : []),
99
168
  ...(needsPerson ? ['person'] : []),
100
- ...['day', 'hour', 'confirm'],
169
+ ...['date', 'confirm'],
101
170
  ], [needsType, needsPerson]);
102
171
  const finalIdx = wizardKeys.length - 1;
172
+ const titleId = useId();
103
173
  const [open, setOpen] = useState(false);
174
+ const containerRef = useRef(null);
175
+ const [theme, setTheme] = useState({});
104
176
  const [wizStep, setWizStep] = useState(0);
105
- const [xfade, setXfade] = useState(false);
106
177
  const [eventTemplate, setEventTemplate] = useState(undefined);
107
178
  const [person, setPerson] = useState(undefined);
108
179
  const [personTouched, setPersonTouched] = useState(false);
@@ -123,14 +194,14 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
123
194
  const [holdError, setHoldError] = useState(undefined);
124
195
  /** Cada pedido de horas lleva un número; solo el último puede escribir el estado (A5.a). */
125
196
  const slotsRequest = useRef(0);
126
- // A hold lost after it was made sends the requester back to the hours, with the reason on screen.
197
+ // A hold lost after it was made sends the requester back to the day and hour, with the reason on screen.
127
198
  const [seenLapsed, setSeenLapsed] = useState(lapsed);
128
199
  if (lapsed !== seenLapsed) {
129
200
  setSeenLapsed(lapsed);
130
201
  if (lapsed) {
131
202
  setSlot(undefined);
132
203
  setHoldError(lapsed);
133
- setWizStep(Math.max(0, wizardKeys.indexOf(date ? 'hour' : 'day')));
204
+ setWizStep(Math.max(0, wizardKeys.indexOf('date')));
134
205
  }
135
206
  }
136
207
  const knownName = nonEmpty(knownContact?.name);
@@ -142,7 +213,6 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
142
213
  const askContactEmail = !withoutContact && askEmail;
143
214
  const localZone = useMemo(() => browserTimeZone(), []);
144
215
  const showLocal = !!timeZone && !!localZone && localZone !== timeZone;
145
- const reduceMotion = useMemo(() => typeof window !== 'undefined' && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches, []);
146
216
  const monthCells = useMemo(() => {
147
217
  const first = startOfWeek(startOfMonth(viewMonth), { weekStartsOn: WEEK_STARTS_ON });
148
218
  return Array.from({ length: 42 }, (_, i) => addDays(first, i));
@@ -208,20 +278,6 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
208
278
  cancelled = true;
209
279
  };
210
280
  }, [fetchDays, open, value, person, eventTemplate, needsType, daysAttempt]);
211
- // Al entrar/salir del paso final el layout se reestructura; el reacomodo
212
- // ocurre mientras la tarjeta está invisible (fade corto en dos fases).
213
- const goToStep = (target) => {
214
- const crossesFinal = (wizStep === finalIdx) !== (target === finalIdx);
215
- if (!crossesFinal || reduceMotion) {
216
- setWizStep(target);
217
- return;
218
- }
219
- setXfade(true);
220
- window.setTimeout(() => {
221
- setWizStep(target);
222
- requestAnimationFrame(() => requestAnimationFrame(() => setXfade(false)));
223
- }, XFADE_MS);
224
- };
225
281
  const currentKey = wizardKeys[wizStep];
226
282
  const canAdvance = (() => {
227
283
  switch (currentKey) {
@@ -229,10 +285,8 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
229
285
  return !!eventTemplate;
230
286
  case 'person':
231
287
  return personsOptional ? personTouched : !!person;
232
- case 'day':
233
- return !!date;
234
- case 'hour':
235
- return !!slot;
288
+ case 'date':
289
+ return !!date && !!slot;
236
290
  case 'confirm':
237
291
  return withoutContact || (nameValue.length > 0 && !emailInvalid);
238
292
  default:
@@ -272,10 +326,10 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
272
326
  onChange({ ...selection, ...held });
273
327
  }
274
328
  catch (e) {
275
- // Somebody else took the slot while the form was being finished: back to the hour, with fresh slots.
329
+ // Somebody else took the slot while the form was being finished: back to the hours, with fresh slots.
276
330
  setHoldError(e instanceof Error && e.message ? e.message : 'No pudimos reservar ese horario.');
277
331
  setSlot(undefined);
278
- goToStep(wizardKeys.indexOf('hour'));
332
+ setWizStep(wizardKeys.indexOf('date'));
279
333
  void loadSlots(date, person?.id, eventTemplate?.id);
280
334
  }
281
335
  finally {
@@ -311,89 +365,107 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
311
365
  setPersonTouched(true);
312
366
  setSlot(undefined);
313
367
  };
314
- // Read-only / already booked → compact summary with the same card aesthetic
368
+ const pickDay = (d) => {
369
+ setDate(d);
370
+ setSlot(undefined);
371
+ };
372
+ const labelText = required ? `${label} *` : label;
373
+ const head = (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.label, children: labelText }), description && _jsx("div", { className: styles.description, children: description })] }));
374
+ // Read-only / already booked → a light row with what was booked.
315
375
  if (readOnly || value) {
316
- const pending = !!value && value.status === 'PENDING' && !readOnly;
317
- 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
318
- ? [
319
- capitalize(longDate(new Date(value.date + 'T00:00:00'))),
320
- `${value.startTime}–${value.endTime}`,
321
- value.personName ? `con ${value.personName}` : undefined,
376
+ if (!value) {
377
+ return (_jsxs("div", { className: styles.container, children: [head, _jsx("div", { className: styles.note, children: "Sin cita" })] }));
378
+ }
379
+ const confirmed = readOnly || value.status === 'CONFIRMED';
380
+ const email = nonEmpty(value.contactEmail) ?? knownEmail;
381
+ const status = confirmed
382
+ ? 'Cita confirmada'
383
+ : value.lockedUntil && timeZone
384
+ ? `Reservado hasta las ${clockIn(value.lockedUntil, timeZone)}`
385
+ : 'Horario reservado';
386
+ return (_jsxs("div", { className: styles.container, children: [head, _jsxs("div", { className: styles.booked, children: [_jsx("span", { className: cx(styles.bookedIcon, confirmed && styles.bookedIconOk), children: confirmed ? _jsx(CheckCircleIcon, { size: 18 }) : _jsx(CalendarIcon, { size: 18 }) }), _jsxs("span", { className: styles.bookedText, children: [_jsxs("span", { className: styles.bookedMain, children: [longDay(new Date(value.date + 'T00:00:00')), " \u00B7 ", value.startTime, " \u2013 ", value.endTime] }), _jsx("span", { className: styles.bookedSub, children: [
387
+ value.personName,
388
+ value.eventTemplate
389
+ ? `${value.eventTemplate.name} · ${value.eventTemplate.durationMinutes} min`
390
+ : undefined,
391
+ status,
322
392
  ]
323
393
  .filter(Boolean)
324
- .join(' · ')
325
- : 'Sin cita' }), value?.eventTemplate && (_jsxs("span", { className: styles.entrySub, children: [value.eventTemplate.name, " \u00B7 ", value.eventTemplate.durationMinutes, " min"] })), pending && (_jsxs("span", { className: styles.entrySub, children: [value.lockedUntil && timeZone
326
- ? `Reservado hasta las ${clockIn(value.lockedUntil, timeZone)}. `
327
- : 'Horario reservado. ', "Env\u00EDa el formulario para confirmar la cita."] }))] }), value && !readOnly && _jsx(ErkButton, { text: "Cambiar", variant: "outlined", onClick: reset })] }) }));
394
+ .join(' · ') })] }), !readOnly && (_jsx(ErkButton, { text: "Cambiar", variant: "outlined", sx: { height: 32, minHeight: 32, padding: '0 12px', fontSize: 13 }, onClick: reset }))] }), !confirmed && !withoutContact && (_jsx("div", { className: styles.note, children: email ? (_jsxs(_Fragment, { children: ["Te confirmaremos a ", _jsx("b", { children: email }), " cuando env\u00EDes el formulario."] })) : ('Envía el formulario para confirmar la cita.') })), confirmed && value.status === 'CONFIRMED' && value.contactEmail && (_jsxs("div", { className: styles.note, children: ["Te enviamos la invitaci\u00F3n a ", value.contactEmail, "."] }))] }));
328
395
  }
329
396
  const isFinal = wizStep === finalIdx;
330
397
  const duration = eventTemplate?.durationMinutes ?? durationMinutes;
331
398
  const mode = eventTemplate?.bookingMode ?? bookingMode;
332
399
  const personLabel = person?.name ?? (personTouched && personsOptional ? 'Cualquiera' : undefined);
333
- const dayText = date ? capitalize(longDate(date)) : undefined;
334
400
  const localStart = showLocal && localZone && slot?.start ? clockIn(slot.start, localZone) : undefined;
335
- const hourText = slot
336
- ? `${slot.startTime} – ${slot.endTime}${localStart ? ` (${localStart} en tu hora)` : ''}`
337
- : undefined;
338
- const zoneText = timeZone ? `Horas en ${zoneLabel(timeZone, date ?? new Date())}` : undefined;
339
- const firstAvailable = slots.find((s) => s.available);
340
- const summaryRows = [
341
- ...(needsType
342
- ? [{ key: 'type', placeholder: 'Tipo de atención', text: eventTemplate?.name }]
343
- : []),
344
- ...(needsPerson ? [{ key: 'person', placeholder: 'Persona', text: personLabel }] : []),
345
- { key: 'day', placeholder: 'Día', text: dayText },
346
- { key: 'hour', placeholder: 'Hora', text: hourText },
401
+ const hourText = slot ? `${slot.startTime} – ${slot.endTime}` : undefined;
402
+ const zoneText = timeZone ? capitalize(zoneLabel(timeZone, date ?? new Date())) : undefined;
403
+ const contactLine = withoutContact
404
+ ? undefined
405
+ : knownEmail
406
+ ? 'email'
407
+ : askName
408
+ ? 'Te pediremos nombre y correo al reservar.'
409
+ : askContactEmail
410
+ ? 'Te pediremos el correo al reservar.'
411
+ : undefined;
412
+ const hint = (() => {
413
+ if (currentKey === 'confirm')
414
+ return undefined;
415
+ if (!canAdvance)
416
+ return currentKey === 'date' && date ? 'Elige una hora.' : STEP_HINT[currentKey];
417
+ return NEXT_HINT[wizardKeys[wizStep + 1]];
418
+ })();
419
+ const chips = [
420
+ ...(eventTemplate ? [{ key: 'type', text: eventTemplate.name }] : []),
421
+ ...(personLabel ? [{ key: 'person', text: personLabel }] : []),
422
+ ...(date ? [{ key: 'day', text: shortDay(date) }] : []),
423
+ ...(hourText ? [{ key: 'hour', text: hourText, accent: true }] : []),
347
424
  ];
348
- const navParts = [];
349
- if (eventTemplate)
350
- navParts.push(_jsx("b", { children: eventTemplate.name }, "t"));
351
- if (personLabel)
352
- navParts.push(_jsx("b", { children: personLabel }, "p"));
353
- if (dayText)
354
- navParts.push(_jsx("span", { children: dayText }, "d"));
355
- if (hourText)
356
- navParts.push(_jsx("span", { className: styles.navHl, children: hourText }, "h"));
357
- 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: copy.open, disabled: disabled, onClick: () => setOpen(true) })] }), loadError && (_jsxs("div", { className: styles.retry, role: "alert", children: [_jsx("span", { children: loadError }), onRetryLoad && _jsx(ErkButton, { text: "Reintentar", variant: "outlined", onClick: onRetryLoad })] })), lapsed && !open && (_jsx("div", { className: styles.err, role: "alert", children: lapsed })), _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" }), duration, " min"] }), mode && (_jsxs("div", { className: styles.row, children: [_jsx("span", { className: styles.ic, children: "\u25F1" }), _jsx("span", { className: styles.pill, children: MODE_LABEL[mode] })] }))] }), _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 && (askName || askContactEmail) && (_jsxs("div", { className: styles.cfields, children: [askName && (_jsx(ErkTextField, { label: "Nombre", value: contactName, onChange: setContactName, required: true })), askContactEmail && (_jsx(ErkTextField, { label: "Correo", type: "email", value: contactEmail, onChange: setContactEmail, onBlur: () => setEmailTouched(true), required: true, error: emailTouched && emailInvalid, helperText: emailTouched && contactEmail.trim() && emailInvalid
358
- ? 'Escribe un correo válido.'
359
- : NO_EMAIL_HINT }))] })), isFinal && withoutContact && _jsx("div", { className: styles.lock, children: copy.note }), isFinal && !withoutContact && (_jsxs("div", { className: styles.lock, children: ["\u23F3", ' ', temporaryLockMinutes ? (_jsxs(_Fragment, { children: ["Reservaremos el horario ", _jsxs("b", { children: [temporaryLockMinutes, " min"] }), ".", ' '] })) : ('Reservaremos el horario. '), copy.note] }))] }), _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
360
- ? `${styles.stepItem} ${styles.stepDone}`
361
- : i === wizStep
362
- ? `${styles.stepItem} ${styles.stepOn}`
363
- : styles.stepItem, children: [_jsx("span", { className: styles.stepNum, children: i < wizStep ? '✓' : i + 1 }), key === 'confirm' ? copy.confirm : STEP_LABEL[key]] }), i < wizardKeys.length - 1 && _jsx("span", { className: styles.stepSep })] }, key))) }), currentKey === 'type' && (_jsxs("section", { className: styles.pane, children: [_jsx("h3", { className: styles.q, children: "\u00BFQu\u00E9 necesitas?" }), _jsx("p", { className: styles.qsub, children: "Elige el tipo de atenci\u00F3n; de \u00E9l dependen la duraci\u00F3n y los horarios." }), _jsx("div", { className: styles.persons, children: (eventTemplates ?? []).map((t) => (_jsxs("button", { type: "button", "aria-pressed": eventTemplate?.id === t.id, className: eventTemplate?.id === t.id
364
- ? `${styles.pcard} ${styles.pcardOn}`
365
- : styles.pcard, onClick: () => pickType(t), children: [_jsxs("span", { className: styles.pinfo, children: [_jsx("span", { className: styles.pn, children: t.name }), t.description && (_jsx("span", { className: styles.pr, children: t.description }))] }), _jsxs("span", { className: styles.tdur, children: [t.durationMinutes, " min"] }), _jsx("span", { className: styles.chk, children: "\u2713" })] }, t.id))) })] }, "type")), 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
366
- ? 'Elige una persona o deja que el sistema asigne a quien esté disponible.'
367
- : '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
368
- ? `${styles.pcard} ${styles.pcardOn}`
369
- : 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
370
- ? `${styles.pcard} ${styles.pcardAny} ${styles.pcardOn}`
371
- : `${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: fetchDays
372
- ? 'Solo se pueden elegir los días con horarios disponibles.'
373
- : '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) => {
374
- const outside = !isSameMonth(d, viewMonth);
375
- const past = isBefore(d, today);
376
- const offered = !daysError && (!days || days.includes(fmtDate(d)));
377
- const selectable = !outside && !past && offered;
378
- const cls = date && isSameDay(d, date)
379
- ? `${styles.mday} ${styles.mdaySel}`
380
- : selectable
381
- ? `${styles.mday} ${styles.mdayFree}`
382
- : `${styles.mday} ${styles.mdayNone}`;
383
- return (_jsx("button", { type: "button", disabled: !selectable, "aria-label": fullDate(d), className: cls, onClick: () => {
384
- setDate(d);
385
- setSlot(undefined);
386
- }, children: format(d, 'd') }, d.toISOString()));
387
- })] })] }), daysError ? (_jsxs("div", { className: styles.retry, role: "alert", children: [_jsx("span", { children: daysError }), _jsx(ErkButton, { text: "Reintentar", variant: "outlined", onClick: () => setDaysAttempt((n) => n + 1) })] })) : fetchDays && days === undefined ? (_jsx("div", { className: styles.empty, children: "Buscando d\u00EDas con horarios\u2026" })) : (days?.length === 0 && (_jsx("div", { className: styles.empty, children: "No hay d\u00EDas con horarios disponibles." })))] }, "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 ", duration, " min"] }), holdError && (_jsx("div", { className: styles.err, role: "alert", children: holdError })), zoneText && _jsx("p", { className: styles.tz, children: zoneText }), loading ? (_jsx("div", { className: styles.empty, children: "Buscando horarios\u2026" })) : slotsError ? (_jsxs("div", { className: styles.retry, role: "alert", children: [_jsx("span", { children: slotsError }), _jsx(ErkButton, { text: "Reintentar", variant: "outlined", onClick: () => date && void loadSlots(date, person?.id, eventTemplate?.id) })] })) : slots.length === 0 ? (_jsx("div", { className: styles.empty, children: "No hay horarios disponibles ese d\u00EDa." })) : (groupSlots(slots).map((group) => (_jsxs("div", { className: styles.slotGroup, children: [_jsx("h4", { className: styles.slotGroupTitle, children: group.label }), _jsx("div", { className: styles.slots, children: group.slots.map((s) => (_jsxs("button", { type: "button", disabled: !s.available, title: `${s.startTime} – ${s.endTime}`, className: slot?.id === s.id
388
- ? `${styles.slot} ${styles.slotSel}`
389
- : s.available
390
- ? styles.slot
391
- : `${styles.slot} ${styles.slotTaken}`, onClick: () => s.available && setSlot(s), children: [s.startTime, s === firstAvailable && (_jsx("span", { className: styles.firstTag, children: "Primera disponible" }))] }, s.id))) })] }, group.label)))), localStart && slot && (_jsxs("p", { className: styles.tz, children: [slot.startTime, " es ", localStart, " en tu hora."] }))] }, "hour")), currentKey === 'confirm' && (_jsxs("section", { className: `${styles.pane} ${styles.paneFinal}`, children: [_jsx("h3", { className: styles.q, children: copy.title }), _jsx("p", { className: styles.qsub, children: withoutContact
392
- ? copy.note
393
- : askName || askEmail
394
- ? 'Completa tus datos y reserva el horario. La cita se confirma al enviar el formulario.'
395
- : 'Reserva el horario. La cita se confirma al enviar el formulario.' })] }, "confirm"))] })] }), _jsxs("div", { className: styles.navbar, children: [_jsx("div", { className: styles.navSummary, children: isFinal || !canAdvance || navParts.length === 0
396
- ? STEP_HINT[currentKey]
397
- : 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: holding ? copy.confirming : copy.confirm, sx: ACCENT_SX, disabled: !slot || holding || !canAdvance, onClick: () => void confirm() })) : (_jsx(ErkButton, { text: "Siguiente", disabled: !canAdvance, onClick: () => goToStep(wizStep + 1) }))] })] }) }), error && _jsx("div", { className: styles.err, children: error })] }));
425
+ const stepLabel = (key) => (key === 'confirm' ? copy.confirm : STEP_LABEL[key]);
426
+ const goTo = (key) => setWizStep(wizardKeys.indexOf(key));
427
+ const lockNote = (_jsxs("div", { className: styles.info, children: [_jsx(InfoIcon, { size: 14, className: styles.infoIcon }), _jsxs("span", { children: [temporaryLockMinutes ? (_jsxs(_Fragment, { children: ["Reservaremos el horario durante ", _jsxs("b", { children: [temporaryLockMinutes, " min"] }), ".", ' '] })) : ('Reservaremos el horario. '), copy.note] })] }));
428
+ const renderType = () => (_jsxs("div", { className: styles.pane, children: [_jsxs("div", { className: styles.question, children: [_jsx("h3", { className: styles.q, children: "\u00BFQu\u00E9 necesitas?" }), _jsx("p", { className: styles.qsub, children: "Elige el tipo de atenci\u00F3n; de \u00E9l dependen la duraci\u00F3n y los horarios." })] }), _jsx("div", { className: styles.options, children: (eventTemplates ?? []).map((t) => (_jsx(OptionCard, { selected: eventTemplate?.id === t.id, title: t.name, subtitle: t.description, aside: `${t.durationMinutes} min`, onClick: () => pickType(t) }, t.id))) })] }));
429
+ const renderPerson = () => (_jsxs("div", { className: styles.pane, children: [_jsxs("div", { className: styles.question, children: [_jsx("h3", { className: styles.q, children: "\u00BFCon qui\u00E9n quieres agendar?" }), _jsx("p", { className: styles.qsub, children: personsOptional
430
+ ? 'Elige una persona o deja que el sistema asigne a quien esté disponible.'
431
+ : 'Elige la persona con la que quieres tu cita.' })] }), _jsxs("div", { className: styles.options, children: [(persons ?? []).map((p) => (_jsx(OptionCard, { selected: person?.id === p.id, avatar: _jsx("span", { className: styles.avatar, children: initials(p.name) }), title: p.name, subtitle: p.role, onClick: () => pickPerson(p) }, p.id))), personsOptional && (_jsx(OptionCard, { selected: personTouched && !person, dashed: true, avatar: _jsx("span", { className: cx(styles.avatar, styles.avatarAny), children: _jsx(GroupIcon, { size: 18 }) }), title: "Cualquiera", subtitle: "El sistema asigna a quien est\u00E9 disponible", onClick: () => pickPerson(undefined) }))] })] }));
432
+ const renderHours = () => {
433
+ if (!date)
434
+ return _jsx("p", { className: styles.empty, children: "Elige un d\u00EDa para ver sus horas." });
435
+ if (loading)
436
+ return _jsx("p", { className: styles.empty, children: "Buscando horarios\u2026" });
437
+ if (slotsError) {
438
+ return (_jsxs("div", { className: styles.retry, role: "alert", children: [_jsx("span", { children: slotsError }), _jsx(ErkButton, { text: "Reintentar", variant: "outlined", onClick: () => void loadSlots(date, person?.id, eventTemplate?.id) })] }));
439
+ }
440
+ if (slots.length === 0)
441
+ return _jsx("p", { className: styles.empty, children: "No hay horarios disponibles ese d\u00EDa." });
442
+ return (_jsx("div", { className: styles.hourList, children: slots.map((s) => (_jsx("button", { type: "button", disabled: !s.available, "aria-pressed": slot?.id === s.id, "aria-label": `${s.startTime} – ${s.endTime}${s.available ? '' : ' (ocupado)'}`, className: cx(styles.hour, slot?.id === s.id && styles.hourOn, !s.available && styles.hourTaken), onClick: () => s.available && setSlot(s), children: s.startTime }, s.id))) }));
443
+ };
444
+ const renderDate = () => (_jsxs("div", { className: styles.split, children: [_jsxs("div", { className: styles.calendar, 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", className: styles.iconBtn, onClick: () => setViewMonth((m) => addMonths(m, -1)), children: _jsx(ArrowLeftIcon, { size: 16 }) }), _jsx("button", { type: "button", "aria-label": "Mes siguiente", className: styles.iconBtn, onClick: () => setViewMonth((m) => addMonths(m, 1)), children: _jsx(ArrowRightIcon, { size: 16 }) })] })] }), _jsx("div", { className: styles.dow, "aria-hidden": true, children: DOW.map((d, i) => (_jsx("span", { children: d }, i))) }), _jsx("div", { className: styles.days, children: monthCells.map((d) => {
445
+ if (!isSameMonth(d, viewMonth))
446
+ return _jsx("span", {}, d.toISOString());
447
+ const past = isBefore(d, today);
448
+ const offered = !daysError && (!days || days.includes(fmtDate(d)));
449
+ const selectable = !past && offered;
450
+ const selected = !!date && isSameDay(d, date);
451
+ return (_jsx("button", { type: "button", disabled: !selectable, "aria-label": fullDate(d), "aria-pressed": selected, className: cx(styles.day, selected
452
+ ? styles.dayOn
453
+ : selectable
454
+ ? styles.dayFree
455
+ : isSameDay(d, today) && styles.dayToday), onClick: () => pickDay(d), children: format(d, 'd') }, d.toISOString()));
456
+ }) }), daysError ? (_jsxs("div", { className: styles.retry, role: "alert", children: [_jsx("span", { children: daysError }), _jsx(ErkButton, { text: "Reintentar", variant: "outlined", onClick: () => setDaysAttempt((n) => n + 1) })] })) : fetchDays && days === undefined ? (_jsx("p", { className: styles.calNote, children: "Buscando d\u00EDas con horarios\u2026" })) : days?.length === 0 ? (_jsx("p", { className: styles.calNote, children: "No hay d\u00EDas con horarios disponibles." })) : (_jsx("p", { className: styles.calNote, children: fetchDays
457
+ ? 'Solo se pueden elegir los días con horarios disponibles.'
458
+ : 'Solo se muestran fechas a partir de hoy.' }))] }), _jsxs("div", { className: styles.hours, children: [_jsxs("div", { className: styles.hoursHead, children: [_jsx("span", { className: styles.hoursTitle, children: date ? longDay(date) : 'Elige un día' }), _jsx("span", { className: styles.hoursSub, children: zoneText ? `${zoneText} · citas de ${duration} min` : `Citas de ${duration} min` }), localStart && slot && (_jsxs("span", { className: styles.hoursSub, children: [slot.startTime, " es ", localStart, " en tu hora."] }))] }), holdError && (_jsx("div", { className: styles.alert, role: "alert", children: holdError })), renderHours()] })] }));
459
+ const renderConfirm = () => (_jsxs("div", { className: styles.split, children: [_jsxs("div", { className: styles.summary, children: [_jsx("p", { className: styles.overline, children: "Tu cita" }), eventTemplate && (_jsx(SummaryRow, { icon: _jsx(ClockIcon, { size: 16 }), title: eventTemplate.name, sub: `${eventTemplate.durationMinutes} min`, change: "Cambiar el tipo de atenci\u00F3n", onChange: () => goTo('type') })), needsPerson && personLabel && (_jsx(SummaryRow, { icon: _jsx(PersonIcon, { size: 16 }), title: personLabel, sub: person ? person.role : 'El sistema asigna a quien esté disponible', change: "Cambiar la persona", onChange: () => goTo('person') })), date && (_jsx(SummaryRow, { icon: _jsx(CalendarIcon, { size: 16 }), title: longDay(date), sub: [hourText, `${duration} min`, localStart ? `${localStart} en tu hora` : undefined]
460
+ .filter(Boolean)
461
+ .join(' · '), change: "Cambiar el d\u00EDa y la hora", onChange: () => goTo('date') })), mode && (_jsx(SummaryRow, { icon: _jsx(ModeIcon, { mode: mode, size: 16 }), title: MODE_SUMMARY[mode].title, sub: MODE_SUMMARY[mode].sub }))] }), _jsx("div", { className: styles.side, children: withoutContact ? (_jsxs("div", { className: styles.question, children: [_jsx("h3", { className: styles.q, children: copy.title }), _jsx("p", { className: styles.qsub, children: copy.note })] })) : askName || askContactEmail ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.question, children: [_jsx("h3", { className: styles.q, children: "Tus datos" }), askContactEmail && (_jsx("p", { className: styles.qsub, children: mode === BookingMode.VIRTUAL
462
+ ? 'Te enviaremos la confirmación y el enlace de la reunión.'
463
+ : 'Te enviaremos la confirmación a tu correo.' }))] }), askName && (_jsx(ErkTextField, { label: "Nombre", value: contactName, onChange: setContactName, required: true })), askContactEmail && (_jsx(ErkTextField, { label: "Correo", type: "email", value: contactEmail, onChange: setContactEmail, onBlur: () => setEmailTouched(true), required: true, error: emailTouched && emailInvalid, helperText: emailTouched && contactEmail.trim() && emailInvalid
464
+ ? 'Escribe un correo válido.'
465
+ : NO_EMAIL_HINT })), lockNote] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.question, children: [_jsx("h3", { className: styles.q, children: "Confirmaci\u00F3n" }), _jsx("p", { className: styles.qsub, children: "Usaremos los datos que ya escribiste en el formulario." })] }), _jsxs("div", { className: styles.known, children: [knownName && (_jsxs("div", { className: styles.knownRow, children: [_jsx(PersonIcon, { size: 16, className: styles.knownIcon }), _jsx("span", { className: styles.knownValue, children: knownName }), _jsx("span", { className: styles.knownFrom, children: "del campo Nombre" })] })), knownEmail && (_jsxs("div", { className: styles.knownRow, children: [_jsx(EmailIcon, { size: 16, className: styles.knownIcon }), _jsx("span", { className: styles.knownValue, children: knownEmail }), _jsx("span", { className: styles.knownFrom, children: "del campo Correo" })] }))] }), _jsx("p", { className: styles.fineprint, children: "\u00BFHay un error? Cierra el selector y corr\u00EDgelo en el formulario; el horario sigue elegido." }), lockNote] })) })] }));
466
+ return (_jsxs("div", { ref: containerRef, className: styles.container, children: [head, _jsx("div", { className: styles.control, children: _jsx(ErkButton, { text: copy.open, variant: "outlined", color: error ? 'error' : 'primary', disabled: disabled, startIcon: _jsx(CalendarIcon, { size: 16 }), sx: { height: 36, padding: '0 14px', ...OUTLINED_DISABLED }, onClick: () => {
467
+ setTheme(themeOf(containerRef.current));
468
+ setOpen(true);
469
+ } }) }), contactLine && (_jsx("div", { className: styles.note, children: contactLine === 'email' ? (_jsxs(_Fragment, { children: ["Te confirmaremos a ", _jsx("b", { children: knownEmail }), " cuando env\u00EDes el formulario."] })) : (contactLine) })), loadError && (_jsxs("div", { className: cx(styles.errorMsg, styles.loadError), role: "alert", children: [_jsx("span", { children: loadError }), onRetryLoad && (_jsx(ErkButton, { text: "Reintentar", variant: "outlined", sx: { height: 28, fontSize: 13 }, onClick: onRetryLoad }))] })), lapsed && !open && (_jsx("div", { className: styles.errorMsg, role: "alert", children: lapsed })), error && (_jsx("div", { className: styles.errorMsg, role: "alert", children: error })), _jsx(Dialog, { open: open, onClose: () => setOpen(false), maxWidth: false, "aria-labelledby": titleId, slotProps: { paper: { sx: PAPER_SX } }, children: _jsxs("div", { className: styles.card, style: theme, children: [_jsxs("header", { className: styles.header, children: [_jsxs("div", { className: styles.headText, children: [_jsx("h2", { id: titleId, className: styles.title, children: label }), _jsxs("div", { className: styles.meta, children: [_jsxs("span", { className: styles.metaItem, children: [_jsx(ClockIcon, { size: 14 }), duration, " min"] }), mode && (_jsxs("span", { className: styles.metaItem, children: [_jsx(ModeIcon, { mode: mode, size: 14 }), MODE_LABEL[mode]] }))] })] }), _jsx("button", { type: "button", "aria-label": "Cerrar", className: styles.close, onClick: () => setOpen(false), children: _jsx(CloseIcon, { size: 16 }) })] }), _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, "aria-current": i === wizStep ? 'step' : undefined, onClick: () => setWizStep(i), className: cx(styles.step, i < wizStep && styles.stepDone, i === wizStep && styles.stepOn), children: [_jsx("span", { className: styles.stepNum, children: i < wizStep ? _jsx(CheckIcon, { size: 12 }) : i + 1 }), _jsx("span", { className: styles.stepLabel, children: stepLabel(key) })] }), i < wizardKeys.length - 1 && _jsx("span", { className: styles.stepSep })] }, key))) }), _jsxs("div", { className: styles.body, children: [currentKey === 'type' && renderType(), currentKey === 'person' && renderPerson(), currentKey === 'date' && renderDate(), currentKey === 'confirm' && renderConfirm()] }, currentKey), _jsxs("footer", { className: styles.footer, children: [_jsxs("div", { className: styles.footSummary, "aria-live": "polite", children: [chips.map((chip) => (_jsx("span", { className: cx(styles.chip, chip.accent && styles.chipAccent), children: chip.text }, chip.key))), hint && _jsx("span", { className: styles.footHint, children: hint })] }), _jsxs("div", { className: styles.footActions, children: [wizStep > 0 && (_jsx(ErkButton, { text: "Atr\u00E1s", variant: "outlined", sx: NAV_SX, onClick: () => setWizStep(wizStep - 1) })), isFinal ? (_jsx(ErkButton, { text: holding ? copy.confirming : copy.confirm, sx: ACCENT_SX, className: styles.primaryAction, disabled: !slot || holding || !canAdvance, onClick: () => void confirm() })) : (_jsx(ErkButton, { text: "Siguiente", sx: { ...NAV_SX, padding: '0 22px' }, className: styles.primaryAction, disabled: !canAdvance, onClick: () => setWizStep(wizStep + 1) }))] })] })] }) })] }));
398
470
  }
399
471
  export default ErkBookingPicker;