@arquimedes.co/eureka-forms 3.0.70 → 3.0.71-test

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 (146) hide show
  1. package/README +1 -33
  2. package/dist/@Types/FormStep.d.ts +2 -7
  3. package/dist/@Types/FormStep.js +1 -2
  4. package/dist/App/App.stories.d.ts +0 -10
  5. package/dist/App/App.stories.js +0 -189
  6. package/dist/App/AppHooks.js +0 -8
  7. package/dist/App/AppHooks.test.js +0 -8
  8. package/dist/Form/Form.js +6 -7
  9. package/dist/Form/FormFunctions.d.ts +0 -17
  10. package/dist/Form/FormFunctions.js +0 -22
  11. package/dist/Form/FormFunctions.test.js +1 -28
  12. package/dist/FormSteps/AYFStepMapper.js +2 -24
  13. package/dist/FormSteps/CheckBoxStep/CheckBoxStep.mobileLabel.test.js +12 -20
  14. package/dist/FormSteps/CheckBoxStep/MaterialCheckBoxStep/MaterialCheckBoxStep.js +13 -6
  15. package/dist/FormSteps/CheckBoxStep/MaterialCheckBoxStep/MaterialCheckBoxStep.module.css +13 -2
  16. package/dist/FormSteps/ClassifierSelectorStep/MaterialClassifierSelectorStep/MaterialClassifierSelectorStep.js +1 -3
  17. package/dist/FormSteps/EntityValueStep/MaterialEntityValuePickerStep/MaterialEntityValuePickerStep.js +26 -26
  18. package/dist/FormSteps/SmartSelectStep/MaterialSmartSelectStep/MaterialSmartSelectStep.js +11 -1
  19. package/dist/FormSteps/SmartSelectStep/SmartSelectStep.staleOptions.test.js +100 -0
  20. package/dist/FormSteps/Step.js +0 -16
  21. package/dist/FormSteps/StepFunctions.js +0 -14
  22. package/dist/FormSteps/StepFunctions.test.js +0 -54
  23. package/dist/FormSteps/StepHooks.d.ts +0 -21
  24. package/dist/FormSteps/StepHooks.js +8 -55
  25. package/dist/FormSteps/ciudadesICAQuery.d.ts +8 -0
  26. package/dist/FormSteps/ciudadesICAQuery.js +31 -0
  27. package/dist/FormSteps/ciudadesICAQuery.test.js +39 -0
  28. package/dist/Shared/ErkDatePicker/ErkDatePicker.js +3 -1
  29. package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.d.ts +3 -2
  30. package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.js +2 -1
  31. package/dist/Shared/ErkSmartSelect/ErkSmartSelect.js +4 -14
  32. package/dist/Shared/ErkSmartSelect/ErkSmartSelect.test.js +21 -1
  33. package/dist/Utils/AxiosWidget.js +1 -9
  34. package/dist/constants/FormStepTypes.d.ts +1 -5
  35. package/dist/constants/FormStepTypes.js +0 -4
  36. package/dist/index.lib.d.ts +2 -4
  37. package/dist/index.lib.js +1 -3
  38. package/package.json +1 -2
  39. package/dist/@Types/AvailabilityFormStep.d.ts +0 -48
  40. package/dist/@Types/AvailabilityFormStep.js +0 -24
  41. package/dist/@Types/BankAccountFormStep.d.ts +0 -67
  42. package/dist/@Types/BankAccountFormStep.js +0 -14
  43. package/dist/@Types/BookingFormStep.d.ts +0 -197
  44. package/dist/@Types/BookingFormStep.js +0 -44
  45. package/dist/@Types/CalendarFormStep.d.ts +0 -209
  46. package/dist/@Types/CalendarFormStep.js +0 -55
  47. package/dist/@Types/CommunicationChannelFormStep.d.ts +0 -51
  48. package/dist/@Types/CommunicationChannelFormStep.js +0 -15
  49. package/dist/@Types/ConsentFormStep.d.ts +0 -36
  50. package/dist/@Types/ConsentFormStep.js +0 -8
  51. package/dist/@Types/EntityFormFormStep.d.ts +0 -66
  52. package/dist/@Types/EntityFormFormStep.js +0 -20
  53. package/dist/@Types/EventFormStep.d.ts +0 -52
  54. package/dist/@Types/EventFormStep.js +0 -13
  55. package/dist/@Types/ExternalReferenceFormStep.d.ts +0 -74
  56. package/dist/@Types/ExternalReferenceFormStep.js +0 -20
  57. package/dist/@Types/LocationFormStep.d.ts +0 -159
  58. package/dist/@Types/LocationFormStep.js +0 -46
  59. package/dist/@Types/NumericFormSteps.d.ts +0 -108
  60. package/dist/@Types/NumericFormSteps.js +0 -20
  61. package/dist/@Types/PredefinedSelectorFormStep.d.ts +0 -51
  62. package/dist/@Types/PredefinedSelectorFormStep.js +0 -39
  63. package/dist/@Types/StepRef.d.ts +0 -135
  64. package/dist/@Types/StepRef.js +0 -49
  65. package/dist/FormSteps/AvailabilityStep/AvailabilityStep.d.ts +0 -7
  66. package/dist/FormSteps/AvailabilityStep/AvailabilityStep.js +0 -14
  67. package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.d.ts +0 -8
  68. package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.js +0 -32
  69. package/dist/FormSteps/BookingStep/BookingStep.d.ts +0 -17
  70. package/dist/FormSteps/BookingStep/BookingStep.js +0 -14
  71. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.d.ts +0 -14
  72. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +0 -133
  73. package/dist/FormSteps/ClassifierSelectorStep/ClassifierSelectorStep.lateClassifiers.test.js +0 -70
  74. package/dist/FormSteps/EntityValueStep/entityValueDependencyValue.d.ts +0 -12
  75. package/dist/FormSteps/EntityValueStep/entityValueDependencyValue.js +0 -35
  76. package/dist/FormSteps/EntityValueStep/entityValueDependencyValue.test.js +0 -31
  77. package/dist/FormSteps/EntityValueStep/entityValuePickerWatchedSteps.d.ts +0 -27
  78. package/dist/FormSteps/EntityValueStep/entityValuePickerWatchedSteps.js +0 -64
  79. package/dist/FormSteps/EntityValueStep/entityValuePickerWatchedSteps.test.d.ts +0 -1
  80. package/dist/FormSteps/EntityValueStep/entityValuePickerWatchedSteps.test.js +0 -95
  81. package/dist/FormSteps/LocationStep/LocationStep.d.ts +0 -10
  82. package/dist/FormSteps/LocationStep/LocationStep.js +0 -14
  83. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.d.ts +0 -3
  84. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.js +0 -54
  85. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.module.css +0 -18
  86. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.d.ts +0 -3
  87. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.js +0 -170
  88. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.module.css +0 -96
  89. package/dist/FormSteps/NumberStep/NumberStep.d.ts +0 -10
  90. package/dist/FormSteps/NumberStep/NumberStep.js +0 -14
  91. package/dist/FormSteps/StepHooks.test.d.ts +0 -1
  92. package/dist/FormSteps/StepHooks.test.js +0 -92
  93. package/dist/FormSteps/isStepHiddenByCondition.test.d.ts +0 -1
  94. package/dist/FormSteps/isStepHiddenByCondition.test.js +0 -42
  95. package/dist/Icons/AddIcon.d.ts +0 -3
  96. package/dist/Icons/AddIcon.js +0 -7
  97. package/dist/Icons/EditIcon.d.ts +0 -3
  98. package/dist/Icons/EditIcon.js +0 -7
  99. package/dist/Icons/RemoveIcon.d.ts +0 -3
  100. package/dist/Icons/RemoveIcon.js +0 -7
  101. package/dist/Icons/ViewDayIcon.d.ts +0 -3
  102. package/dist/Icons/ViewDayIcon.js +0 -7
  103. package/dist/Icons/ViewWeekIcon.d.ts +0 -3
  104. package/dist/Icons/ViewWeekIcon.js +0 -7
  105. package/dist/Services/BookingService.d.ts +0 -79
  106. package/dist/Services/BookingService.js +0 -66
  107. package/dist/Services/BookingService.test.d.ts +0 -1
  108. package/dist/Services/BookingService.test.js +0 -57
  109. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.d.ts +0 -19
  110. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.js +0 -80
  111. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.module.css +0 -88
  112. package/dist/Shared/ErkAddressAutocomplete/googlePlaces.d.ts +0 -29
  113. package/dist/Shared/ErkAddressAutocomplete/googlePlaces.js +0 -139
  114. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.d.ts +0 -25
  115. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.js +0 -112
  116. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.module.css +0 -88
  117. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.test.d.ts +0 -1
  118. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.test.js +0 -73
  119. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.d.ts +0 -54
  120. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.js +0 -294
  121. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.module.css +0 -608
  122. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.d.ts +0 -28
  123. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.js +0 -117
  124. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.d.ts +0 -1
  125. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.js +0 -49
  126. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.d.ts +0 -1
  127. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.js +0 -171
  128. package/dist/Shared/ErkIconButton/ErkIconButton.d.ts +0 -17
  129. package/dist/Shared/ErkIconButton/ErkIconButton.js +0 -11
  130. package/dist/Shared/ErkIconButton/ErkIconButton.module.css +0 -41
  131. package/dist/Shared/ErkLocationField/ErkLocationField.d.ts +0 -24
  132. package/dist/Shared/ErkLocationField/ErkLocationField.js +0 -128
  133. package/dist/Shared/ErkLocationField/ErkLocationField.module.css +0 -164
  134. package/dist/Shared/ErkLocationField/ErkLocationField.test.d.ts +0 -1
  135. package/dist/Shared/ErkLocationField/ErkLocationField.test.js +0 -82
  136. package/dist/Shared/ErkLocationField/addressFields.d.ts +0 -12
  137. package/dist/Shared/ErkLocationField/addressFields.js +0 -34
  138. package/dist/Shared/ErkLocationField/locationRestrictions.d.ts +0 -6
  139. package/dist/Shared/ErkLocationField/locationRestrictions.js +0 -51
  140. package/dist/Shared/ErkLocationInput/ErkLocationInput.d.ts +0 -15
  141. package/dist/Shared/ErkLocationInput/ErkLocationInput.js +0 -502
  142. package/dist/Shared/ErkLocationInput/ErkLocationInput.module.css +0 -128
  143. package/dist/Shared/ErkNumberField/ErkNumberField.d.ts +0 -42
  144. package/dist/Shared/ErkNumberField/ErkNumberField.js +0 -169
  145. /package/dist/FormSteps/{ClassifierSelectorStep/ClassifierSelectorStep.lateClassifiers.test.d.ts → SmartSelectStep/SmartSelectStep.staleOptions.test.d.ts} +0 -0
  146. /package/dist/FormSteps/{EntityValueStep/entityValueDependencyValue.test.d.ts → ciudadesICAQuery.test.d.ts} +0 -0
@@ -1,73 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { render, screen } from '@testing-library/react';
3
- import userEvent from '@testing-library/user-event';
4
- import { describe, expect, it, vi } from 'vitest';
5
- import '@testing-library/jest-dom';
6
- import { addDays, format, startOfWeek } from 'date-fns';
7
- import ErkAvailabilityPicker from './ErkAvailabilityPicker';
8
- import MaterialProviders from '../../Utils/MaterialProviders';
9
- import InternalFormStyle from '../../constants/InternalFormStyle';
10
- import { CalendarEventCategory } from '../../@Types/CalendarFormStep';
11
- function iso(dayIdx, hhmm) {
12
- const day = addDays(startOfWeek(new Date(), { weekStartsOn: 1 }), dayIdx);
13
- return `${format(day, 'yyyy-MM-dd')}T${hhmm}:00`;
14
- }
15
- describe('ErkAvailabilityPicker', () => {
16
- it('marks a cell and emits a weekly WORKING window', async () => {
17
- const user = userEvent.setup();
18
- const onChange = vi.fn();
19
- render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkAvailabilityPicker, { value: null, onChange: onChange, label: "Disponibilidad", minTime: "08:00", maxTime: "12:00" }) }));
20
- await user.click(screen.getByRole('button', { name: 'Lunes 9:00' }));
21
- expect(onChange).toHaveBeenCalledWith({
22
- type: 'CALENDAR_VALUE',
23
- events: [
24
- expect.objectContaining({
25
- category: CalendarEventCategory.WORKING,
26
- start: iso(0, '09:00'),
27
- end: iso(0, '10:00'),
28
- recurrence: 'FREQ=WEEKLY',
29
- }),
30
- ],
31
- timezone: undefined,
32
- });
33
- });
34
- it('merges contiguous marked hours into a single window', async () => {
35
- const user = userEvent.setup();
36
- const onChange = vi.fn();
37
- const value = {
38
- type: 'CALENDAR_VALUE',
39
- events: [
40
- {
41
- id: 'avail-0-9-10',
42
- category: CalendarEventCategory.WORKING,
43
- start: iso(0, '09:00'),
44
- end: iso(0, '10:00'),
45
- recurrence: 'FREQ=WEEKLY',
46
- },
47
- ],
48
- };
49
- render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkAvailabilityPicker, { value: value, onChange: onChange, label: "Disponibilidad", minTime: "08:00", maxTime: "12:00" }) }));
50
- await user.click(screen.getByRole('button', { name: 'Lunes 10:00' }));
51
- expect(onChange).toHaveBeenCalledWith(expect.objectContaining({
52
- events: [expect.objectContaining({ start: iso(0, '09:00'), end: iso(0, '11:00') })],
53
- }));
54
- });
55
- it('renders existing windows as pressed cells and blocks toggling when readOnly', () => {
56
- const value = {
57
- type: 'CALENDAR_VALUE',
58
- events: [
59
- {
60
- id: 'avail-2-14-16',
61
- category: CalendarEventCategory.WORKING,
62
- start: iso(2, '14:00'),
63
- end: iso(2, '16:00'),
64
- recurrence: 'FREQ=WEEKLY',
65
- },
66
- ],
67
- };
68
- render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkAvailabilityPicker, { value: value, onChange: () => { }, label: "Disponibilidad", minTime: "13:00", maxTime: "17:00", readOnly: true }) }));
69
- expect(screen.getByRole('button', { name: 'Miércoles 14:00', pressed: true })).toBeDisabled();
70
- expect(screen.getByRole('button', { name: 'Miércoles 15:00', pressed: true })).toBeInTheDocument();
71
- expect(screen.getByRole('button', { name: 'Miércoles 16:00', pressed: false })).toBeInTheDocument();
72
- });
73
- });
@@ -1,54 +0,0 @@
1
- import { BookablePerson, BookingMode, BookingSlot, BookingValue, PersonSelectionMode } from '../../@Types/BookingFormStep';
2
- export interface ErkBookingPickerProps {
3
- value: BookingValue | null;
4
- onChange: (value: BookingValue | null) => void;
5
- label: string;
6
- description?: string | null;
7
- durationMinutes: number;
8
- bufferMinutes?: number;
9
- bookingMode?: BookingMode;
10
- temporaryLockMinutes?: number;
11
- personSelectionMode?: PersonSelectionMode;
12
- persons?: BookablePerson[];
13
- /** Recopila nombre/correo del que agenda. */
14
- collectContact?: boolean;
15
- /** El nombre es obligatorio para confirmar: el paso no admite reservas anónimas. */
16
- requireContact?: boolean;
17
- /** Provee los slots disponibles para una persona + día. */
18
- fetchSlots?: (args: {
19
- personId?: string;
20
- date: string;
21
- }) => Promise<BookingSlot[]>;
22
- /**
23
- * Los días (`YYYY-MM-DD`) con algún horario para la persona elegida. Sin él, cualquier día desde
24
- * hoy es elegible.
25
- */
26
- fetchDays?: (args: {
27
- personId?: string;
28
- }) => Promise<string[]>;
29
- /**
30
- * Retira el slot de la oferta al confirmar. Lo que devuelve se guarda en el valor junto con la
31
- * selección; si rechaza, el wizard vuelve a la hora con el motivo y recarga los horarios.
32
- */
33
- holdSlot?: (args: {
34
- slot: BookingSlot;
35
- personId?: string;
36
- contact?: {
37
- name?: string;
38
- email?: string;
39
- };
40
- }) => Promise<Pick<BookingValue, 'appointment' | 'hold' | 'lockedUntil'>>;
41
- /** Deshabilita la tarjeta de entrada mientras el host termina de cargar la oferta. */
42
- disabled?: boolean;
43
- readOnly?: boolean;
44
- error?: string;
45
- }
46
- /**
47
- * Selector de cita: una tarjeta de entrada ("Agendar cita") abre el wizard en un modal —
48
- * una decisión por pantalla (persona → día → hora → confirmar), con panel izquierdo de
49
- * resumen que se va llenando, breadcrumb navegable arriba y barra inferior Atrás / Siguiente.
50
- * En el paso final el panel derecho desaparece y el resumen se expande en una columna
51
- * centrada (cross-fade corto). Confirmar cierra el modal y deja el resumen compacto.
52
- */
53
- declare function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bufferMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, collectContact, requireContact, fetchSlots, fetchDays, holdSlot, disabled, readOnly, error, }: ErkBookingPickerProps): JSX.Element;
54
- export default ErkBookingPicker;
@@ -1,294 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useState } from 'react';
3
- import { Dialog } from '@mui/material';
4
- import { addDays, addMonths, format, isBefore, isSameDay, isSameMonth, startOfDay, startOfMonth, startOfWeek, } from 'date-fns';
5
- import { es } from 'date-fns/locale';
6
- import { BookingMode, PersonSelectionMode, } from '../../@Types/BookingFormStep';
7
- import ErkButton from '../ErkButton/ErkButton';
8
- import ErkTextField from '../ErkTextField/ErkTextField';
9
- import styles from './ErkBookingPicker.module.css';
10
- const WEEK_STARTS_ON = 1;
11
- const DOW = ['L', 'M', 'M', 'J', 'V', 'S', 'D'];
12
- const MODE_LABEL = {
13
- [BookingMode.IN_PERSON]: 'Presencial',
14
- [BookingMode.VIRTUAL]: 'Virtual',
15
- [BookingMode.HYBRID]: 'Híbrida',
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
- };
37
- function fmtDate(date) {
38
- return format(date, 'yyyy-MM-dd');
39
- }
40
- function longDate(date) {
41
- return format(date, 'EEEE d MMM', { locale: es });
42
- }
43
- function initials(name) {
44
- return name
45
- .split(' ')
46
- .map((p) => p[0])
47
- .filter(Boolean)
48
- .slice(0, 2)
49
- .join('')
50
- .toUpperCase();
51
- }
52
- function capitalize(text) {
53
- return text.charAt(0).toUpperCase() + text.slice(1);
54
- }
55
- /**
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.
61
- */
62
- function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bufferMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, collectContact = false, requireContact = false, fetchSlots, fetchDays, holdSlot, disabled = false, readOnly = false, error, }) {
63
- const needsPerson = !!personSelectionMode && personSelectionMode !== PersonSelectionMode.NONE;
64
- const personsOptional = personSelectionMode === PersonSelectionMode.OPTIONAL;
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);
71
- const [person, setPerson] = useState(undefined);
72
- const [personTouched, setPersonTouched] = useState(false);
73
- const [date, setDate] = useState(undefined);
74
- const [viewMonth, setViewMonth] = useState(() => startOfMonth(today));
75
- const [slot, setSlot] = useState(undefined);
76
- const [slots, setSlots] = useState([]);
77
- const [loading, setLoading] = useState(false);
78
- const [contactName, setContactName] = useState('');
79
- const [contactEmail, setContactEmail] = useState('');
80
- /** `undefined` mientras se consultan; sin `fetchDays` nunca se consultan y todo día futuro vale. */
81
- const [days, setDays] = useState(undefined);
82
- const [holding, setHolding] = useState(false);
83
- const [holdError, setHoldError] = useState(undefined);
84
- const reduceMotion = useMemo(() => typeof window !== 'undefined' && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches, []);
85
- const monthCells = useMemo(() => {
86
- const first = startOfWeek(startOfMonth(viewMonth), { weekStartsOn: WEEK_STARTS_ON });
87
- return Array.from({ length: 42 }, (_, i) => addDays(first, i));
88
- }, [viewMonth]);
89
- const loadSlots = useCallback(async (day, personId) => {
90
- if (!fetchSlots)
91
- return;
92
- setLoading(true);
93
- setSlots([]);
94
- try {
95
- setSlots(await fetchSlots({ personId, date: fmtDate(day) }));
96
- }
97
- catch {
98
- setSlots([]);
99
- }
100
- finally {
101
- setLoading(false);
102
- }
103
- }, [fetchSlots]);
104
- useEffect(() => {
105
- if (value || !date)
106
- return;
107
- // The picker synchronises with the slots API here, and `loadSlots` raises its own
108
- // loading flag as it goes out — the very shape the rule asks effects to be used for.
109
- // oxlint-disable-next-line react/set-state-in-effect
110
- void loadSlots(date, person?.id);
111
- }, [value, date, person, loadSlots]);
112
- useEffect(() => {
113
- if (!fetchDays || !open || value)
114
- return;
115
- let cancelled = false;
116
- // Same shape as the slots effect: the days come from the host's API and the picker only mirrors them.
117
- // oxlint-disable-next-line react/set-state-in-effect
118
- setDays(undefined);
119
- fetchDays({ personId: person?.id })
120
- .then((available) => {
121
- if (cancelled)
122
- return;
123
- setDays(available);
124
- // The month on screen may hold nothing; the first day on offer is where the requester needs to be.
125
- const first = available[0];
126
- if (!first)
127
- return;
128
- setViewMonth((current) => available.some((d) => isSameMonth(new Date(d + 'T00:00:00'), current))
129
- ? current
130
- : startOfMonth(new Date(first + 'T00:00:00')));
131
- })
132
- .catch(() => {
133
- if (!cancelled)
134
- setDays([]);
135
- });
136
- return () => {
137
- cancelled = true;
138
- };
139
- }, [fetchDays, open, value, person]);
140
- // Al entrar/salir del paso final el layout se reestructura; el reacomodo
141
- // ocurre mientras la tarjeta está invisible (fade corto en dos fases).
142
- const goToStep = (target) => {
143
- const crossesFinal = (wizStep === finalIdx) !== (target === finalIdx);
144
- if (!crossesFinal || reduceMotion) {
145
- setWizStep(target);
146
- return;
147
- }
148
- setXfade(true);
149
- window.setTimeout(() => {
150
- setWizStep(target);
151
- requestAnimationFrame(() => requestAnimationFrame(() => setXfade(false)));
152
- }, XFADE_MS);
153
- };
154
- const currentKey = wizardKeys[wizStep];
155
- const canAdvance = (() => {
156
- switch (currentKey) {
157
- case 'person':
158
- return personsOptional ? personTouched : !!person;
159
- case 'day':
160
- return !!date;
161
- case 'hour':
162
- return !!slot;
163
- case 'confirm':
164
- return !requireContact || contactName.trim().length > 0;
165
- default:
166
- return true;
167
- }
168
- })();
169
- const confirm = async () => {
170
- if (!slot || !date)
171
- return;
172
- const selection = {
173
- type: 'BOOKING_VALUE',
174
- slotId: slot.id,
175
- date: fmtDate(date),
176
- startTime: slot.startTime,
177
- endTime: slot.endTime,
178
- personId: person?.id,
179
- personName: person?.name,
180
- contactName: contactName || undefined,
181
- contactEmail: contactEmail || undefined,
182
- status: 'PENDING',
183
- };
184
- if (!holdSlot) {
185
- setOpen(false);
186
- onChange(selection);
187
- return;
188
- }
189
- setHolding(true);
190
- setHoldError(undefined);
191
- try {
192
- const held = await holdSlot({
193
- slot,
194
- personId: person?.id,
195
- contact: collectContact
196
- ? { name: contactName || undefined, email: contactEmail || undefined }
197
- : undefined,
198
- });
199
- setOpen(false);
200
- onChange({ ...selection, ...held });
201
- }
202
- catch (e) {
203
- // Somebody else took the slot while the form was being finished: back to the hour, with fresh slots.
204
- setHoldError(e instanceof Error && e.message ? e.message : 'No pudimos reservar ese horario.');
205
- setSlot(undefined);
206
- goToStep(wizardKeys.indexOf('hour'));
207
- void loadSlots(date, person?.id);
208
- }
209
- finally {
210
- setHolding(false);
211
- }
212
- };
213
- const reset = () => {
214
- onChange(null);
215
- setWizStep(0);
216
- setPerson(undefined);
217
- setPersonTouched(false);
218
- setDate(undefined);
219
- setSlot(undefined);
220
- setDays(undefined);
221
- setHoldError(undefined);
222
- setContactName('');
223
- setContactEmail('');
224
- };
225
- const pickPerson = (p) => {
226
- setPerson(p);
227
- setPersonTouched(true);
228
- setSlot(undefined);
229
- };
230
- // Read-only / already booked → compact summary with the same card aesthetic
231
- if (readOnly || value) {
232
- 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
233
- ? [
234
- value.personName,
235
- capitalize(longDate(new Date(value.date + 'T00:00:00'))),
236
- `${value.startTime}–${value.endTime}`,
237
- ]
238
- .filter(Boolean)
239
- .join(' · ')
240
- : 'Sin cita' })] }), value && !readOnly && _jsx(ErkButton, { text: "Cambiar", variant: "outlined", onClick: reset })] }) }));
241
- }
242
- const isFinal = wizStep === finalIdx;
243
- const personLabel = person?.name ?? (personTouched && personsOptional ? 'Cualquiera' : undefined);
244
- const dayText = date ? capitalize(longDate(date)) : undefined;
245
- const hourText = slot ? `${slot.startTime} – ${slot.endTime}` : undefined;
246
- const summaryRows = [
247
- ...(needsPerson ? [{ key: 'person', placeholder: 'Persona', text: personLabel }] : []),
248
- { key: 'day', placeholder: 'Día', text: dayText },
249
- { key: 'hour', placeholder: 'Hora', text: hourText },
250
- ];
251
- const navParts = [];
252
- if (personLabel)
253
- navParts.push(_jsx("b", { children: personLabel }, "p"));
254
- if (dayText)
255
- navParts.push(_jsx("span", { children: dayText }, "d"));
256
- if (hourText)
257
- navParts.push(_jsx("span", { className: styles.navHl, children: hourText }, "h"));
258
- 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", disabled: disabled, 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
259
- ? `${styles.stepItem} ${styles.stepDone}`
260
- : i === wizStep
261
- ? `${styles.stepItem} ${styles.stepOn}`
262
- : 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
263
- ? 'Elige una persona o deja que el sistema asigne a quien esté disponible.'
264
- : '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
265
- ? `${styles.pcard} ${styles.pcardOn}`
266
- : 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
267
- ? `${styles.pcard} ${styles.pcardAny} ${styles.pcardOn}`
268
- : `${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
269
- ? 'Solo se pueden elegir los días con horarios disponibles.'
270
- : '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) => {
271
- const outside = !isSameMonth(d, viewMonth);
272
- const past = isBefore(d, today);
273
- const offered = !days || days.includes(fmtDate(d));
274
- const selectable = !outside && !past && offered;
275
- const cls = date && isSameDay(d, date)
276
- ? `${styles.mday} ${styles.mdaySel}`
277
- : selectable
278
- ? `${styles.mday} ${styles.mdayFree}`
279
- : `${styles.mday} ${styles.mdayNone}`;
280
- return (_jsx("button", { type: "button", disabled: !selectable, className: cls, onClick: () => {
281
- setDate(d);
282
- setSlot(undefined);
283
- }, children: format(d, 'd') }, d.toISOString()));
284
- })] })] }), 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 ", durationMinutes, " min"] }), holdError && (_jsx("div", { className: styles.err, role: "alert", children: holdError })), 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
285
- ? `${styles.slot} ${styles.slotSel}`
286
- : s.available
287
- ? styles.slot
288
- : `${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
289
- ? 'Si todo está bien, completa tus datos y confirma.'
290
- : '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
291
- ? STEP_HINT[currentKey]
292
- : 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 ? 'Reservando…' : 'Confirmar cita', 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 })] }));
293
- }
294
- export default ErkBookingPicker;