@arquimedes.co/eureka-forms 3.0.62 → 3.0.63
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/README +33 -1
- package/dist/@Types/AvailabilityFormStep.d.ts +48 -0
- package/dist/@Types/AvailabilityFormStep.js +24 -0
- package/dist/@Types/BankAccountFormStep.d.ts +67 -0
- package/dist/@Types/BankAccountFormStep.js +14 -0
- package/dist/@Types/BookingFormStep.d.ts +187 -0
- package/dist/@Types/BookingFormStep.js +44 -0
- package/dist/@Types/CalendarFormStep.d.ts +209 -0
- package/dist/@Types/CalendarFormStep.js +55 -0
- package/dist/@Types/CommunicationChannelFormStep.d.ts +51 -0
- package/dist/@Types/CommunicationChannelFormStep.js +15 -0
- package/dist/@Types/ConsentFormStep.d.ts +36 -0
- package/dist/@Types/ConsentFormStep.js +8 -0
- package/dist/@Types/EntityFormFormStep.d.ts +66 -0
- package/dist/@Types/EntityFormFormStep.js +20 -0
- package/dist/@Types/EventFormStep.d.ts +52 -0
- package/dist/@Types/EventFormStep.js +13 -0
- package/dist/@Types/ExternalReferenceFormStep.d.ts +74 -0
- package/dist/@Types/ExternalReferenceFormStep.js +20 -0
- package/dist/@Types/FormStep.d.ts +7 -2
- package/dist/@Types/FormStep.js +2 -1
- package/dist/@Types/LocationFormStep.d.ts +159 -0
- package/dist/@Types/LocationFormStep.js +46 -0
- package/dist/@Types/NumericFormSteps.d.ts +108 -0
- package/dist/@Types/NumericFormSteps.js +20 -0
- package/dist/@Types/PredefinedSelectorFormStep.d.ts +51 -0
- package/dist/@Types/PredefinedSelectorFormStep.js +39 -0
- package/dist/@Types/StepRef.d.ts +135 -0
- package/dist/@Types/StepRef.js +49 -0
- package/dist/FormSteps/AvailabilityStep/AvailabilityStep.d.ts +7 -0
- package/dist/FormSteps/AvailabilityStep/AvailabilityStep.js +14 -0
- package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.d.ts +8 -0
- package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.js +32 -0
- package/dist/FormSteps/BookingStep/BookingStep.d.ts +17 -0
- package/dist/FormSteps/BookingStep/BookingStep.js +14 -0
- package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.d.ts +7 -0
- package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +32 -0
- package/dist/FormSteps/LocationStep/LocationStep.d.ts +10 -0
- package/dist/FormSteps/LocationStep/LocationStep.js +14 -0
- package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.d.ts +3 -0
- package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.js +54 -0
- package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.module.css +18 -0
- package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.d.ts +3 -0
- package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.js +170 -0
- package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.module.css +96 -0
- package/dist/FormSteps/NumberStep/NumberStep.d.ts +10 -0
- package/dist/FormSteps/NumberStep/NumberStep.js +14 -0
- package/dist/FormSteps/Step.js +16 -0
- package/dist/FormSteps/StepFunctions.js +14 -0
- package/dist/FormSteps/StepFunctions.test.js +54 -0
- package/dist/Icons/AddIcon.d.ts +3 -0
- package/dist/Icons/AddIcon.js +7 -0
- package/dist/Icons/EditIcon.d.ts +3 -0
- package/dist/Icons/EditIcon.js +7 -0
- package/dist/Icons/RemoveIcon.d.ts +3 -0
- package/dist/Icons/RemoveIcon.js +7 -0
- package/dist/Icons/ViewDayIcon.d.ts +3 -0
- package/dist/Icons/ViewDayIcon.js +7 -0
- package/dist/Icons/ViewWeekIcon.d.ts +3 -0
- package/dist/Icons/ViewWeekIcon.js +7 -0
- package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.d.ts +19 -0
- package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.js +80 -0
- package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.module.css +88 -0
- package/dist/Shared/ErkAddressAutocomplete/googlePlaces.d.ts +29 -0
- package/dist/Shared/ErkAddressAutocomplete/googlePlaces.js +139 -0
- package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.d.ts +25 -0
- package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.js +112 -0
- package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.module.css +88 -0
- package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.test.d.ts +1 -0
- package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.test.js +73 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.d.ts +31 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.js +228 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.module.css +608 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.d.ts +28 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.js +117 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.d.ts +1 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.js +49 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.d.ts +1 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.js +107 -0
- package/dist/Shared/ErkDatePicker/ErkDatePicker.js +1 -3
- package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.d.ts +2 -3
- package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.js +1 -2
- package/dist/Shared/ErkIconButton/ErkIconButton.d.ts +17 -0
- package/dist/Shared/ErkIconButton/ErkIconButton.js +11 -0
- package/dist/Shared/ErkIconButton/ErkIconButton.module.css +41 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.d.ts +24 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.js +128 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.module.css +164 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.test.d.ts +1 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.test.js +82 -0
- package/dist/Shared/ErkLocationField/addressFields.d.ts +12 -0
- package/dist/Shared/ErkLocationField/addressFields.js +34 -0
- package/dist/Shared/ErkLocationField/locationRestrictions.d.ts +6 -0
- package/dist/Shared/ErkLocationField/locationRestrictions.js +51 -0
- package/dist/Shared/ErkLocationInput/ErkLocationInput.d.ts +15 -0
- package/dist/Shared/ErkLocationInput/ErkLocationInput.js +502 -0
- package/dist/Shared/ErkLocationInput/ErkLocationInput.module.css +128 -0
- package/dist/Shared/ErkNumberField/ErkNumberField.d.ts +42 -0
- package/dist/Shared/ErkNumberField/ErkNumberField.js +169 -0
- package/dist/Utils/AxiosWidget.js +9 -1
- package/dist/constants/FormStepTypes.d.ts +5 -1
- package/dist/constants/FormStepTypes.js +4 -0
- package/dist/index.lib.d.ts +4 -2
- package/dist/index.lib.js +3 -1
- package/package.json +2 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import ErkBookingPicker from './ErkBookingPicker';
|
|
4
|
+
import { withFormStepProviders } from '../../FormSteps/@StoryDecorators/FormStepDecorator';
|
|
5
|
+
import { BookingMode, PersonSelectionMode } from '../../@Types/BookingFormStep';
|
|
6
|
+
const SLOTS = [
|
|
7
|
+
{ id: 's-1', startTime: '09:00', endTime: '09:50', available: true },
|
|
8
|
+
{ id: 's-2', startTime: '10:00', endTime: '10:50', available: false },
|
|
9
|
+
{ id: 's-3', startTime: '11:00', endTime: '11:50', available: true },
|
|
10
|
+
{ id: 's-4', startTime: '14:00', endTime: '14:50', available: true },
|
|
11
|
+
{ id: 's-5', startTime: '15:00', endTime: '15:50', available: false },
|
|
12
|
+
{ id: 's-6', startTime: '16:00', endTime: '16:50', available: true },
|
|
13
|
+
];
|
|
14
|
+
function mockFetchSlots() {
|
|
15
|
+
return new Promise((resolve) => setTimeout(() => resolve(SLOTS), 300));
|
|
16
|
+
}
|
|
17
|
+
const PERSONS = [
|
|
18
|
+
{ id: 'p-1', name: 'María González', role: 'Asesora' },
|
|
19
|
+
{ id: 'p-2', name: 'Carlos Ruiz', role: 'Técnico' },
|
|
20
|
+
];
|
|
21
|
+
/** Controlled wrapper so the picker's value is interactive in Storybook. */
|
|
22
|
+
function Interactive(args) {
|
|
23
|
+
const [value, setValue] = useState(args.value);
|
|
24
|
+
return (_jsx("div", { style: { maxWidth: 660 }, children: _jsx(ErkBookingPicker, { ...args, value: value, onChange: setValue }) }));
|
|
25
|
+
}
|
|
26
|
+
const meta = {
|
|
27
|
+
component: ErkBookingPicker,
|
|
28
|
+
tags: ['autodocs'],
|
|
29
|
+
decorators: [withFormStepProviders],
|
|
30
|
+
render: Interactive,
|
|
31
|
+
args: {
|
|
32
|
+
// Supplied by the Interactive wrapper; declared here so stories don't each repeat it.
|
|
33
|
+
onChange: () => undefined,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
export default meta;
|
|
37
|
+
/** Full flow: person → date → time. */
|
|
38
|
+
export const WithPersonSelection = {
|
|
39
|
+
args: {
|
|
40
|
+
value: null,
|
|
41
|
+
label: 'Seleccionar cita',
|
|
42
|
+
description: 'Elige profesional, día y hora.',
|
|
43
|
+
durationMinutes: 50,
|
|
44
|
+
personSelectionMode: PersonSelectionMode.REQUIRED,
|
|
45
|
+
persons: PERSONS,
|
|
46
|
+
fetchSlots: mockFetchSlots,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
/** Optional person — adds a "Cualquiera" option. */
|
|
50
|
+
export const PersonOptional = {
|
|
51
|
+
args: {
|
|
52
|
+
value: null,
|
|
53
|
+
label: 'Reservar turno',
|
|
54
|
+
description: 'Puedes elegir o dejar que asignemos.',
|
|
55
|
+
durationMinutes: 30,
|
|
56
|
+
personSelectionMode: PersonSelectionMode.OPTIONAL,
|
|
57
|
+
persons: PERSONS,
|
|
58
|
+
fetchSlots: mockFetchSlots,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
/** No person selection → straight to date + time. */
|
|
62
|
+
export const NoPerson = {
|
|
63
|
+
args: {
|
|
64
|
+
value: null,
|
|
65
|
+
label: 'Seleccionar horario',
|
|
66
|
+
description: 'Elige tu día y hora.',
|
|
67
|
+
durationMinutes: 60,
|
|
68
|
+
personSelectionMode: PersonSelectionMode.NONE,
|
|
69
|
+
fetchSlots: mockFetchSlots,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
/** Extra contact step (collectContact). */
|
|
73
|
+
export const WithContact = {
|
|
74
|
+
args: {
|
|
75
|
+
value: null,
|
|
76
|
+
label: 'Agendar cita',
|
|
77
|
+
description: 'Elige y déjanos tus datos.',
|
|
78
|
+
durationMinutes: 45,
|
|
79
|
+
personSelectionMode: PersonSelectionMode.OPTIONAL,
|
|
80
|
+
persons: PERSONS,
|
|
81
|
+
collectContact: true,
|
|
82
|
+
fetchSlots: mockFetchSlots,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
/** Full config: contact data, buffer, lock notice and in-person mode. */
|
|
86
|
+
export const Complete = {
|
|
87
|
+
args: {
|
|
88
|
+
value: null,
|
|
89
|
+
label: 'Agendar cita',
|
|
90
|
+
description: 'Soporte postventa',
|
|
91
|
+
durationMinutes: 60,
|
|
92
|
+
bufferMinutes: 10,
|
|
93
|
+
bookingMode: BookingMode.IN_PERSON,
|
|
94
|
+
temporaryLockMinutes: 15,
|
|
95
|
+
personSelectionMode: PersonSelectionMode.OPTIONAL,
|
|
96
|
+
persons: PERSONS,
|
|
97
|
+
collectContact: true,
|
|
98
|
+
fetchSlots: mockFetchSlots,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
/** Already booked / read-only summary. */
|
|
102
|
+
export const ReadOnly = {
|
|
103
|
+
args: {
|
|
104
|
+
readOnly: true,
|
|
105
|
+
label: 'Cita confirmada',
|
|
106
|
+
durationMinutes: 50,
|
|
107
|
+
value: {
|
|
108
|
+
type: 'BOOKING_VALUE',
|
|
109
|
+
slotId: 's-3',
|
|
110
|
+
date: '2026-07-02',
|
|
111
|
+
startTime: '11:00',
|
|
112
|
+
endTime: '11:50',
|
|
113
|
+
personName: 'María González',
|
|
114
|
+
status: 'CONFIRMED',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { composeStories } from '@storybook/react-vite';
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import '@testing-library/jest-dom';
|
|
7
|
+
import { format } from 'date-fns';
|
|
8
|
+
import * as stories from './ErkBookingPicker.stories';
|
|
9
|
+
const { Complete } = composeStories(stories);
|
|
10
|
+
function clickableToday() {
|
|
11
|
+
// El número de hoy puede aparecer también como día de un mes adyacente (deshabilitado).
|
|
12
|
+
const candidates = screen.getAllByRole('button', { name: format(new Date(), 'd') });
|
|
13
|
+
const enabled = candidates.find((b) => !b.disabled);
|
|
14
|
+
if (!enabled)
|
|
15
|
+
throw new Error('No selectable day found for today');
|
|
16
|
+
return enabled;
|
|
17
|
+
}
|
|
18
|
+
describe('ErkBookingPicker Complete story', () => {
|
|
19
|
+
it('opens the wizard in a modal, completes it, and confirming closes the modal leaving the summary', async () => {
|
|
20
|
+
const user = userEvent.setup();
|
|
21
|
+
render(_jsx(Complete, {}));
|
|
22
|
+
// Trigger → modal con el wizard
|
|
23
|
+
await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
|
|
24
|
+
expect(await screen.findByRole('heading', { name: '¿Con quién quieres agendar?' })).toBeInTheDocument();
|
|
25
|
+
// Persona
|
|
26
|
+
await user.click(screen.getByText('María González'));
|
|
27
|
+
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
28
|
+
// Día
|
|
29
|
+
await screen.findByRole('heading', { name: 'Elige un día' });
|
|
30
|
+
await user.click(clickableToday());
|
|
31
|
+
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
32
|
+
// Hora (los slots del mock llegan con delay)
|
|
33
|
+
await screen.findByRole('heading', { name: 'Elige una hora' });
|
|
34
|
+
await user.click(await screen.findByRole('button', { name: '09:00' }));
|
|
35
|
+
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
36
|
+
// Confirmar
|
|
37
|
+
await screen.findByRole('heading', { name: 'Revisa y confirma tu cita' });
|
|
38
|
+
await user.type(screen.getByLabelText('Nombre'), 'Juan Pérez');
|
|
39
|
+
await user.type(screen.getByLabelText('Correo'), 'juan@correo.com');
|
|
40
|
+
await user.click(screen.getByRole('button', { name: 'Confirmar cita' }));
|
|
41
|
+
// El modal se cierra y queda el resumen compacto con opción de cambiar
|
|
42
|
+
await waitFor(() => expect(screen.queryByRole('heading', { name: 'Revisa y confirma tu cita' })).not.toBeInTheDocument());
|
|
43
|
+
expect(screen.getByText(/María González .* 09:00–09:50/)).toBeInTheDocument();
|
|
44
|
+
expect(screen.getByRole('button', { name: 'Cambiar' })).toBeInTheDocument();
|
|
45
|
+
// "Cambiar" limpia el valor y vuelve al botón de agendar
|
|
46
|
+
await user.click(screen.getByRole('button', { name: 'Cambiar' }));
|
|
47
|
+
expect(screen.getByRole('button', { name: 'Agendar cita' })).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,107 @@
|
|
|
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 { format } from 'date-fns';
|
|
7
|
+
import ErkBookingPicker from './ErkBookingPicker';
|
|
8
|
+
import MaterialProviders from '../../Utils/MaterialProviders';
|
|
9
|
+
import InternalFormStyle from '../../constants/InternalFormStyle';
|
|
10
|
+
import { PersonSelectionMode } from '../../@Types/BookingFormStep';
|
|
11
|
+
const SLOTS = [
|
|
12
|
+
{ id: 's-1', startTime: '09:00', endTime: '09:50', available: true },
|
|
13
|
+
{ id: 's-2', startTime: '10:00', endTime: '10:50', available: false },
|
|
14
|
+
{ id: 's-3', startTime: '11:00', endTime: '11:50', available: true },
|
|
15
|
+
];
|
|
16
|
+
const PERSONS = [
|
|
17
|
+
{ id: 'p-1', name: 'María González', role: 'Asesora' },
|
|
18
|
+
{ id: 'p-2', name: 'Carlos Ruiz', role: 'Técnico' },
|
|
19
|
+
];
|
|
20
|
+
function clickableToday() {
|
|
21
|
+
// El número de hoy puede aparecer también como día de un mes adyacente (deshabilitado).
|
|
22
|
+
const candidates = screen.getAllByRole('button', { name: format(new Date(), 'd') });
|
|
23
|
+
const enabled = candidates.find((b) => !b.disabled);
|
|
24
|
+
if (!enabled)
|
|
25
|
+
throw new Error('No selectable day found for today');
|
|
26
|
+
return enabled;
|
|
27
|
+
}
|
|
28
|
+
describe('ErkBookingPicker (wizard)', () => {
|
|
29
|
+
it('drives the full flow: person → day → hour → confirm', async () => {
|
|
30
|
+
const user = userEvent.setup();
|
|
31
|
+
const onChange = vi.fn();
|
|
32
|
+
const fetchSlots = vi.fn(() => Promise.resolve(SLOTS));
|
|
33
|
+
render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkBookingPicker, { value: null, onChange: onChange, label: "Agendar cita", durationMinutes: 50, personSelectionMode: PersonSelectionMode.OPTIONAL, persons: PERSONS, collectContact: true, fetchSlots: fetchSlots }) }));
|
|
34
|
+
// La tarjeta de entrada abre el wizard en un modal
|
|
35
|
+
await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
|
|
36
|
+
// Paso 1: persona — Siguiente bloqueado hasta elegir
|
|
37
|
+
expect(await screen.findByRole('heading', { name: '¿Con quién quieres agendar?' })).toBeInTheDocument();
|
|
38
|
+
const next = screen.getByRole('button', { name: 'Siguiente' });
|
|
39
|
+
expect(next).toBeDisabled();
|
|
40
|
+
await user.click(screen.getByText('María González'));
|
|
41
|
+
expect(next).toBeEnabled();
|
|
42
|
+
await user.click(next);
|
|
43
|
+
// Paso 2: día
|
|
44
|
+
expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
|
|
45
|
+
expect(screen.getByRole('button', { name: 'Siguiente' })).toBeDisabled();
|
|
46
|
+
await user.click(clickableToday());
|
|
47
|
+
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
48
|
+
// Paso 3: hora — slots del fetch; ocupados deshabilitados
|
|
49
|
+
expect(await screen.findByRole('heading', { name: 'Elige una hora' })).toBeInTheDocument();
|
|
50
|
+
expect(fetchSlots).toHaveBeenCalledWith({ personId: 'p-1', date: format(new Date(), 'yyyy-MM-dd') });
|
|
51
|
+
const taken = await screen.findByRole('button', { name: '10:00' });
|
|
52
|
+
expect(taken).toBeDisabled();
|
|
53
|
+
await user.click(screen.getByRole('button', { name: '09:00' }));
|
|
54
|
+
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
55
|
+
// Paso 4: confirmar — datos de contacto en la barra y CTA
|
|
56
|
+
expect(await screen.findByRole('heading', { name: 'Revisa y confirma tu cita' })).toBeInTheDocument();
|
|
57
|
+
await user.type(screen.getByLabelText('Nombre'), 'Juan Pérez');
|
|
58
|
+
await user.type(screen.getByLabelText('Correo'), 'juan@correo.com');
|
|
59
|
+
await user.click(screen.getByRole('button', { name: 'Confirmar cita' }));
|
|
60
|
+
expect(onChange).toHaveBeenCalledWith({
|
|
61
|
+
type: 'BOOKING_VALUE',
|
|
62
|
+
slotId: 's-1',
|
|
63
|
+
date: format(new Date(), 'yyyy-MM-dd'),
|
|
64
|
+
startTime: '09:00',
|
|
65
|
+
endTime: '09:50',
|
|
66
|
+
personId: 'p-1',
|
|
67
|
+
personName: 'María González',
|
|
68
|
+
contactName: 'Juan Pérez',
|
|
69
|
+
contactEmail: 'juan@correo.com',
|
|
70
|
+
status: 'PENDING',
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
it('skips the person step when personSelectionMode is NONE', async () => {
|
|
74
|
+
const user = userEvent.setup();
|
|
75
|
+
render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkBookingPicker, { value: null, onChange: () => { }, label: "Agendar cita", durationMinutes: 30, personSelectionMode: PersonSelectionMode.NONE, fetchSlots: () => Promise.resolve(SLOTS) }) }));
|
|
76
|
+
await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
|
|
77
|
+
expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
|
|
78
|
+
expect(screen.queryByText('¿Con quién quieres agendar?')).not.toBeInTheDocument();
|
|
79
|
+
expect(screen.queryByText('Persona')).not.toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
it('allows navigating back through the breadcrumb without losing the selection', async () => {
|
|
82
|
+
const user = userEvent.setup();
|
|
83
|
+
render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkBookingPicker, { value: null, onChange: () => { }, label: "Agendar cita", durationMinutes: 50, personSelectionMode: PersonSelectionMode.REQUIRED, persons: PERSONS, fetchSlots: () => Promise.resolve(SLOTS) }) }));
|
|
84
|
+
await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
|
|
85
|
+
await user.click(await screen.findByText('Carlos Ruiz'));
|
|
86
|
+
await user.click(screen.getByRole('button', { name: 'Siguiente' }));
|
|
87
|
+
expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
|
|
88
|
+
// Volver por el breadcrumb: el paso "Persona" completado es clicable
|
|
89
|
+
await user.click(screen.getByRole('button', { name: /Persona/ }));
|
|
90
|
+
expect(await screen.findByRole('heading', { name: '¿Con quién quieres agendar?' })).toBeInTheDocument();
|
|
91
|
+
// La selección se mantiene y permite avanzar de nuevo
|
|
92
|
+
expect(screen.getByRole('button', { name: 'Siguiente' })).toBeEnabled();
|
|
93
|
+
});
|
|
94
|
+
it('renders the compact summary when a value exists', () => {
|
|
95
|
+
render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkBookingPicker, { value: {
|
|
96
|
+
type: 'BOOKING_VALUE',
|
|
97
|
+
slotId: 's-3',
|
|
98
|
+
date: '2026-07-02',
|
|
99
|
+
startTime: '11:00',
|
|
100
|
+
endTime: '11:50',
|
|
101
|
+
personName: 'María González',
|
|
102
|
+
status: 'CONFIRMED',
|
|
103
|
+
}, onChange: () => { }, label: "Cita confirmada", durationMinutes: 50 }) }));
|
|
104
|
+
expect(screen.getByText(/María González .* 11:00–11:50/)).toBeInTheDocument();
|
|
105
|
+
expect(screen.getByRole('button', { name: 'Cambiar' })).toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -2,11 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import React, { useMemo } from 'react';
|
|
3
3
|
import { format as dateFnsFormat, isValid } from 'date-fns';
|
|
4
4
|
import { InputAdornment, styled, Dialog as MuiDialog } from '@mui/material';
|
|
5
|
-
import { MobileDatePicker as MuiDatePicker, MobileDateTimePicker as MuiDateTimePicker, DatePickerToolbar as MuiDatePickerToolbar, DateTimePickerToolbar as MuiDateTimePickerToolbar, usePickerContext, } from '@mui/x-date-pickers';
|
|
6
|
-
import { PickerDay as MuiPickerDay } from '@mui/x-date-pickers/PickerDay';
|
|
5
|
+
import { MobileDatePicker as MuiDatePicker, MobileDateTimePicker as MuiDateTimePicker, DatePickerToolbar as MuiDatePickerToolbar, DateTimePickerToolbar as MuiDateTimePickerToolbar, usePickerContext, PickerDay as MuiPickerDay, renderTimeViewClock, } from '@mui/x-date-pickers';
|
|
7
6
|
import { StyledTextField } from '../ErkTextField/ErkTextField';
|
|
8
7
|
import CalendarIcon from '../../Icons/CalendarIcon';
|
|
9
|
-
import { renderTimeViewClock } from '@mui/x-date-pickers/timeViewRenderers';
|
|
10
8
|
const StyledDateTimePicker = styled(MuiDateTimePicker, {
|
|
11
9
|
shouldForwardProp: (propName) => propName !== 'size',
|
|
12
10
|
})(({ disabled, readOnly, size }) => ({
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { DateRange } from '@mui/x-date-pickers-pro';
|
|
4
|
-
import { PickersShortcutsItem } from '@mui/x-date-pickers/PickersShortcuts';
|
|
2
|
+
import { PickersShortcutsItem } from '@mui/x-date-pickers';
|
|
3
|
+
import { MobileDateRangePickerProps as MuiDateRangePickerProps, DateRange } from '@mui/x-date-pickers-pro';
|
|
5
4
|
import { ErkTextFieldProps } from '../ErkTextField/ErkTextField';
|
|
6
5
|
interface ErkDateRangePickerProps extends Omit<MuiDateRangePickerProps, 'ref' | 'onChange' | 'value' | 'slotProps'>, Pick<ErkTextFieldProps, 'error' | 'required' | 'helperText' | 'size'> {
|
|
7
6
|
onChange?: (dateRange: DateRange<Date>) => void;
|
|
@@ -2,8 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import React, { useMemo } from 'react';
|
|
3
3
|
import { InputAdornment, styled, Dialog as MuiDialog } from '@mui/material';
|
|
4
4
|
import { DatePickerToolbar as MuiDatePickerToolbar, usePickerContext, } from '@mui/x-date-pickers';
|
|
5
|
-
import { MobileDateRangePicker as MuiDateRangePicker, } from '@mui/x-date-pickers-pro
|
|
6
|
-
import { DateRangePickerDay as MuiDateRangePickerDay } from '@mui/x-date-pickers-pro';
|
|
5
|
+
import { MobileDateRangePicker as MuiDateRangePicker, DateRangePickerDay as MuiDateRangePickerDay, } from '@mui/x-date-pickers-pro';
|
|
7
6
|
import { format, isValid } from 'date-fns';
|
|
8
7
|
import { StyledTextField } from '../ErkTextField/ErkTextField';
|
|
9
8
|
import CalendarIcon from '../../Icons/CalendarIcon';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ErkIconButtonProps {
|
|
3
|
+
/** Tooltip y nombre accesible del botón. */
|
|
4
|
+
title: string;
|
|
5
|
+
onClick?: () => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** Muestra un spinner en lugar del icono (y deshabilita el botón). */
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Botón de icono de la casa: cuadrado redondeado con la misma altura y borde
|
|
14
|
+
* que los inputs del form (theming vía variables --eureka-*), con tooltip.
|
|
15
|
+
*/
|
|
16
|
+
declare function ErkIconButton({ title, onClick, disabled, loading, children, className, }: ErkIconButtonProps): JSX.Element;
|
|
17
|
+
export default ErkIconButton;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { CircularProgress, Tooltip } from '@mui/material';
|
|
3
|
+
import styles from './ErkIconButton.module.css';
|
|
4
|
+
/**
|
|
5
|
+
* Botón de icono de la casa: cuadrado redondeado con la misma altura y borde
|
|
6
|
+
* que los inputs del form (theming vía variables --eureka-*), con tooltip.
|
|
7
|
+
*/
|
|
8
|
+
function ErkIconButton({ title, onClick, disabled = false, loading = false, children, className, }) {
|
|
9
|
+
return (_jsx(Tooltip, { title: title, children: _jsx("span", { className: styles.wrap, children: _jsx("button", { type: "button", "aria-label": title, disabled: disabled || loading, className: className ? `${styles.button} ${className}` : styles.button, onClick: onClick, children: loading ? _jsx(CircularProgress, { size: 16 }) : children }) }) }));
|
|
10
|
+
}
|
|
11
|
+
export default ErkIconButton;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.wrap {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
flex-shrink: 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* Mismo alto, radio y borde que los inputs del form (ErkTextField small) */
|
|
7
|
+
.button {
|
|
8
|
+
height: 31px;
|
|
9
|
+
width: 31px;
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
border: 1px solid var(--eureka-outline, #b8b8b8);
|
|
12
|
+
border-radius: 10px;
|
|
13
|
+
background-color: var(--eureka-bg, #fff);
|
|
14
|
+
color: var(--eureka-text, #293241);
|
|
15
|
+
display: grid;
|
|
16
|
+
place-items: center;
|
|
17
|
+
padding: 0;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
transition: all 0.2s ease-in-out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.button svg {
|
|
23
|
+
width: 18px;
|
|
24
|
+
height: 18px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.button:hover:not(:disabled) {
|
|
28
|
+
background-color: rgba(0, 0, 0, 0.04);
|
|
29
|
+
border-color: var(--eureka-primary, #3d5a7f);
|
|
30
|
+
color: var(--eureka-primary, #3d5a7f);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.button:focus-visible {
|
|
34
|
+
outline: 2px solid var(--eureka-primary, #3d5a7f);
|
|
35
|
+
outline-offset: 1px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.button:disabled {
|
|
39
|
+
opacity: 0.5;
|
|
40
|
+
cursor: default;
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LocationInputMode, LocationStep, LocationStepValue } from '../../@Types/LocationFormStep';
|
|
3
|
+
export interface ErkLocationFieldProps {
|
|
4
|
+
value?: LocationStepValue | null;
|
|
5
|
+
onChange: (value: LocationStepValue | null) => void;
|
|
6
|
+
allowedModes: LocationInputMode[];
|
|
7
|
+
defaultMode?: LocationInputMode;
|
|
8
|
+
addressFields?: LocationStep['addressFields'];
|
|
9
|
+
/** ISO-2 para acotar el buscador (ej. ['co']). */
|
|
10
|
+
regionCodes?: string[];
|
|
11
|
+
error?: boolean;
|
|
12
|
+
helperText?: string | null;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
inputRef?: React.Ref<HTMLInputElement>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Campo de ubicación: una sola fila con el buscador de direcciones (Base UI +
|
|
19
|
+
* Google Places) y botones de acción — GPS y edición manual (en modal). Al
|
|
20
|
+
* seleccionar, el campo muestra la dirección con opciones de editar/limpiar.
|
|
21
|
+
* La config por campo (fijo / omitir) se aplica a todos los modos.
|
|
22
|
+
*/
|
|
23
|
+
declare function ErkLocationField({ value, onChange, allowedModes, addressFields, regionCodes, error, helperText, required, readOnly, inputRef, }: ErkLocationFieldProps): JSX.Element;
|
|
24
|
+
export default ErkLocationField;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
import { Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
|
|
4
|
+
import { AddressFieldMode, LocationInputMode, } from '../../@Types/LocationFormStep';
|
|
5
|
+
import ErkAddressAutocomplete from '../ErkAddressAutocomplete/ErkAddressAutocomplete';
|
|
6
|
+
import { reverseGeocode } from '../ErkAddressAutocomplete/googlePlaces';
|
|
7
|
+
import { applyAddressFields, fieldConfig } from './addressFields';
|
|
8
|
+
import ErkTextField from '../ErkTextField/ErkTextField';
|
|
9
|
+
import ErkButton from '../ErkButton/ErkButton';
|
|
10
|
+
import ErkIconButton from '../ErkIconButton/ErkIconButton';
|
|
11
|
+
import LocationIcon from '../../Icons/LocationIcon';
|
|
12
|
+
import EditIcon from '../../Icons/EditIcon';
|
|
13
|
+
import CloseIcon from '../../Icons/CloseIcon';
|
|
14
|
+
import styles from './ErkLocationField.module.css';
|
|
15
|
+
/** Claves que el formulario manual exige (si no están omitidas ni fijas). */
|
|
16
|
+
const MANUAL_REQUIRED_KEYS = ['street', 'city', 'country'];
|
|
17
|
+
/** Dirección legible a partir del desglose, en el orden natural de una dirección. */
|
|
18
|
+
function composeAddress(detail) {
|
|
19
|
+
return [
|
|
20
|
+
detail.street,
|
|
21
|
+
detail.streetNumber,
|
|
22
|
+
detail.unit ? `Apto/Int ${detail.unit}` : '',
|
|
23
|
+
detail.neighborhood,
|
|
24
|
+
detail.city,
|
|
25
|
+
detail.state,
|
|
26
|
+
detail.country,
|
|
27
|
+
]
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.join(', ');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Campo de ubicación: una sola fila con el buscador de direcciones (Base UI +
|
|
33
|
+
* Google Places) y botones de acción — GPS y edición manual (en modal). Al
|
|
34
|
+
* seleccionar, el campo muestra la dirección con opciones de editar/limpiar.
|
|
35
|
+
* La config por campo (fijo / omitir) se aplica a todos los modos.
|
|
36
|
+
*/
|
|
37
|
+
function ErkLocationField({ value, onChange, allowedModes, addressFields, regionCodes = ['co'], error = false, helperText, required = false, readOnly = false, inputRef, }) {
|
|
38
|
+
const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? '';
|
|
39
|
+
const canGoogle = allowedModes.includes(LocationInputMode.GOOGLE_PLACES);
|
|
40
|
+
const canGps = allowedModes.includes(LocationInputMode.GPS);
|
|
41
|
+
const canManual = allowedModes.includes(LocationInputMode.MANUAL);
|
|
42
|
+
const [gpsLoading, setGpsLoading] = useState(false);
|
|
43
|
+
const [gpsError, setGpsError] = useState(null);
|
|
44
|
+
const [manualOpen, setManualOpen] = useState(false);
|
|
45
|
+
const [manualDetail, setManualDetail] = useState({});
|
|
46
|
+
const handleGoogle = useCallback((resolved) => {
|
|
47
|
+
onChange({ ...resolved, addressDetail: applyAddressFields(resolved.addressDetail ?? {}, addressFields) });
|
|
48
|
+
}, [onChange, addressFields]);
|
|
49
|
+
const handleGps = useCallback(() => {
|
|
50
|
+
if (!navigator.geolocation) {
|
|
51
|
+
setGpsError('La geolocalización no es compatible con este navegador.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
setGpsLoading(true);
|
|
55
|
+
setGpsError(null);
|
|
56
|
+
navigator.geolocation.getCurrentPosition((position) => {
|
|
57
|
+
const lat = position.coords.latitude;
|
|
58
|
+
const lng = position.coords.longitude;
|
|
59
|
+
void reverseGeocode(apiKey, lat, lng)
|
|
60
|
+
.then(({ address, addressDetail }) => {
|
|
61
|
+
onChange({
|
|
62
|
+
source: LocationInputMode.GPS,
|
|
63
|
+
latitude: lat,
|
|
64
|
+
longitude: lng,
|
|
65
|
+
address: address ?? `${lat.toFixed(6)}, ${lng.toFixed(6)}`,
|
|
66
|
+
addressDetail: applyAddressFields(addressDetail, addressFields),
|
|
67
|
+
});
|
|
68
|
+
})
|
|
69
|
+
.finally(() => setGpsLoading(false));
|
|
70
|
+
}, (err) => {
|
|
71
|
+
let message = 'No se pudo obtener la ubicación GPS.';
|
|
72
|
+
if (err.code === err.PERMISSION_DENIED) {
|
|
73
|
+
message =
|
|
74
|
+
'Permiso denegado para acceder a la ubicación. Asegúrate de habilitar los permisos en tu navegador y en los Ajustes del Sistema (Privacidad y seguridad > Localización).';
|
|
75
|
+
}
|
|
76
|
+
else if (err.code === err.TIMEOUT) {
|
|
77
|
+
message =
|
|
78
|
+
'Tiempo de espera agotado al obtener la ubicación. Si estás en Mac, asegúrate de activar el Wi-Fi para ayudar a la localización, o introduce la dirección manualmente.';
|
|
79
|
+
}
|
|
80
|
+
setGpsError(message);
|
|
81
|
+
setGpsLoading(false);
|
|
82
|
+
}, { enableHighAccuracy: false, timeout: 15000, maximumAge: 300000 });
|
|
83
|
+
}, [apiKey, onChange, addressFields]);
|
|
84
|
+
const openManual = useCallback(() => {
|
|
85
|
+
setManualDetail(value?.addressDetail ?? {});
|
|
86
|
+
setManualOpen(true);
|
|
87
|
+
}, [value]);
|
|
88
|
+
const manualFieldValue = useCallback((key) => {
|
|
89
|
+
const config = fieldConfig(addressFields, key);
|
|
90
|
+
if (config.mode === AddressFieldMode.FIXED)
|
|
91
|
+
return config.fixedValue ?? '';
|
|
92
|
+
return manualDetail[key] ?? '';
|
|
93
|
+
}, [addressFields, manualDetail]);
|
|
94
|
+
const canSaveManual = MANUAL_REQUIRED_KEYS.every((key) => {
|
|
95
|
+
const config = fieldConfig(addressFields, key);
|
|
96
|
+
if (config.mode === AddressFieldMode.OMIT)
|
|
97
|
+
return true;
|
|
98
|
+
if (config.mode === AddressFieldMode.FIXED)
|
|
99
|
+
return !!config.fixedValue;
|
|
100
|
+
return !!manualDetail[key];
|
|
101
|
+
});
|
|
102
|
+
const saveManual = () => {
|
|
103
|
+
if (!canSaveManual)
|
|
104
|
+
return;
|
|
105
|
+
const detail = applyAddressFields(manualDetail, addressFields);
|
|
106
|
+
onChange({ source: LocationInputMode.MANUAL, address: composeAddress(detail), addressDetail: detail });
|
|
107
|
+
setManualOpen(false);
|
|
108
|
+
};
|
|
109
|
+
const clearValue = () => {
|
|
110
|
+
onChange(null);
|
|
111
|
+
setGpsError(null);
|
|
112
|
+
};
|
|
113
|
+
const manualField = (key, label, requiredField = false) => {
|
|
114
|
+
const config = fieldConfig(addressFields, key);
|
|
115
|
+
if (config.mode === AddressFieldMode.OMIT)
|
|
116
|
+
return null;
|
|
117
|
+
const fixed = config.mode === AddressFieldMode.FIXED;
|
|
118
|
+
return (_jsx(ErkTextField, { label: fixed ? `${label} (fijo)` : label, value: manualFieldValue(key), readOnly: fixed, required: requiredField && !fixed, onChange: (text) => setManualDetail((detail) => ({ ...detail, [key]: text || undefined })) }));
|
|
119
|
+
};
|
|
120
|
+
// Vista de solo lectura: tarjeta con el resumen de la ubicación
|
|
121
|
+
if (readOnly) {
|
|
122
|
+
return (_jsx("div", { className: styles.readOnlyContainer, children: _jsxs("div", { className: styles.readOnlyCard, children: [_jsx("div", { className: styles.readOnlyIcon, children: _jsx(LocationIcon, { fill: "var(--eureka-primary)" }) }), _jsxs("div", { className: styles.readOnlyDetails, children: [value?.name && _jsx("span", { className: styles.readOnlyName, children: value.name }), _jsx("span", { className: styles.readOnlyAddress, children: value?.address ?? 'Ubicación no proporcionada' }), value?.latitude !== undefined && value?.longitude !== undefined && (_jsxs("span", { className: styles.readOnlyCoords, children: ["Coordenadas: ", value.latitude.toFixed(6), ", ", value.longitude.toFixed(6)] }))] })] }) }));
|
|
123
|
+
}
|
|
124
|
+
return (_jsxs("div", { className: styles.container, children: [_jsx("div", { className: styles.inputRow, children: value?.address ? (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.grow, children: _jsx("input", { ref: inputRef, className: error ? `${styles.plainInput} ${styles.plainInputError}` : styles.plainInput, value: value.address, readOnly: true, "aria-label": "Ubicaci\u00F3n seleccionada" }) }), canManual && (_jsx(ErkIconButton, { title: "Editar direcci\u00F3n manualmente", onClick: openManual, children: _jsx(EditIcon, { fill: "currentColor" }) })), _jsx(ErkIconButton, { title: "Limpiar selecci\u00F3n", onClick: clearValue, children: _jsx(CloseIcon, { fill: "currentColor" }) })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.grow, children: canGoogle ? (_jsx(ErkAddressAutocomplete, { value: value, onChange: handleGoogle, regionCodes: regionCodes, error: error, required: required, inputRef: inputRef })) : canManual ? (_jsx("input", { ref: inputRef, className: error ? `${styles.plainInput} ${styles.plainInputError}` : styles.plainInput, placeholder: "Ingresa una ubicaci\u00F3n...", value: value?.address ?? '', required: required, onChange: (e) => onChange(e.target.value
|
|
125
|
+
? { source: LocationInputMode.MANUAL, address: e.target.value }
|
|
126
|
+
: null) })) : (_jsx("input", { ref: inputRef, className: error ? `${styles.plainInput} ${styles.plainInputError}` : styles.plainInput, placeholder: "Usa el bot\u00F3n para obtener tu ubicaci\u00F3n", value: "", readOnly: true })) }), canGps && (_jsx(ErkIconButton, { title: "Obtener ubicaci\u00F3n por GPS", loading: gpsLoading, onClick: handleGps, children: _jsx(LocationIcon, { fill: "currentColor" }) })), canManual && (_jsx(ErkIconButton, { title: "Ingresar direcci\u00F3n manualmente", onClick: openManual, children: _jsx(EditIcon, { fill: "currentColor" }) }))] })) }), (gpsError ?? helperText) && (_jsx("div", { className: error || gpsError ? styles.errorText : styles.helperText, children: gpsError ?? helperText })), _jsxs(Dialog, { open: manualOpen, onClose: () => setManualOpen(false), maxWidth: "sm", fullWidth: true, slotProps: { paper: { className: styles.dialogPaper } }, children: [_jsx(DialogTitle, { className: styles.dialogTitle, children: "Ingresar Direcci\u00F3n" }), _jsx(DialogContent, { className: styles.dialogContent, children: _jsxs("div", { className: styles.manualGrid, children: [_jsx("div", { className: styles.manualRow, children: _jsx("div", { className: styles.col12, children: manualField('street', 'Calle / Carrera / Avenida (Línea 1)', true) }) }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col6, children: manualField('streetNumber', 'Número / Portal') }), _jsx("div", { className: styles.col6, children: manualField('unit', 'Apto / Oficina / Interior') })] }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col6, children: manualField('neighborhood', 'Barrio / Zona') }), _jsx("div", { className: styles.col6, children: manualField('city', 'Ciudad', true) })] }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col4, children: manualField('state', 'Estado / Provincia / Dpto') }), _jsx("div", { className: styles.col4, children: manualField('postalCode', 'Código Postal') }), _jsx("div", { className: styles.col4, children: manualField('country', 'País', true) })] })] }) }), _jsxs(DialogActions, { style: { padding: '16px 0 0 0', gap: '12px' }, children: [_jsx(ErkButton, { text: "Cancelar", variant: "outlined", onClick: () => setManualOpen(false) }), _jsx(ErkButton, { text: "Guardar", variant: "contained", disabled: !canSaveManual, onClick: saveManual })] })] })] }));
|
|
127
|
+
}
|
|
128
|
+
export default ErkLocationField;
|