@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.
- package/dist/BookingManage/BookingManagePage.test.js +0 -1
- package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +3 -1
- package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.test.js +20 -14
- package/dist/Icons/CheckIcon.d.ts +3 -0
- package/dist/Icons/CheckIcon.js +7 -0
- package/dist/Icons/VideoIcon.d.ts +3 -0
- package/dist/Icons/VideoIcon.js +7 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.d.ts +19 -16
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.js +204 -132
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.module.css +606 -429
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.d.ts +9 -5
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.js +37 -5
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.js +18 -15
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.js +178 -52
- package/package.json +1 -1
|
@@ -100,7 +100,6 @@ describe('BookingManagePage', () => {
|
|
|
100
100
|
renderPage();
|
|
101
101
|
await user.click(await screen.findByRole('button', { name: 'Elegir otra hora' }));
|
|
102
102
|
await user.click(await screen.findByRole('button', { name: format(TOMORROW, "EEEE d 'de' MMMM 'de' yyyy", { locale: es }) }));
|
|
103
|
-
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
104
103
|
await user.click(await screen.findByRole('button', { name: /^10:00/ }));
|
|
105
104
|
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
106
105
|
expect(await screen.findByRole('heading', { name: 'Revisa la nueva hora' })).toBeInTheDocument();
|
|
@@ -67,6 +67,8 @@ function MaterialBookingStep({ step, editable, fetchSlots: customFetchSlots }) {
|
|
|
67
67
|
'--eureka-text': formStyle.textColor,
|
|
68
68
|
'--eureka-bg': formStyle.standAloneBackgroundColor ?? '#fff',
|
|
69
69
|
'--booking-accent': formStyle.secondaryContrastColor,
|
|
70
|
+
'--eureka-description': formStyle.descriptionTextColor,
|
|
71
|
+
'--eureka-error': formStyle.errorColor,
|
|
70
72
|
};
|
|
71
73
|
const { ref, value, onChange, error } = useFormStep(step, {
|
|
72
74
|
defaultValue: null,
|
|
@@ -284,6 +286,6 @@ function MaterialBookingStep({ step, editable, fetchSlots: customFetchSlots }) {
|
|
|
284
286
|
const personSelectionMode = usesEdge
|
|
285
287
|
? (offer?.personSelection ?? PersonSelectionMode.NONE)
|
|
286
288
|
: step.personSelectionMode;
|
|
287
|
-
return (_jsx("div", { ref: ref, style: cssVars, children: _jsx(ErkBookingPicker, { value: value, onChange: handleChange, label: step.label, description: step.description, durationMinutes: offer?.durationMinutes ?? step.durationMinutes, bookingMode: step.bookingMode, temporaryLockMinutes: step.temporaryLockMinutes, personSelectionMode: personSelectionMode, persons: usesEdge ? persons : step.persons, eventTemplates: usesEdge ? eventTemplates : undefined, timeZone: offer?.timeZone, knownContact: knownContact, askEmail: askEmail, lapsed: lapsed, fetchSlots: usesEdge ? fetchSlots : customFetchSlots, fetchDays: usesEdge ? fetchDays : undefined, holdSlot: usesEdge ? holdSlot : undefined, disabled: usesEdge && !offer, readOnly: isReadOnly, error: error?.message, loadError: usesEdge ? offerError : undefined, onRetryLoad: retryOffer }) }));
|
|
289
|
+
return (_jsx("div", { ref: ref, style: cssVars, children: _jsx(ErkBookingPicker, { value: value, onChange: handleChange, label: step.label, description: step.description, required: step.required, durationMinutes: offer?.durationMinutes ?? step.durationMinutes, bookingMode: step.bookingMode, temporaryLockMinutes: step.temporaryLockMinutes, personSelectionMode: personSelectionMode, persons: usesEdge ? persons : step.persons, eventTemplates: usesEdge ? eventTemplates : undefined, timeZone: offer?.timeZone, knownContact: knownContact, askEmail: askEmail, lapsed: lapsed, fetchSlots: usesEdge ? fetchSlots : customFetchSlots, fetchDays: usesEdge ? fetchDays : undefined, holdSlot: usesEdge ? holdSlot : undefined, disabled: usesEdge && !offer, readOnly: isReadOnly, error: error?.message, loadError: usesEdge ? offerError : undefined, onRetryLoad: retryOffer }) }));
|
|
288
290
|
}
|
|
289
291
|
export default MaterialBookingStep;
|
|
@@ -98,12 +98,11 @@ function today() {
|
|
|
98
98
|
return screen.getByRole('button', { name: format(new Date(), "EEEE d 'de' MMMM 'de' yyyy", { locale: es }) });
|
|
99
99
|
}
|
|
100
100
|
async function reserveFirstHour(user) {
|
|
101
|
-
const open = await screen.findByRole('button', { name: '
|
|
101
|
+
const open = await screen.findByRole('button', { name: 'Elegir horario' });
|
|
102
102
|
await waitFor(() => expect(open).toBeEnabled());
|
|
103
103
|
await user.click(open);
|
|
104
104
|
await waitFor(() => expect(today()).toBeEnabled());
|
|
105
105
|
await user.click(today());
|
|
106
|
-
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
107
106
|
await user.click(await screen.findByRole('button', { name: /^09:00/ }));
|
|
108
107
|
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
109
108
|
await user.click(await screen.findByRole('button', { name: 'Reservar' }));
|
|
@@ -127,8 +126,9 @@ describe('MaterialBookingStep', () => {
|
|
|
127
126
|
contact: { name: 'Ana Gómez', email: 'ana@correo.com' },
|
|
128
127
|
});
|
|
129
128
|
// Hours and person come from the hold, not from the button that was clicked.
|
|
130
|
-
expect(await screen.findByText(/09:15–09:45
|
|
131
|
-
expect(screen.getByText(
|
|
129
|
+
expect(await screen.findByText(/ · 09:15 – 09:45$/)).toBeInTheDocument();
|
|
130
|
+
expect(screen.getByText('Bruno Pérez · Reservado hasta las 08:00')).toBeInTheDocument();
|
|
131
|
+
expect(screen.getByText(/Te confirmaremos a/)).toHaveTextContent('Te confirmaremos a ana@correo.com cuando envíes el formulario.');
|
|
132
132
|
});
|
|
133
133
|
it('asks for the email in the picker when the step is set to FIELD', async () => {
|
|
134
134
|
const user = userEvent.setup();
|
|
@@ -136,12 +136,11 @@ describe('MaterialBookingStep', () => {
|
|
|
136
136
|
'email-step': 'ana@correo.com',
|
|
137
137
|
'name-step': 'Ana Gómez',
|
|
138
138
|
});
|
|
139
|
-
const open = await screen.findByRole('button', { name: '
|
|
139
|
+
const open = await screen.findByRole('button', { name: 'Elegir horario' });
|
|
140
140
|
await waitFor(() => expect(open).toBeEnabled());
|
|
141
141
|
await user.click(open);
|
|
142
142
|
await waitFor(() => expect(today()).toBeEnabled());
|
|
143
143
|
await user.click(today());
|
|
144
|
-
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
145
144
|
await user.click(await screen.findByRole('button', { name: /^09:00/ }));
|
|
146
145
|
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
147
146
|
expect(await screen.findByLabelText(/Correo/)).toBeInTheDocument();
|
|
@@ -171,7 +170,7 @@ describe('MaterialBookingStep', () => {
|
|
|
171
170
|
});
|
|
172
171
|
expect(renewBookingHold).toHaveBeenLastCalledWith(expect.anything(), { renew: 'renew-2', hold: 'secret-1' });
|
|
173
172
|
expect(await screen.findByRole('alert')).toHaveTextContent('Tu reserva venció; elige la hora de nuevo.');
|
|
174
|
-
expect(screen.getByRole('button', { name: '
|
|
173
|
+
expect(screen.getByRole('button', { name: 'Elegir horario' })).toBeInTheDocument();
|
|
175
174
|
});
|
|
176
175
|
it('reloads the offer and retries once when a person token went stale', async () => {
|
|
177
176
|
const user = userEvent.setup();
|
|
@@ -196,14 +195,13 @@ describe('MaterialBookingStep', () => {
|
|
|
196
195
|
'email-step': 'ana@correo.com',
|
|
197
196
|
'name-step': 'Ana Gómez',
|
|
198
197
|
});
|
|
199
|
-
const open = await screen.findByRole('button', { name: '
|
|
198
|
+
const open = await screen.findByRole('button', { name: 'Elegir horario' });
|
|
200
199
|
await waitFor(() => expect(open).toBeEnabled());
|
|
201
200
|
await user.click(open);
|
|
202
201
|
await user.click(await screen.findByText('Dra. Ruiz'));
|
|
203
202
|
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
204
203
|
await waitFor(() => expect(today()).toBeEnabled());
|
|
205
204
|
await user.click(today());
|
|
206
|
-
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
207
205
|
expect(await screen.findByRole('button', { name: /^09:00/ })).toBeInTheDocument();
|
|
208
206
|
expect(vi.mocked(fetchBookingSlots).mock.calls.map((call) => call[2]?.person)).toEqual(['p-old', 'p-new']);
|
|
209
207
|
});
|
|
@@ -222,14 +220,13 @@ describe('MaterialBookingStep', () => {
|
|
|
222
220
|
eventTemplate: { name: 'Asesoría de crédito', durationMinutes: 45 },
|
|
223
221
|
});
|
|
224
222
|
renderStep(bookingStep(), { 'email-step': 'ana@correo.com', 'name-step': 'Ana Gómez' });
|
|
225
|
-
const open = await screen.findByRole('button', { name: '
|
|
223
|
+
const open = await screen.findByRole('button', { name: 'Elegir horario' });
|
|
226
224
|
await waitFor(() => expect(open).toBeEnabled());
|
|
227
225
|
await user.click(open);
|
|
228
226
|
await user.click(await screen.findByRole('button', { name: /Asesoría de crédito/ }));
|
|
229
227
|
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
230
228
|
await waitFor(() => expect(today()).toBeEnabled());
|
|
231
229
|
await user.click(today());
|
|
232
|
-
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
233
230
|
await user.click(await screen.findByRole('button', { name: /^09:00/ }));
|
|
234
231
|
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
235
232
|
await user.click(await screen.findByRole('button', { name: 'Reservar' }));
|
|
@@ -240,7 +237,7 @@ describe('MaterialBookingStep', () => {
|
|
|
240
237
|
contact: { name: 'Ana Gómez', email: 'ana@correo.com' },
|
|
241
238
|
eventTemplate: 'tt-2',
|
|
242
239
|
});
|
|
243
|
-
expect(await screen.findByText(
|
|
240
|
+
expect(await screen.findByText(/^Bruno Pérez · Asesoría de crédito · 45 min · Reservado hasta/)).toBeInTheDocument();
|
|
244
241
|
});
|
|
245
242
|
it('asks for no type when the step does not let the requester pick one', async () => {
|
|
246
243
|
const user = userEvent.setup();
|
|
@@ -266,10 +263,19 @@ describe('MaterialBookingStep', () => {
|
|
|
266
263
|
.mockResolvedValue(OFFER);
|
|
267
264
|
renderStep(bookingStep(), { 'email-step': 'ana@correo.com', 'name-step': 'Ana Gómez' });
|
|
268
265
|
expect(await screen.findByRole('alert')).toHaveTextContent('Algo falló');
|
|
269
|
-
expect(screen.getByRole('button', { name: '
|
|
266
|
+
expect(screen.getByRole('button', { name: 'Elegir horario' })).toBeDisabled();
|
|
270
267
|
await user.click(screen.getByRole('button', { name: 'Reintentar' }));
|
|
271
|
-
await waitFor(() => expect(screen.getByRole('button', { name: '
|
|
268
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Elegir horario' })).toBeEnabled());
|
|
272
269
|
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
|
273
270
|
expect(fetchBookingOffer).toHaveBeenCalledTimes(2);
|
|
274
271
|
});
|
|
272
|
+
it('tells the requester before opening where the confirmation will go', async () => {
|
|
273
|
+
renderStep(bookingStep({ required: true }), { 'email-step': 'ana@correo.com', 'name-step': 'Ana Gómez' });
|
|
274
|
+
expect(await screen.findByText(/Te confirmaremos a/)).toHaveTextContent('Te confirmaremos a ana@correo.com cuando envíes el formulario.');
|
|
275
|
+
expect(screen.getByText('Agendar cita *')).toBeInTheDocument();
|
|
276
|
+
});
|
|
277
|
+
it('promises to ask for name and email when no earlier step gives them', async () => {
|
|
278
|
+
renderStep(bookingStep({ contactEmail: { source: BookingContactEmailSourceTypes.FIELD } }), {});
|
|
279
|
+
expect(await screen.findByText('Te pediremos nombre y correo al reservar.')).toBeInTheDocument();
|
|
280
|
+
});
|
|
275
281
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useIconProps } from './@ErkIcon';
|
|
3
|
+
function CheckIcon(props) {
|
|
4
|
+
const iconProps = useIconProps(props);
|
|
5
|
+
return (_jsx("svg", { ...iconProps, version: "1.1", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M9 16.17 5.53 12.7a.9959.9959 0 0 0-1.41 0c-.39.39-.39 1.02 0 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71c.39-.39.39-1.02 0-1.41a.9959.9959 0 0 0-1.41 0L9 16.17z" }) }));
|
|
6
|
+
}
|
|
7
|
+
export default CheckIcon;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useIconProps } from './@ErkIcon';
|
|
3
|
+
function VideoIcon(props) {
|
|
4
|
+
const iconProps = useIconProps(props);
|
|
5
|
+
return (_jsx("svg", { ...iconProps, version: "1.1", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l2.29 2.29c.63.63 1.71.18 1.71-.71V8.91c0-.89-1.08-1.34-1.71-.71L17 10.5z" }) }));
|
|
6
|
+
}
|
|
7
|
+
export default VideoIcon;
|
|
@@ -4,15 +4,15 @@ import { BookablePerson, BookingEventTemplateOption, BookingMode, BookingSlot, B
|
|
|
4
4
|
* (la página de "Elegir otra hora" del enlace del correo).
|
|
5
5
|
*/
|
|
6
6
|
export interface BookingPickerCopy {
|
|
7
|
-
/** Botón
|
|
7
|
+
/** Botón que abre el selector. */
|
|
8
8
|
open: string;
|
|
9
|
-
/** Paso final
|
|
9
|
+
/** Paso final y su botón. */
|
|
10
10
|
confirm: string;
|
|
11
11
|
/** El botón mientras se confirma. */
|
|
12
12
|
confirming: string;
|
|
13
|
-
/** El título del paso final. */
|
|
13
|
+
/** El título del paso final cuando no se piden datos (`withoutContact`). */
|
|
14
14
|
title: string;
|
|
15
|
-
/** La explicación
|
|
15
|
+
/** La explicación del paso final: cuándo queda hecha la cita. */
|
|
16
16
|
note: string;
|
|
17
17
|
}
|
|
18
18
|
export interface ErkBookingPickerProps {
|
|
@@ -20,6 +20,8 @@ export interface ErkBookingPickerProps {
|
|
|
20
20
|
onChange: (value: BookingValue | null) => void;
|
|
21
21
|
label: string;
|
|
22
22
|
description?: string | null;
|
|
23
|
+
/** Marca la etiqueta con " *". */
|
|
24
|
+
required?: boolean;
|
|
23
25
|
durationMinutes: number;
|
|
24
26
|
bookingMode?: BookingMode;
|
|
25
27
|
temporaryLockMinutes?: number;
|
|
@@ -32,8 +34,8 @@ export interface ErkBookingPickerProps {
|
|
|
32
34
|
*/
|
|
33
35
|
eventTemplates?: BookingEventTemplateOption[];
|
|
34
36
|
/**
|
|
35
|
-
* Zona horaria de la organización (IANA). Con ella el paso de hora dice en qué reloj están las
|
|
36
|
-
* y, si el navegador está en otra zona, la equivalencia en la hora de quien agenda.
|
|
37
|
+
* Zona horaria de la organización (IANA). Con ella el paso de día y hora dice en qué reloj están las
|
|
38
|
+
* horas y, si el navegador está en otra zona, la equivalencia en la hora de quien agenda.
|
|
37
39
|
*/
|
|
38
40
|
timeZone?: string;
|
|
39
41
|
/**
|
|
@@ -48,7 +50,7 @@ export interface ErkBookingPickerProps {
|
|
|
48
50
|
askEmail?: boolean;
|
|
49
51
|
/**
|
|
50
52
|
* Mensaje de una reserva que se perdió después de hecha (la renovación falló). Al cambiar, el
|
|
51
|
-
* wizard vuelve al paso de hora con el mensaje y horas frescas.
|
|
53
|
+
* wizard vuelve al paso de día y hora con el mensaje y horas frescas.
|
|
52
54
|
*/
|
|
53
55
|
lapsed?: string;
|
|
54
56
|
/** Provee los slots disponibles para una persona + día (+ el tipo de atención elegido). */
|
|
@@ -78,7 +80,7 @@ export interface ErkBookingPickerProps {
|
|
|
78
80
|
email?: string;
|
|
79
81
|
};
|
|
80
82
|
}) => Promise<Partial<BookingValue>>;
|
|
81
|
-
/** Deshabilita
|
|
83
|
+
/** Deshabilita el botón de abrir mientras el host termina de cargar la oferta. */
|
|
82
84
|
disabled?: boolean;
|
|
83
85
|
/** No pide nombre ni correo: la cita que se está moviendo ya los tiene. */
|
|
84
86
|
withoutContact?: boolean;
|
|
@@ -87,19 +89,20 @@ export interface ErkBookingPickerProps {
|
|
|
87
89
|
readOnly?: boolean;
|
|
88
90
|
error?: string;
|
|
89
91
|
/**
|
|
90
|
-
* La oferta no cargó: el mensaje se muestra bajo
|
|
92
|
+
* La oferta no cargó: el mensaje se muestra bajo el botón de abrir con "Reintentar", que llama a
|
|
91
93
|
* `onRetryLoad`.
|
|
92
94
|
*/
|
|
93
95
|
loadError?: string;
|
|
94
96
|
onRetryLoad?: () => void;
|
|
95
97
|
}
|
|
96
98
|
/**
|
|
97
|
-
* Selector de cita
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
99
|
+
* Selector de cita. En el formulario es un control suelto como el de archivos: etiqueta, descripción y
|
|
100
|
+
* el botón "Elegir horario"; con un horario elegido, una fila con el resumen y "Cambiar".
|
|
101
|
+
*
|
|
102
|
+
* El botón abre el wizard en un modal de tamaño fijo — una decisión por pantalla ([tipo] → [persona] →
|
|
103
|
+
* día y hora → reservar), con stepper navegable arriba y barra inferior con lo elegido y Atrás /
|
|
104
|
+
* Siguiente. "Reservar" retira el horario de la oferta por unos minutos, cierra el modal y deja el
|
|
105
|
+
* resumen; la cita se confirma cuando se envía el formulario.
|
|
103
106
|
*/
|
|
104
|
-
declare function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, eventTemplates, timeZone, knownContact, askEmail, lapsed, fetchSlots, fetchDays, holdSlot, disabled, withoutContact, copy: copyOverrides, readOnly, error, loadError, onRetryLoad, }: ErkBookingPickerProps): JSX.Element;
|
|
107
|
+
declare function ErkBookingPicker({ value, onChange, label, description, required, durationMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, eventTemplates, timeZone, knownContact, askEmail, lapsed, fetchSlots, fetchDays, holdSlot, disabled, withoutContact, copy: copyOverrides, readOnly, error, loadError, onRetryLoad, }: ErkBookingPickerProps): JSX.Element;
|
|
105
108
|
export default ErkBookingPicker;
|