@arquimedes.co/eureka-forms 3.0.57-new-steps → 3.0.57

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 (120) hide show
  1. package/dist/@Types/FormStep.d.ts +2 -7
  2. package/dist/@Types/FormStep.js +1 -2
  3. package/dist/@Types/GenericFormSteps.d.ts +7 -0
  4. package/dist/App/App.cbr.test.js +78 -0
  5. package/dist/App/App.d.ts +6 -1
  6. package/dist/App/AppHooks.js +2 -2
  7. package/dist/FormSteps/CheckBoxStep/CheckBoxStep.mobileLabel.test.js +123 -0
  8. package/dist/FormSteps/CheckBoxStep/MaterialCheckBoxStep/MaterialCheckBoxStep.js +34 -7
  9. package/dist/FormSteps/CheckBoxStep/MaterialCheckBoxStep/MaterialCheckBoxStep.module.css +30 -0
  10. package/dist/FormSteps/MapperStep/MaterialMapperStep/Element/MapperElementComponent.js +1 -1
  11. package/dist/FormSteps/PhoneInputStep/MaterialTextInputStep/MaterialPhoneInputStep.js +5 -4
  12. package/dist/FormSteps/Step.js +4 -19
  13. package/dist/FormSteps/StepFunctions.d.ts +16 -1
  14. package/dist/FormSteps/StepFunctions.js +58 -14
  15. package/dist/FormSteps/StepFunctions.test.js +32 -55
  16. package/dist/FormSteps/TextInputStep/MaterialTextInputStep/MaterialTextInputStep.js +5 -4
  17. package/dist/FormSteps/TitleStep/MaterialTitleStep/MaterialTitleStep.js +4 -4
  18. package/dist/FormSteps/Utils/@StepFiller/StepFiller.js +5 -4
  19. package/dist/FormSteps/Utils/MaterialInputContainer/MaterialInputContainer.js +5 -4
  20. package/dist/Shared/ErkDatePicker/ErkDatePicker.js +7 -5
  21. package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.d.ts +3 -2
  22. package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.js +5 -4
  23. package/dist/Utils/CBRFunctions.js +4 -2
  24. package/dist/Utils/CBRFunctions.test.d.ts +1 -0
  25. package/dist/Utils/CBRFunctions.test.js +123 -0
  26. package/dist/Utils/MaterialProviders.js +6 -0
  27. package/dist/constants/FormStepTypes.d.ts +1 -5
  28. package/dist/constants/FormStepTypes.js +0 -4
  29. package/dist/hooks.d.ts +1 -0
  30. package/dist/hooks.js +1 -0
  31. package/dist/index.lib.d.ts +2 -4
  32. package/dist/index.lib.js +1 -3
  33. package/package.json +7 -8
  34. package/dist/@Types/AvailabilityFormStep.d.ts +0 -48
  35. package/dist/@Types/AvailabilityFormStep.js +0 -24
  36. package/dist/@Types/BankAccountFormStep.d.ts +0 -67
  37. package/dist/@Types/BankAccountFormStep.js +0 -14
  38. package/dist/@Types/BookingFormStep.d.ts +0 -166
  39. package/dist/@Types/BookingFormStep.js +0 -44
  40. package/dist/@Types/CalendarFormStep.d.ts +0 -209
  41. package/dist/@Types/CalendarFormStep.js +0 -55
  42. package/dist/@Types/CommunicationChannelFormStep.d.ts +0 -51
  43. package/dist/@Types/CommunicationChannelFormStep.js +0 -15
  44. package/dist/@Types/ConsentFormStep.d.ts +0 -36
  45. package/dist/@Types/ConsentFormStep.js +0 -8
  46. package/dist/@Types/EntityFormFormStep.d.ts +0 -66
  47. package/dist/@Types/EntityFormFormStep.js +0 -20
  48. package/dist/@Types/EventFormStep.d.ts +0 -52
  49. package/dist/@Types/EventFormStep.js +0 -13
  50. package/dist/@Types/ExternalReferenceFormStep.d.ts +0 -74
  51. package/dist/@Types/ExternalReferenceFormStep.js +0 -20
  52. package/dist/@Types/LocationFormStep.d.ts +0 -159
  53. package/dist/@Types/LocationFormStep.js +0 -46
  54. package/dist/@Types/NumericFormSteps.d.ts +0 -108
  55. package/dist/@Types/NumericFormSteps.js +0 -20
  56. package/dist/@Types/PredefinedSelectorFormStep.d.ts +0 -51
  57. package/dist/@Types/PredefinedSelectorFormStep.js +0 -39
  58. package/dist/@Types/StepRef.d.ts +0 -135
  59. package/dist/@Types/StepRef.js +0 -49
  60. package/dist/FormSteps/AvailabilityStep/AvailabilityStep.d.ts +0 -7
  61. package/dist/FormSteps/AvailabilityStep/AvailabilityStep.js +0 -14
  62. package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.d.ts +0 -8
  63. package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.js +0 -32
  64. package/dist/FormSteps/BookingStep/BookingStep.d.ts +0 -17
  65. package/dist/FormSteps/BookingStep/BookingStep.js +0 -14
  66. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.d.ts +0 -7
  67. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +0 -32
  68. package/dist/FormSteps/LocationStep/LocationStep.d.ts +0 -10
  69. package/dist/FormSteps/LocationStep/LocationStep.js +0 -14
  70. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.d.ts +0 -3
  71. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.js +0 -54
  72. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.module.css +0 -18
  73. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.d.ts +0 -3
  74. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.js +0 -168
  75. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.module.css +0 -94
  76. package/dist/FormSteps/NumberStep/NumberStep.d.ts +0 -10
  77. package/dist/FormSteps/NumberStep/NumberStep.js +0 -14
  78. package/dist/Icons/AddIcon.d.ts +0 -3
  79. package/dist/Icons/AddIcon.js +0 -7
  80. package/dist/Icons/EditIcon.d.ts +0 -3
  81. package/dist/Icons/EditIcon.js +0 -7
  82. package/dist/Icons/RemoveIcon.d.ts +0 -3
  83. package/dist/Icons/RemoveIcon.js +0 -7
  84. package/dist/Icons/ViewDayIcon.d.ts +0 -3
  85. package/dist/Icons/ViewDayIcon.js +0 -7
  86. package/dist/Icons/ViewWeekIcon.d.ts +0 -3
  87. package/dist/Icons/ViewWeekIcon.js +0 -7
  88. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.d.ts +0 -19
  89. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.js +0 -80
  90. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.module.css +0 -82
  91. package/dist/Shared/ErkAddressAutocomplete/googlePlaces.d.ts +0 -29
  92. package/dist/Shared/ErkAddressAutocomplete/googlePlaces.js +0 -139
  93. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.d.ts +0 -25
  94. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.js +0 -112
  95. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.module.css +0 -88
  96. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.test.js +0 -75
  97. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.d.ts +0 -31
  98. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.js +0 -223
  99. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.module.css +0 -608
  100. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.d.ts +0 -28
  101. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.js +0 -117
  102. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.js +0 -49
  103. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.d.ts +0 -1
  104. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.js +0 -107
  105. package/dist/Shared/ErkLocationField/ErkLocationField.d.ts +0 -24
  106. package/dist/Shared/ErkLocationField/ErkLocationField.js +0 -125
  107. package/dist/Shared/ErkLocationField/ErkLocationField.module.css +0 -153
  108. package/dist/Shared/ErkLocationField/ErkLocationField.test.d.ts +0 -1
  109. package/dist/Shared/ErkLocationField/ErkLocationField.test.js +0 -82
  110. package/dist/Shared/ErkLocationField/addressFields.d.ts +0 -12
  111. package/dist/Shared/ErkLocationField/addressFields.js +0 -34
  112. package/dist/Shared/ErkLocationField/locationRestrictions.d.ts +0 -6
  113. package/dist/Shared/ErkLocationField/locationRestrictions.js +0 -51
  114. package/dist/Shared/ErkLocationInput/ErkLocationInput.d.ts +0 -15
  115. package/dist/Shared/ErkLocationInput/ErkLocationInput.js +0 -502
  116. package/dist/Shared/ErkLocationInput/ErkLocationInput.module.css +0 -115
  117. package/dist/Shared/ErkNumberField/ErkNumberField.d.ts +0 -42
  118. package/dist/Shared/ErkNumberField/ErkNumberField.js +0 -170
  119. /package/dist/{Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.test.d.ts → App/App.cbr.test.d.ts} +0 -0
  120. /package/dist/{Shared/ErkBookingPicker/ErkBookingPicker.stories.test.d.ts → FormSteps/CheckBoxStep/CheckBoxStep.mobileLabel.test.d.ts} +0 -0
@@ -1,112 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo } from 'react';
3
- import { addDays, format, startOfWeek } from 'date-fns';
4
- import { CalendarEventCategory, } from '../../@Types/CalendarFormStep';
5
- import styles from './ErkAvailabilityPicker.module.css';
6
- const DOW_LABELS = ['L', 'M', 'M', 'J', 'V', 'S', 'D'];
7
- const DOW_NAMES = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'];
8
- function parseHour(hhmm) {
9
- return Number(hhmm.split(':')[0]) || 0;
10
- }
11
- /** Ancla del día `dayIdx` (0=Lun … 6=Dom) en la semana actual. */
12
- function anchorDate(dayIdx) {
13
- return addDays(startOfWeek(new Date(), { weekStartsOn: 1 }), dayIdx);
14
- }
15
- function cellISO(dayIdx, hour) {
16
- const day = anchorDate(dayIdx);
17
- return `${format(day, 'yyyy-MM-dd')}T${String(hour).padStart(2, '0')}:00:00`;
18
- }
19
- /** `Date.getDay()` → índice de columna (0=Lun … 6=Dom). */
20
- function dayToIdx(day) {
21
- return (day + 6) % 7;
22
- }
23
- const RRULE_DAY_CODES = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
24
- /**
25
- * Expande el value a celdas marcadas. Soporta las ventanas que el propio picker
26
- * genera (semanales por día) y BYDAY (ej: L–V) por si el valor viene de otra fuente.
27
- */
28
- function valueToCells(value) {
29
- const cells = new Set();
30
- for (const event of value?.events ?? []) {
31
- const startHour = parseHour(event.start.slice(11, 16));
32
- const endHour = parseHour(event.end.slice(11, 16)) || 24;
33
- const byday = /BYDAY=([A-Z,]+)/.exec(event.recurrence ?? '');
34
- const dayIdxs = byday
35
- ? byday[1]
36
- .split(',')
37
- .map((code) => RRULE_DAY_CODES.indexOf(code))
38
- .filter((d) => d >= 0)
39
- .map(dayToIdx)
40
- : [dayToIdx(new Date(`${event.start.slice(0, 10)}T00:00:00`).getDay())];
41
- for (const dayIdx of dayIdxs) {
42
- for (let h = startHour; h < endHour; h++)
43
- cells.add(`${dayIdx}:${h}`);
44
- }
45
- }
46
- return cells;
47
- }
48
- /** Convierte las celdas marcadas en ventanas semanales contiguas por día. */
49
- function cellsToEvents(cells, hours) {
50
- const events = [];
51
- for (let dayIdx = 0; dayIdx < 7; dayIdx++) {
52
- let windowStart = null;
53
- for (const h of hours) {
54
- const marked = cells.has(`${dayIdx}:${h}`);
55
- if (marked && windowStart === null)
56
- windowStart = h;
57
- if (!marked && windowStart !== null) {
58
- events.push(windowEvent(dayIdx, windowStart, h));
59
- windowStart = null;
60
- }
61
- }
62
- if (windowStart !== null) {
63
- events.push(windowEvent(dayIdx, windowStart, hours[hours.length - 1] + 1));
64
- }
65
- }
66
- return events;
67
- }
68
- function windowEvent(dayIdx, startHour, endHour) {
69
- return {
70
- id: `avail-${dayIdx}-${startHour}-${endHour}`,
71
- category: CalendarEventCategory.WORKING,
72
- start: cellISO(dayIdx, startHour),
73
- end: cellISO(dayIdx, endHour),
74
- recurrence: 'FREQ=WEEKLY',
75
- };
76
- }
77
- /**
78
- * Picker compacto de disponibilidad para el paso CALENDAR/Availability del form:
79
- * un grid semanal pequeño (día × hora) donde quien responde marca sus ventanas
80
- * disponibles. Produce ventanas WORKING con recurrencia semanal (RRULE), las que
81
- * un paso BOOKING puede usar como disponibilidad.
82
- */
83
- function ErkAvailabilityPicker({ value, onChange, label, description, minTime = '07:00', maxTime = '19:00', showWeekends = true, timezone, readOnly = false, error, }) {
84
- const minHour = parseHour(minTime);
85
- const maxHour = parseHour(maxTime);
86
- const hours = useMemo(() => Array.from({ length: Math.max(maxHour - minHour, 1) }, (_, i) => minHour + i), [minHour, maxHour]);
87
- const dayIdxs = showWeekends ? [0, 1, 2, 3, 4, 5, 6] : [0, 1, 2, 3, 4];
88
- const cells = useMemo(() => valueToCells(value), [value]);
89
- const toggle = (dayIdx, hour) => {
90
- if (readOnly)
91
- return;
92
- const next = new Set(cells);
93
- const key = `${dayIdx}:${hour}`;
94
- if (next.has(key))
95
- next.delete(key);
96
- else
97
- next.add(key);
98
- onChange({
99
- type: 'CALENDAR_VALUE',
100
- events: cellsToEvents(next, hours),
101
- timezone: value?.timezone ?? timezone,
102
- });
103
- };
104
- const markedCount = cells.size;
105
- return (_jsxs("div", { className: styles.container, children: [_jsx("span", { className: styles.label, children: label }), description && _jsx("p", { className: styles.description, children: description }), _jsxs("div", { className: styles.card, children: [_jsxs("div", { className: styles.grid, style: { gridTemplateColumns: `44px repeat(${dayIdxs.length}, 1fr)` }, children: [_jsx("span", { className: styles.corner }), dayIdxs.map((d) => (_jsx("span", { className: styles.dow, children: DOW_LABELS[d] }, d))), hours.map((h) => (_jsxs("div", { className: styles.row, children: [_jsxs("span", { className: styles.hour, children: [h, ":00"] }), dayIdxs.map((d) => {
106
- const on = cells.has(`${d}:${h}`);
107
- return (_jsx("button", { type: "button", disabled: readOnly, "aria-pressed": on, "aria-label": `${DOW_NAMES[d]} ${h}:00`, className: on ? `${styles.cell} ${styles.cellOn}` : styles.cell, onClick: () => toggle(d, h) }, d));
108
- })] }, h)))] }), _jsx("div", { className: styles.footer, children: markedCount > 0
109
- ? 'Disponible cada semana en las horas marcadas.'
110
- : 'Marca las horas en las que estás disponible cada semana.' })] }), error && _jsx("div", { className: styles.err, children: error })] }));
111
- }
112
- export default ErkAvailabilityPicker;
@@ -1,88 +0,0 @@
1
- .container {
2
- max-width: 100%;
3
- box-sizing: border-box;
4
- display: flex;
5
- flex-direction: column;
6
- gap: 4px;
7
- }
8
- .label {
9
- font-size: 14.5px;
10
- font-weight: 700;
11
- color: var(--eureka-text, #293241);
12
- }
13
- .description {
14
- margin: 0 0 4px;
15
- font-size: 12.5px;
16
- color: #787878;
17
- }
18
-
19
- .card {
20
- border: 1px solid var(--eureka-outline, #d9dee6);
21
- border-radius: 12px;
22
- overflow: hidden;
23
- background: var(--eureka-bg, #fff);
24
- max-width: 460px;
25
- }
26
- .grid {
27
- display: grid;
28
- padding: 10px 12px 4px;
29
- gap: 2px;
30
- font-variant-numeric: tabular-nums;
31
- }
32
- .corner {
33
- grid-column: 1;
34
- }
35
- .dow {
36
- text-align: center;
37
- font-size: 10.5px;
38
- font-weight: 700;
39
- color: #787878;
40
- text-transform: uppercase;
41
- padding-bottom: 2px;
42
- }
43
- .row {
44
- display: contents;
45
- }
46
- .hour {
47
- font-size: 10.5px;
48
- color: #787878;
49
- text-align: right;
50
- padding-right: 6px;
51
- align-self: center;
52
- }
53
- .cell {
54
- height: 26px;
55
- border: 1px solid var(--eureka-outline, #eceff3);
56
- border-radius: 6px;
57
- background: #fbfcfd;
58
- cursor: pointer;
59
- padding: 0;
60
- }
61
- .cell:hover {
62
- border-color: var(--eureka-primary, #3d5a7f);
63
- }
64
- .cell:disabled {
65
- cursor: default;
66
- }
67
- .cellOn {
68
- background: repeating-linear-gradient(
69
- -45deg,
70
- color-mix(in srgb, var(--eureka-primary, #3d5a7f) 30%, #fff),
71
- color-mix(in srgb, var(--eureka-primary, #3d5a7f) 30%, #fff) 5px,
72
- color-mix(in srgb, var(--eureka-primary, #3d5a7f) 18%, #fff) 5px,
73
- color-mix(in srgb, var(--eureka-primary, #3d5a7f) 18%, #fff) 10px
74
- );
75
- border-color: var(--eureka-primary, #3d5a7f);
76
- }
77
- .footer {
78
- padding: 8px 12px 10px;
79
- font-size: 11.5px;
80
- color: #787878;
81
- border-top: 1px solid var(--eureka-outline, #eceff3);
82
- background: color-mix(in srgb, var(--eureka-primary, #3d5a7f) 2%, #fff);
83
- }
84
- .err {
85
- font-size: 12px;
86
- color: #d0473f;
87
- margin-top: 2px;
88
- }
@@ -1,75 +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: [
53
- expect.objectContaining({ start: iso(0, '09:00'), end: iso(0, '11:00') }),
54
- ],
55
- }));
56
- });
57
- it('renders existing windows as pressed cells and blocks toggling when readOnly', () => {
58
- const value = {
59
- type: 'CALENDAR_VALUE',
60
- events: [
61
- {
62
- id: 'avail-2-14-16',
63
- category: CalendarEventCategory.WORKING,
64
- start: iso(2, '14:00'),
65
- end: iso(2, '16:00'),
66
- recurrence: 'FREQ=WEEKLY',
67
- },
68
- ],
69
- };
70
- render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkAvailabilityPicker, { value: value, onChange: () => { }, label: "Disponibilidad", minTime: "13:00", maxTime: "17:00", readOnly: true }) }));
71
- expect(screen.getByRole('button', { name: 'Miércoles 14:00', pressed: true })).toBeDisabled();
72
- expect(screen.getByRole('button', { name: 'Miércoles 15:00', pressed: true })).toBeInTheDocument();
73
- expect(screen.getByRole('button', { name: 'Miércoles 16:00', pressed: false })).toBeInTheDocument();
74
- });
75
- });
@@ -1,31 +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
- /** Provee los slots disponibles para una persona + día. */
16
- fetchSlots?: (args: {
17
- personId?: string;
18
- date: string;
19
- }) => Promise<BookingSlot[]>;
20
- readOnly?: boolean;
21
- error?: string;
22
- }
23
- /**
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.
29
- */
30
- declare function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bufferMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, collectContact, fetchSlots, readOnly, error, }: ErkBookingPickerProps): JSX.Element;
31
- export default ErkBookingPicker;
@@ -1,223 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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, fetchSlots, 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
- const reduceMotion = useMemo(() => typeof window !== 'undefined' && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches, []);
81
- const monthCells = useMemo(() => {
82
- const first = startOfWeek(startOfMonth(viewMonth), { weekStartsOn: WEEK_STARTS_ON });
83
- return Array.from({ length: 42 }, (_, i) => addDays(first, i));
84
- }, [viewMonth]);
85
- const loadSlots = useCallback(async (day, personId) => {
86
- if (!fetchSlots)
87
- return;
88
- setLoading(true);
89
- setSlots([]);
90
- try {
91
- setSlots(await fetchSlots({ personId, date: fmtDate(day) }));
92
- }
93
- catch {
94
- setSlots([]);
95
- }
96
- finally {
97
- setLoading(false);
98
- }
99
- }, [fetchSlots]);
100
- useEffect(() => {
101
- if (value || !date)
102
- return;
103
- void loadSlots(date, person?.id);
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
- })();
132
- const confirm = () => {
133
- if (!slot || !date)
134
- return;
135
- setOpen(false);
136
- onChange({
137
- type: 'BOOKING_VALUE',
138
- slotId: slot.id,
139
- date: fmtDate(date),
140
- startTime: slot.startTime,
141
- endTime: slot.endTime,
142
- personId: person?.id,
143
- personName: person?.name,
144
- contactName: contactName || undefined,
145
- contactEmail: contactEmail || undefined,
146
- status: 'PENDING',
147
- });
148
- };
149
- const reset = () => {
150
- onChange(null);
151
- setWizStep(0);
152
- setPerson(undefined);
153
- setPersonTouched(false);
154
- setDate(undefined);
155
- setSlot(undefined);
156
- setContactName('');
157
- setContactEmail('');
158
- };
159
- const pickPerson = (p) => {
160
- setPerson(p);
161
- setPersonTouched(true);
162
- setSlot(undefined);
163
- };
164
- // Read-only / already booked → compact summary with the same card aesthetic
165
- if (readOnly || 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
167
- ? [
168
- value.personName,
169
- capitalize(longDate(new Date(value.date + 'T00:00:00'))),
170
- `${value.startTime}–${value.endTime}`,
171
- ]
172
- .filter(Boolean)
173
- .join(' · ')
174
- : 'Sin cita' })] }), value && !readOnly && _jsx(ErkButton, { text: "Cambiar", variant: "outlined", onClick: reset })] }) }));
175
- }
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 })] }));
222
- }
223
- export default ErkBookingPicker;