@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,164 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
width: 100%;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.inputRow {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: flex-start;
|
|
12
|
+
gap: 10px;
|
|
13
|
+
width: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.grow {
|
|
17
|
+
flex: 1;
|
|
18
|
+
min-width: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Mismo input del buscador de direcciones, para estados sin autocomplete */
|
|
22
|
+
.plainInput {
|
|
23
|
+
width: 100%;
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
height: 31px;
|
|
26
|
+
padding: 6px 12px;
|
|
27
|
+
font-size: 1rem;
|
|
28
|
+
font-family: inherit;
|
|
29
|
+
color: var(--eureka-text, #293241);
|
|
30
|
+
background-color: var(--eureka-bg, #fff);
|
|
31
|
+
border: 1px solid var(--eureka-outline, #b8b8b8);
|
|
32
|
+
border-radius: 10px;
|
|
33
|
+
outline: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.plainInput::placeholder {
|
|
37
|
+
color: var(--eureka-text, #293241);
|
|
38
|
+
opacity: 0.5;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.plainInput:hover:not(:disabled):not(:read-only) {
|
|
42
|
+
border-color: var(--eureka-primary, #3d5a7f);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.plainInput:focus:not(:read-only) {
|
|
46
|
+
border-color: var(--eureka-primary, #3d5a7f);
|
|
47
|
+
border-width: 2px;
|
|
48
|
+
padding: 5px 11px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.plainInputError {
|
|
52
|
+
border-color: var(--eureka-error, #e53935);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.helperText {
|
|
56
|
+
font-size: 12px;
|
|
57
|
+
color: var(--eureka-text, #666);
|
|
58
|
+
opacity: 0.7;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.errorText {
|
|
62
|
+
font-size: 12px;
|
|
63
|
+
color: var(--eureka-error, #e53935);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Formulario manual (modal) */
|
|
67
|
+
.manualGrid {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
gap: 12px;
|
|
71
|
+
width: 100%;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.manualRow {
|
|
75
|
+
display: flex;
|
|
76
|
+
gap: 12px;
|
|
77
|
+
width: 100%;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.col12 {
|
|
81
|
+
width: 100%;
|
|
82
|
+
}
|
|
83
|
+
.col6 {
|
|
84
|
+
width: 50%;
|
|
85
|
+
}
|
|
86
|
+
.col4 {
|
|
87
|
+
width: 33.333%;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@media (max-width: 600px) {
|
|
91
|
+
.manualRow {
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
gap: 12px;
|
|
94
|
+
}
|
|
95
|
+
.col6,
|
|
96
|
+
.col4 {
|
|
97
|
+
width: 100%;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Vista de solo lectura */
|
|
102
|
+
.readOnlyContainer {
|
|
103
|
+
width: 100%;
|
|
104
|
+
box-sizing: border-box;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.readOnlyCard {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: flex-start;
|
|
110
|
+
gap: 15px;
|
|
111
|
+
padding: 15px;
|
|
112
|
+
border: 1px solid var(--eureka-outline, #d9dee6);
|
|
113
|
+
border-radius: 10px;
|
|
114
|
+
background-color: var(--eureka-bg, #fff);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.readOnlyIcon {
|
|
118
|
+
flex-shrink: 0;
|
|
119
|
+
margin-top: 2px;
|
|
120
|
+
width: 24px;
|
|
121
|
+
height: 24px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.readOnlyDetails {
|
|
125
|
+
display: flex;
|
|
126
|
+
flex-direction: column;
|
|
127
|
+
gap: 4px;
|
|
128
|
+
color: var(--eureka-text, #333);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.readOnlyName {
|
|
132
|
+
font-size: 1rem;
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.readOnlyAddress {
|
|
137
|
+
font-size: 0.9rem;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.readOnlyCoords {
|
|
141
|
+
font-size: 0.8rem;
|
|
142
|
+
opacity: 0.65;
|
|
143
|
+
font-family: monospace;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Modal de dirección manual (portal: usa fallbacks del theme del form) */
|
|
147
|
+
.dialogPaper {
|
|
148
|
+
border-radius: 20px !important;
|
|
149
|
+
padding: 16px 24px;
|
|
150
|
+
background-color: var(--eureka-bg, #fff) !important;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.dialogTitle {
|
|
154
|
+
font-weight: 700 !important;
|
|
155
|
+
padding: 0 0 16px 0 !important;
|
|
156
|
+
color: var(--eureka-text, #333);
|
|
157
|
+
font-family: inherit !important;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.dialogContent {
|
|
161
|
+
padding: 16px 0 !important;
|
|
162
|
+
border-top: 1px solid var(--eureka-outline, #d9dee6);
|
|
163
|
+
border-bottom: 1px solid var(--eureka-outline, #d9dee6);
|
|
164
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,82 @@
|
|
|
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 ErkLocationField from './ErkLocationField';
|
|
7
|
+
import MaterialProviders from '../../Utils/MaterialProviders';
|
|
8
|
+
import InternalFormStyle from '../../constants/InternalFormStyle';
|
|
9
|
+
import { AddressFieldMode, LocationInputMode } from '../../@Types/LocationFormStep';
|
|
10
|
+
const ALL_MODES = [LocationInputMode.GOOGLE_PLACES, LocationInputMode.GPS, LocationInputMode.MANUAL];
|
|
11
|
+
function renderField(props = {}) {
|
|
12
|
+
const onChange = vi.fn();
|
|
13
|
+
render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkLocationField, { value: null, onChange: onChange, allowedModes: ALL_MODES, ...props }) }));
|
|
14
|
+
return { onChange };
|
|
15
|
+
}
|
|
16
|
+
describe('ErkLocationField', () => {
|
|
17
|
+
it('renders the single-row search with GPS and manual action buttons', () => {
|
|
18
|
+
renderField();
|
|
19
|
+
expect(screen.getByPlaceholderText('Buscar dirección…')).toBeInTheDocument();
|
|
20
|
+
expect(screen.getByRole('button', { name: 'Obtener ubicación por GPS' })).toBeInTheDocument();
|
|
21
|
+
expect(screen.getByRole('button', { name: 'Ingresar dirección manualmente' })).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
it('saves a manual address from the dialog, composing the readable address', async () => {
|
|
24
|
+
const user = userEvent.setup();
|
|
25
|
+
const { onChange } = renderField();
|
|
26
|
+
await user.click(screen.getByRole('button', { name: 'Ingresar dirección manualmente' }));
|
|
27
|
+
expect(await screen.findByText('Ingresar Dirección')).toBeInTheDocument();
|
|
28
|
+
// Guardar bloqueado hasta llenar calle/ciudad/país
|
|
29
|
+
expect(screen.getByRole('button', { name: 'Guardar' })).toBeDisabled();
|
|
30
|
+
await user.type(screen.getByLabelText(/Calle \/ Carrera/), 'Calle 100');
|
|
31
|
+
await user.type(screen.getByLabelText(/^Ciudad/), 'Bogotá');
|
|
32
|
+
await user.type(screen.getByLabelText(/^País/), 'Colombia');
|
|
33
|
+
await user.click(screen.getByRole('button', { name: 'Guardar' }));
|
|
34
|
+
expect(onChange).toHaveBeenCalledWith({
|
|
35
|
+
source: LocationInputMode.MANUAL,
|
|
36
|
+
address: 'Calle 100, Bogotá, Colombia',
|
|
37
|
+
addressDetail: { street: 'Calle 100', city: 'Bogotá', country: 'Colombia' },
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it('respects fixed and omitted fields in the manual dialog', async () => {
|
|
41
|
+
const user = userEvent.setup();
|
|
42
|
+
renderField({
|
|
43
|
+
addressFields: {
|
|
44
|
+
country: { mode: AddressFieldMode.FIXED, fixedValue: 'Colombia' },
|
|
45
|
+
postalCode: { mode: AddressFieldMode.OMIT },
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
await user.click(screen.getByRole('button', { name: 'Ingresar dirección manualmente' }));
|
|
49
|
+
await screen.findByText('Ingresar Dirección');
|
|
50
|
+
expect(screen.getByLabelText('País (fijo)')).toHaveValue('Colombia');
|
|
51
|
+
expect(screen.queryByLabelText('Código Postal')).not.toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
it('shows the selected address with edit and clear actions', async () => {
|
|
54
|
+
const user = userEvent.setup();
|
|
55
|
+
const value = {
|
|
56
|
+
source: LocationInputMode.GOOGLE_PLACES,
|
|
57
|
+
address: 'Carrera 7 # 71-21, Bogotá',
|
|
58
|
+
latitude: 4.65,
|
|
59
|
+
longitude: -74.05,
|
|
60
|
+
};
|
|
61
|
+
const { onChange } = renderField({ value });
|
|
62
|
+
expect(screen.getByDisplayValue('Carrera 7 # 71-21, Bogotá')).toBeInTheDocument();
|
|
63
|
+
expect(screen.getByRole('button', { name: 'Editar dirección manualmente' })).toBeInTheDocument();
|
|
64
|
+
await user.click(screen.getByRole('button', { name: 'Limpiar selección' }));
|
|
65
|
+
expect(onChange).toHaveBeenCalledWith(null);
|
|
66
|
+
});
|
|
67
|
+
it('renders the read-only summary card', () => {
|
|
68
|
+
renderField({
|
|
69
|
+
readOnly: true,
|
|
70
|
+
value: {
|
|
71
|
+
source: LocationInputMode.GPS,
|
|
72
|
+
name: 'Oficina',
|
|
73
|
+
address: 'Calle 93 # 12-34, Bogotá',
|
|
74
|
+
latitude: 4.678901,
|
|
75
|
+
longitude: -74.048123,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
expect(screen.getByText('Oficina')).toBeInTheDocument();
|
|
79
|
+
expect(screen.getByText('Calle 93 # 12-34, Bogotá')).toBeInTheDocument();
|
|
80
|
+
expect(screen.getByText(/Coordenadas: 4.678901, -74.048123/)).toBeInTheDocument();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AddressFieldConfig, AddressFieldKey, LocationAddressDetail, LocationStep } from '../../@Types/LocationFormStep';
|
|
2
|
+
/** Campos de dirección en orden de render, con su etiqueta de UI. */
|
|
3
|
+
export declare const ADDRESS_FIELDS: {
|
|
4
|
+
key: AddressFieldKey;
|
|
5
|
+
label: string;
|
|
6
|
+
}[];
|
|
7
|
+
export declare function fieldConfig(addressFields: LocationStep['addressFields'], key: AddressFieldKey): AddressFieldConfig;
|
|
8
|
+
/**
|
|
9
|
+
* Aplica la config por campo a una dirección resuelta (Google/GPS):
|
|
10
|
+
* los `FIXED` se sobreescriben con el valor del diseñador y los `OMIT` se descartan.
|
|
11
|
+
*/
|
|
12
|
+
export declare function applyAddressFields(detail: LocationAddressDetail, addressFields: LocationStep['addressFields']): LocationAddressDetail;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AddressFieldMode, } from '../../@Types/LocationFormStep';
|
|
2
|
+
/** Campos de dirección en orden de render, con su etiqueta de UI. */
|
|
3
|
+
export const ADDRESS_FIELDS = [
|
|
4
|
+
{ key: 'street', label: 'Calle' },
|
|
5
|
+
{ key: 'streetNumber', label: 'Número' },
|
|
6
|
+
{ key: 'unit', label: 'Interior / Apto' },
|
|
7
|
+
{ key: 'neighborhood', label: 'Barrio' },
|
|
8
|
+
{ key: 'city', label: 'Ciudad' },
|
|
9
|
+
{ key: 'state', label: 'Departamento / Región' },
|
|
10
|
+
{ key: 'postalCode', label: 'Código postal' },
|
|
11
|
+
{ key: 'country', label: 'País' },
|
|
12
|
+
];
|
|
13
|
+
export function fieldConfig(addressFields, key) {
|
|
14
|
+
return addressFields?.[key] ?? { mode: AddressFieldMode.ASK };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Aplica la config por campo a una dirección resuelta (Google/GPS):
|
|
18
|
+
* los `FIXED` se sobreescriben con el valor del diseñador y los `OMIT` se descartan.
|
|
19
|
+
*/
|
|
20
|
+
export function applyAddressFields(detail, addressFields) {
|
|
21
|
+
if (!addressFields)
|
|
22
|
+
return detail;
|
|
23
|
+
const result = { ...detail };
|
|
24
|
+
for (const { key } of ADDRESS_FIELDS) {
|
|
25
|
+
const config = addressFields[key];
|
|
26
|
+
if (!config)
|
|
27
|
+
continue;
|
|
28
|
+
if (config.mode === AddressFieldMode.OMIT)
|
|
29
|
+
delete result[key];
|
|
30
|
+
else if (config.mode === AddressFieldMode.FIXED)
|
|
31
|
+
result[key] = config.fixedValue;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { LocationRestriction } from '../../@Types/LocationFormStep';
|
|
2
|
+
/**
|
|
3
|
+
* Valida una coordenada contra todas las restricciones (lógica AND).
|
|
4
|
+
* Devuelve el mensaje de error de la primera restricción que falla, o `null` si es válida.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateRestrictions(lat: number, lng: number, restrictions: LocationRestriction[] | undefined): string | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { LocationRestrictionBehavior, LocationRestrictionType, } from '../../@Types/LocationFormStep';
|
|
2
|
+
const EARTH_RADIUS_M = 6371000;
|
|
3
|
+
function toRad(deg) {
|
|
4
|
+
return (deg * Math.PI) / 180;
|
|
5
|
+
}
|
|
6
|
+
/** Distancia en metros entre dos coordenadas (haversine). */
|
|
7
|
+
function haversineMeters(aLat, aLng, bLat, bLng) {
|
|
8
|
+
const dLat = toRad(bLat - aLat);
|
|
9
|
+
const dLng = toRad(bLng - aLng);
|
|
10
|
+
const lat1 = toRad(aLat);
|
|
11
|
+
const lat2 = toRad(bLat);
|
|
12
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLng / 2) ** 2;
|
|
13
|
+
return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(h)));
|
|
14
|
+
}
|
|
15
|
+
/** Punto dentro de polígono (ray casting). El polígono se cierra automáticamente. */
|
|
16
|
+
function pointInPolygon(lat, lng, polygon) {
|
|
17
|
+
let inside = false;
|
|
18
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
19
|
+
const yi = polygon[i].latitude;
|
|
20
|
+
const xi = polygon[i].longitude;
|
|
21
|
+
const yj = polygon[j].latitude;
|
|
22
|
+
const xj = polygon[j].longitude;
|
|
23
|
+
const intersects = yi > lat !== yj > lat && lng < ((xj - xi) * (lat - yi)) / (yj - yi) + xi;
|
|
24
|
+
if (intersects)
|
|
25
|
+
inside = !inside;
|
|
26
|
+
}
|
|
27
|
+
return inside;
|
|
28
|
+
}
|
|
29
|
+
function isInside(lat, lng, restriction) {
|
|
30
|
+
if (restriction.type === LocationRestrictionType.RADIUS) {
|
|
31
|
+
return (haversineMeters(lat, lng, restriction.center.latitude, restriction.center.longitude) <=
|
|
32
|
+
restriction.radiusMeters);
|
|
33
|
+
}
|
|
34
|
+
return pointInPolygon(lat, lng, restriction.polygon);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Valida una coordenada contra todas las restricciones (lógica AND).
|
|
38
|
+
* Devuelve el mensaje de error de la primera restricción que falla, o `null` si es válida.
|
|
39
|
+
*/
|
|
40
|
+
export function validateRestrictions(lat, lng, restrictions) {
|
|
41
|
+
for (const restriction of restrictions ?? []) {
|
|
42
|
+
const inside = isInside(lat, lng, restriction);
|
|
43
|
+
if (restriction.behavior === LocationRestrictionBehavior.ALLOW && !inside) {
|
|
44
|
+
return restriction.errorMessage ?? 'La ubicación está fuera de la zona permitida.';
|
|
45
|
+
}
|
|
46
|
+
if (restriction.behavior === LocationRestrictionBehavior.DENY && inside) {
|
|
47
|
+
return restriction.errorMessage ?? 'La ubicación está en una zona no permitida.';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LocationInputMode, LocationStepValue } from '../../@Types/LocationFormStep';
|
|
3
|
+
export interface ErkLocationInputProps {
|
|
4
|
+
value?: LocationStepValue | null;
|
|
5
|
+
onChange?: (value: LocationStepValue | null) => void;
|
|
6
|
+
allowedModes: LocationInputMode[];
|
|
7
|
+
defaultMode?: LocationInputMode;
|
|
8
|
+
label: string;
|
|
9
|
+
helperText?: string | null;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
error?: boolean;
|
|
12
|
+
readOnly?: boolean;
|
|
13
|
+
inputRef?: React.Ref<any>;
|
|
14
|
+
}
|
|
15
|
+
export default function ErkLocationInput({ value, onChange, allowedModes, label, helperText, required, error, readOnly, inputRef, }: ErkLocationInputProps): JSX.Element;
|