@arquimedes.co/eureka-forms 3.0.47-test → 3.0.49-new-steps
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/@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 +156 -0
- package/dist/@Types/BookingFormStep.js +44 -0
- package/dist/@Types/CalendarFormStep.d.ts +192 -0
- package/dist/@Types/CalendarFormStep.js +48 -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/BookingStep/BookingStep.d.ts +17 -0
- package/dist/FormSteps/BookingStep/BookingStep.js +14 -0
- package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.d.ts +3 -0
- package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +362 -0
- package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.module.css +174 -0
- package/dist/FormSteps/CalendarStep/CalendarStep.d.ts +7 -0
- package/dist/FormSteps/CalendarStep/CalendarStep.js +14 -0
- package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.d.ts +3 -0
- package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.js +269 -0
- package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.module.css +531 -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 +47 -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 +168 -0
- package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.module.css +94 -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 +57 -3
- package/dist/Icons/@ErkIcon.d.ts +2 -2
- package/dist/Icons/@ErkIcon.js +2 -2
- 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 +82 -0
- package/dist/Shared/ErkAddressAutocomplete/googlePlaces.d.ts +29 -0
- package/dist/Shared/ErkAddressAutocomplete/googlePlaces.js +139 -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/ErkLocationField/ErkLocationField.d.ts +23 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.js +73 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.module.css +29 -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 +499 -0
- package/dist/Shared/ErkLocationInput/ErkLocationInput.module.css +115 -0
- package/dist/Shared/ErkNumberField/ErkNumberField.d.ts +42 -0
- package/dist/Shared/ErkNumberField/ErkNumberField.js +170 -0
- package/dist/constants/FormStepTypes.d.ts +5 -1
- package/dist/constants/FormStepTypes.js +4 -0
- package/package.json +2 -1
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
3
|
+
import { Autocomplete, CircularProgress, Typography, IconButton, Tooltip, Dialog, DialogTitle, DialogContent, DialogActions, } from '@mui/material';
|
|
4
|
+
import { useAppSelector } from '../../hooks';
|
|
5
|
+
import ErkButton from '../ErkButton/ErkButton';
|
|
6
|
+
import { LocationInputMode } from '../../@Types/LocationFormStep';
|
|
7
|
+
import { StyledTextField } from '../ErkTextField/ErkTextField';
|
|
8
|
+
import ErkTextField from '../ErkTextField/ErkTextField';
|
|
9
|
+
import LocationIcon from '../../Icons/LocationIcon';
|
|
10
|
+
import EditIcon from '../../Icons/EditIcon';
|
|
11
|
+
import CloseIcon from '../../Icons/CloseIcon';
|
|
12
|
+
import styles from './ErkLocationInput.module.css';
|
|
13
|
+
// ─── Script Loader ────────────────────────────────────────────────────────────
|
|
14
|
+
let loadPromise = null;
|
|
15
|
+
function loadGoogleMaps(apiKey) {
|
|
16
|
+
if (typeof window === 'undefined') {
|
|
17
|
+
return Promise.resolve();
|
|
18
|
+
}
|
|
19
|
+
const win = window;
|
|
20
|
+
if (win.google?.maps?.importLibrary) {
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
}
|
|
23
|
+
if (loadPromise) {
|
|
24
|
+
return loadPromise;
|
|
25
|
+
}
|
|
26
|
+
loadPromise = new Promise((resolve, reject) => {
|
|
27
|
+
const script = document.createElement('script');
|
|
28
|
+
script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&loading=async`;
|
|
29
|
+
script.async = true;
|
|
30
|
+
script.defer = true;
|
|
31
|
+
script.onload = () => {
|
|
32
|
+
resolve();
|
|
33
|
+
};
|
|
34
|
+
script.onerror = (err) => {
|
|
35
|
+
loadPromise = null;
|
|
36
|
+
reject(new Error(typeof err === 'string' ? err : 'Google Maps script failed to load.'));
|
|
37
|
+
};
|
|
38
|
+
document.head.appendChild(script);
|
|
39
|
+
});
|
|
40
|
+
return loadPromise;
|
|
41
|
+
}
|
|
42
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
43
|
+
function parseAddressComponents(components) {
|
|
44
|
+
const detail = {};
|
|
45
|
+
for (const component of components) {
|
|
46
|
+
const types = component.types;
|
|
47
|
+
const longName = component.long_name ?? component.longText;
|
|
48
|
+
const shortName = component.short_name ?? component.shortText;
|
|
49
|
+
if (types.includes('route')) {
|
|
50
|
+
detail.street = longName;
|
|
51
|
+
}
|
|
52
|
+
else if (types.includes('street_number')) {
|
|
53
|
+
detail.streetNumber = longName;
|
|
54
|
+
}
|
|
55
|
+
else if (types.includes('subpremise')) {
|
|
56
|
+
detail.unit = longName;
|
|
57
|
+
}
|
|
58
|
+
else if (types.includes('neighborhood') || types.includes('sublocality')) {
|
|
59
|
+
detail.neighborhood = longName;
|
|
60
|
+
}
|
|
61
|
+
else if (types.includes('locality')) {
|
|
62
|
+
detail.city = longName;
|
|
63
|
+
}
|
|
64
|
+
else if (types.includes('administrative_area_level_1')) {
|
|
65
|
+
detail.state = longName;
|
|
66
|
+
}
|
|
67
|
+
else if (types.includes('country')) {
|
|
68
|
+
detail.country = longName;
|
|
69
|
+
detail.countryCode = shortName;
|
|
70
|
+
}
|
|
71
|
+
else if (types.includes('postal_code')) {
|
|
72
|
+
detail.postalCode = longName;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return detail;
|
|
76
|
+
}
|
|
77
|
+
// ─── Main Component ───────────────────────────────────────────────────────────
|
|
78
|
+
export default function ErkLocationInput({ value, onChange, allowedModes, label, helperText, required = false, error = false, readOnly = false, inputRef, }) {
|
|
79
|
+
// Brand design theme
|
|
80
|
+
const { formStyle } = useAppSelector((state) => state.global);
|
|
81
|
+
// Google Maps API Key
|
|
82
|
+
const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? '';
|
|
83
|
+
// Autocomplete / Search States
|
|
84
|
+
const [loadingScript, setLoadingScript] = useState(false);
|
|
85
|
+
const [scriptError, setScriptError] = useState(false);
|
|
86
|
+
const [inputValue, setInputValue] = useState('');
|
|
87
|
+
const [options, setOptions] = useState([]);
|
|
88
|
+
const [searching, setSearching] = useState(false);
|
|
89
|
+
const [selectedPlace, setSelectedPlace] = useState(null);
|
|
90
|
+
// Modern Google Places Class References
|
|
91
|
+
const [autocompleteSuggestionClass, setAutocompleteSuggestionClass] = useState(null);
|
|
92
|
+
const [autocompleteSessionTokenClass, setAutocompleteSessionTokenClass] = useState(null);
|
|
93
|
+
const [geocoderClass, setGeocoderClass] = useState(null);
|
|
94
|
+
// Geolocation / GPS States
|
|
95
|
+
const [gpsLoading, setGpsLoading] = useState(false);
|
|
96
|
+
const [gpsError, setGpsError] = useState(null);
|
|
97
|
+
// Manual Modal States
|
|
98
|
+
const [manualModalOpen, setManualModalOpen] = useState(false);
|
|
99
|
+
const [localStreet, setLocalStreet] = useState('');
|
|
100
|
+
const [localStreetNumber, setLocalStreetNumber] = useState('');
|
|
101
|
+
const [localUnit, setLocalUnit] = useState('');
|
|
102
|
+
const [localNeighborhood, setLocalNeighborhood] = useState('');
|
|
103
|
+
const [localCity, setLocalCity] = useState('');
|
|
104
|
+
const [localState, setLocalState] = useState('');
|
|
105
|
+
const [localCountry, setLocalCountry] = useState('');
|
|
106
|
+
const [localPostalCode, setLocalPostalCode] = useState('');
|
|
107
|
+
// References for Google Services
|
|
108
|
+
const geocoderRef = useRef(null);
|
|
109
|
+
const sessionTokenRef = useRef(null);
|
|
110
|
+
// Load Google Maps Script if GOOGLE_PLACES is allowed
|
|
111
|
+
const isGooglePlacesAllowed = allowedModes.includes(LocationInputMode.GOOGLE_PLACES);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
const win = window;
|
|
114
|
+
if (isGooglePlacesAllowed && !readOnly) {
|
|
115
|
+
void Promise.resolve().then(() => {
|
|
116
|
+
setLoadingScript(true);
|
|
117
|
+
});
|
|
118
|
+
loadGoogleMaps(apiKey)
|
|
119
|
+
.then(async () => {
|
|
120
|
+
if (win.google?.maps?.importLibrary) {
|
|
121
|
+
try {
|
|
122
|
+
const { AutocompleteSuggestion, AutocompleteSessionToken } = await win.google.maps.importLibrary('places');
|
|
123
|
+
const { Geocoder } = await win.google.maps.importLibrary('geocoding');
|
|
124
|
+
setAutocompleteSuggestionClass(() => AutocompleteSuggestion);
|
|
125
|
+
setAutocompleteSessionTokenClass(() => AutocompleteSessionToken);
|
|
126
|
+
setGeocoderClass(() => Geocoder);
|
|
127
|
+
geocoderRef.current = new Geocoder();
|
|
128
|
+
sessionTokenRef.current = new AutocompleteSessionToken();
|
|
129
|
+
setLoadingScript(false);
|
|
130
|
+
setScriptError(false);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
console.error('Error importing Google Maps libraries:', err);
|
|
134
|
+
setLoadingScript(false);
|
|
135
|
+
setScriptError(true);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if (win.google?.maps) {
|
|
139
|
+
// Fallback if importLibrary doesn't exist
|
|
140
|
+
geocoderRef.current = new win.google.maps.Geocoder();
|
|
141
|
+
setLoadingScript(false);
|
|
142
|
+
setScriptError(false);
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
.catch((err) => {
|
|
146
|
+
console.error('Error loading Google Maps:', err);
|
|
147
|
+
setLoadingScript(false);
|
|
148
|
+
setScriptError(true);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}, [isGooglePlacesAllowed, apiKey, readOnly]);
|
|
152
|
+
// Google Places Search Hook/Debounce (Supports new AutocompleteSuggestion & fallback)
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
const win = window;
|
|
155
|
+
if (!win.google?.maps || !inputValue || inputValue.length < 3 || readOnly) {
|
|
156
|
+
void Promise.resolve().then(() => {
|
|
157
|
+
setOptions([]);
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
void Promise.resolve().then(() => {
|
|
162
|
+
setSearching(true);
|
|
163
|
+
});
|
|
164
|
+
const timeout = setTimeout(() => {
|
|
165
|
+
// Check if modern AutocompleteSuggestion API is available
|
|
166
|
+
if (autocompleteSuggestionClass) {
|
|
167
|
+
const request = {
|
|
168
|
+
input: inputValue,
|
|
169
|
+
};
|
|
170
|
+
if (sessionTokenRef.current) {
|
|
171
|
+
request.sessionToken = sessionTokenRef.current;
|
|
172
|
+
}
|
|
173
|
+
autocompleteSuggestionClass
|
|
174
|
+
.fetchAutocompleteSuggestions(request)
|
|
175
|
+
.then((response) => {
|
|
176
|
+
setSearching(false);
|
|
177
|
+
if (response.suggestions) {
|
|
178
|
+
const mapped = response.suggestions
|
|
179
|
+
.filter((s) => s.placePrediction)
|
|
180
|
+
.map((s) => ({
|
|
181
|
+
description: s.placePrediction.text.text,
|
|
182
|
+
place_id: s.placePrediction.placeId,
|
|
183
|
+
mainText: s.placePrediction.mainText?.text ?? s.placePrediction.text.text,
|
|
184
|
+
toPlace: () => s.placePrediction.toPlace(),
|
|
185
|
+
}));
|
|
186
|
+
setOptions(mapped);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
setOptions([]);
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
.catch((err) => {
|
|
193
|
+
console.error('Error fetching autocomplete suggestions:', err);
|
|
194
|
+
setSearching(false);
|
|
195
|
+
setOptions([]);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
else if (win.google.maps.places?.AutocompleteService) {
|
|
199
|
+
// Fallback to legacy AutocompleteService
|
|
200
|
+
const service = new win.google.maps.places.AutocompleteService();
|
|
201
|
+
service.getPlacePredictions({ input: inputValue, types: ['geocode', 'establishment'] }, (predictions, status) => {
|
|
202
|
+
setSearching(false);
|
|
203
|
+
if (status === 'OK' && predictions) {
|
|
204
|
+
setOptions(predictions.map((p) => ({
|
|
205
|
+
description: p.description,
|
|
206
|
+
place_id: p.place_id,
|
|
207
|
+
mainText: p.structured_formatting.main_text,
|
|
208
|
+
})));
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
setOptions([]);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
setSearching(false);
|
|
217
|
+
setOptions([]);
|
|
218
|
+
}
|
|
219
|
+
}, 300);
|
|
220
|
+
return () => {
|
|
221
|
+
clearTimeout(timeout);
|
|
222
|
+
};
|
|
223
|
+
}, [inputValue, readOnly, autocompleteSuggestionClass]);
|
|
224
|
+
// Map current value to Autocomplete object if available
|
|
225
|
+
const autocompleteValue = useMemo(() => {
|
|
226
|
+
if (selectedPlace)
|
|
227
|
+
return selectedPlace;
|
|
228
|
+
if (value?.address && value.source === LocationInputMode.GOOGLE_PLACES) {
|
|
229
|
+
return {
|
|
230
|
+
description: value.address,
|
|
231
|
+
place_id: value.placeId ?? '',
|
|
232
|
+
mainText: value.name ?? value.address,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}, [selectedPlace, value]);
|
|
237
|
+
// Initialize Manual Modal states when opened
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (manualModalOpen) {
|
|
240
|
+
void Promise.resolve().then(() => {
|
|
241
|
+
setLocalStreet(value?.addressDetail?.street ?? '');
|
|
242
|
+
setLocalStreetNumber(value?.addressDetail?.streetNumber ?? '');
|
|
243
|
+
setLocalUnit(value?.addressDetail?.unit ?? '');
|
|
244
|
+
setLocalNeighborhood(value?.addressDetail?.neighborhood ?? '');
|
|
245
|
+
setLocalCity(value?.addressDetail?.city ?? '');
|
|
246
|
+
setLocalState(value?.addressDetail?.state ?? '');
|
|
247
|
+
setLocalCountry(value?.addressDetail?.country ?? '');
|
|
248
|
+
setLocalPostalCode(value?.addressDetail?.postalCode ?? '');
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}, [manualModalOpen, value]);
|
|
252
|
+
// Geolocation triggering
|
|
253
|
+
const handleGPSFetch = () => {
|
|
254
|
+
if (!navigator.geolocation) {
|
|
255
|
+
setGpsError('La geolocalización no es compatible con este navegador.');
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
setGpsLoading(true);
|
|
259
|
+
setGpsError(null);
|
|
260
|
+
// Low accuracy fallback options to prevent VM/Desktop timeouts
|
|
261
|
+
const geoOptions = {
|
|
262
|
+
enableHighAccuracy: false,
|
|
263
|
+
timeout: 15000,
|
|
264
|
+
maximumAge: 300000, // Allow cached positions up to 5 minutes old
|
|
265
|
+
};
|
|
266
|
+
navigator.geolocation.getCurrentPosition((position) => {
|
|
267
|
+
const lat = position.coords.latitude;
|
|
268
|
+
const lng = position.coords.longitude;
|
|
269
|
+
const win = window;
|
|
270
|
+
const resolveLocation = () => {
|
|
271
|
+
const geocoderInstance = geocoderRef.current ??
|
|
272
|
+
(geocoderClass
|
|
273
|
+
? new geocoderClass()
|
|
274
|
+
: win.google?.maps?.Geocoder
|
|
275
|
+
? new win.google.maps.Geocoder()
|
|
276
|
+
: null);
|
|
277
|
+
if (geocoderInstance) {
|
|
278
|
+
geocoderInstance.geocode({ location: { lat, lng } }, (results, status) => {
|
|
279
|
+
let address = undefined;
|
|
280
|
+
let addressDetail = undefined;
|
|
281
|
+
if (status === 'OK' && results?.[0]) {
|
|
282
|
+
address = results[0].formatted_address;
|
|
283
|
+
addressDetail = parseAddressComponents(results[0].address_components);
|
|
284
|
+
}
|
|
285
|
+
onChange?.({
|
|
286
|
+
source: LocationInputMode.GPS,
|
|
287
|
+
latitude: lat,
|
|
288
|
+
longitude: lng,
|
|
289
|
+
address: address ?? `${lat.toFixed(6)}, ${lng.toFixed(6)}`,
|
|
290
|
+
addressDetail,
|
|
291
|
+
});
|
|
292
|
+
setGpsLoading(false);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
onChange?.({
|
|
297
|
+
source: LocationInputMode.GPS,
|
|
298
|
+
latitude: lat,
|
|
299
|
+
longitude: lng,
|
|
300
|
+
address: `${lat.toFixed(6)}, ${lng.toFixed(6)}`,
|
|
301
|
+
});
|
|
302
|
+
setGpsLoading(false);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
if (!win.google?.maps && apiKey) {
|
|
306
|
+
loadGoogleMaps(apiKey)
|
|
307
|
+
.then(() => {
|
|
308
|
+
resolveLocation();
|
|
309
|
+
})
|
|
310
|
+
.catch(() => {
|
|
311
|
+
onChange?.({
|
|
312
|
+
source: LocationInputMode.GPS,
|
|
313
|
+
latitude: lat,
|
|
314
|
+
longitude: lng,
|
|
315
|
+
address: `${lat.toFixed(6)}, ${lng.toFixed(6)}`,
|
|
316
|
+
});
|
|
317
|
+
setGpsLoading(false);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
resolveLocation();
|
|
322
|
+
}
|
|
323
|
+
}, (err) => {
|
|
324
|
+
console.error('GPS error:', err);
|
|
325
|
+
let message = 'No se pudo obtener la ubicación GPS.';
|
|
326
|
+
if (err.code === err.PERMISSION_DENIED) {
|
|
327
|
+
message =
|
|
328
|
+
'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).';
|
|
329
|
+
}
|
|
330
|
+
else if (err.code === err.TIMEOUT) {
|
|
331
|
+
message =
|
|
332
|
+
'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.';
|
|
333
|
+
}
|
|
334
|
+
setGpsError(message);
|
|
335
|
+
setGpsLoading(false);
|
|
336
|
+
}, geoOptions);
|
|
337
|
+
};
|
|
338
|
+
// Save Manual fields
|
|
339
|
+
const handleSaveManual = () => {
|
|
340
|
+
if (!localStreet || !localCity || !localCountry) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const updatedDetail = {
|
|
344
|
+
street: localStreet,
|
|
345
|
+
streetNumber: localStreetNumber,
|
|
346
|
+
unit: localUnit,
|
|
347
|
+
neighborhood: localNeighborhood,
|
|
348
|
+
city: localCity,
|
|
349
|
+
state: localState,
|
|
350
|
+
country: localCountry,
|
|
351
|
+
postalCode: localPostalCode,
|
|
352
|
+
};
|
|
353
|
+
const parts = [
|
|
354
|
+
localStreet,
|
|
355
|
+
localStreetNumber,
|
|
356
|
+
localUnit ? `Apto/Int ${localUnit}` : '',
|
|
357
|
+
localNeighborhood,
|
|
358
|
+
localCity,
|
|
359
|
+
localState,
|
|
360
|
+
localCountry,
|
|
361
|
+
].filter(Boolean);
|
|
362
|
+
const formattedAddress = parts.join(', ');
|
|
363
|
+
onChange?.({
|
|
364
|
+
source: LocationInputMode.MANUAL,
|
|
365
|
+
address: formattedAddress,
|
|
366
|
+
addressDetail: updatedDetail,
|
|
367
|
+
});
|
|
368
|
+
setManualModalOpen(false);
|
|
369
|
+
};
|
|
370
|
+
// Render Read-Only View
|
|
371
|
+
if (readOnly) {
|
|
372
|
+
return (_jsx("div", { className: styles.readOnlyContainer, style: {
|
|
373
|
+
'--eureka-outline': formStyle.outlineColor,
|
|
374
|
+
'--eureka-primary': formStyle.primaryColor,
|
|
375
|
+
'--eureka-text': formStyle.textColor,
|
|
376
|
+
'--eureka-bg': formStyle.standAloneBackgroundColor || '#fff',
|
|
377
|
+
}, 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: [_jsx(Typography, { className: styles.readOnlyLabel, children: label }), value?.name && _jsx(Typography, { className: styles.readOnlyName, children: value.name }), _jsx(Typography, { className: styles.readOnlyAddress, children: value?.address ?? 'Ubicación no proporcionada' }), value?.latitude !== undefined && value?.longitude !== undefined && (_jsxs(Typography, { className: styles.readOnlyCoords, children: ["Coordenadas: ", value.latitude.toFixed(6), ", ", value.longitude.toFixed(6)] }))] })] }) }));
|
|
378
|
+
}
|
|
379
|
+
const win = window;
|
|
380
|
+
return (_jsxs("div", { className: styles.container, style: {
|
|
381
|
+
'--eureka-outline': formStyle.outlineColor,
|
|
382
|
+
'--eureka-primary': formStyle.primaryColor,
|
|
383
|
+
'--eureka-text': formStyle.textColor,
|
|
384
|
+
'--eureka-bg': formStyle.standAloneBackgroundColor || '#fff',
|
|
385
|
+
}, children: [_jsxs(Typography, { variant: "subtitle2", color: "textSecondary", style: { fontWeight: 600, marginBottom: '2px', color: formStyle.textColor }, children: [label, " ", required && _jsx("span", { style: { color: formStyle.errorColor || 'red' }, children: "*" })] }), _jsx("div", { className: styles.inputRow, children: value?.address ? (_jsxs(_Fragment, { children: [_jsx("div", { style: { flex: 1 }, children: _jsx(ErkTextField, { label: "Ubicaci\u00F3n seleccionada", value: value.address, readOnly: true, error: error, helperText: helperText, required: required, inputRef: inputRef }) }), allowedModes.includes(LocationInputMode.MANUAL) && (_jsx(Tooltip, { title: "Editar direcci\u00F3n manualmente", children: _jsx(IconButton, { onClick: () => setManualModalOpen(true), className: styles.actionButton, children: _jsx(EditIcon, { fill: "currentColor" }) }) })), _jsx(Tooltip, { title: "Limpiar selecci\u00F3n", children: _jsx(IconButton, { onClick: () => {
|
|
386
|
+
onChange?.(null);
|
|
387
|
+
setSelectedPlace(null);
|
|
388
|
+
setInputValue('');
|
|
389
|
+
if (autocompleteSessionTokenClass) {
|
|
390
|
+
sessionTokenRef.current = new autocompleteSessionTokenClass();
|
|
391
|
+
}
|
|
392
|
+
}, className: styles.actionButton, children: _jsx(CloseIcon, { fill: "currentColor" }) }) })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { style: { flex: 1 }, children: loadingScript ? (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', height: '40px' }, children: [_jsx(CircularProgress, { size: 20 }), _jsx(Typography, { variant: "body2", children: "Cargando buscador..." })] })) : scriptError || !isGooglePlacesAllowed ? (_jsx(ErkTextField, { label: "Ingresa una ubicaci\u00F3n...", value: inputValue, onChange: (val) => {
|
|
393
|
+
setInputValue(val);
|
|
394
|
+
onChange?.({
|
|
395
|
+
source: LocationInputMode.MANUAL,
|
|
396
|
+
address: val,
|
|
397
|
+
});
|
|
398
|
+
}, error: error, helperText: helperText, required: required, inputRef: inputRef })) : (_jsx(Autocomplete, { options: options, getOptionLabel: (option) => option.description, filterOptions: (x) => x, loading: searching, value: autocompleteValue, popupIcon: searching ? _jsx(CircularProgress, { color: "inherit", size: 20 }) : undefined, noOptionsText: inputValue.length >= 3 ? 'No se encontraron resultados' : 'Escribe para buscar...', onInputChange: (_, newInputValue) => {
|
|
399
|
+
setInputValue(newInputValue);
|
|
400
|
+
}, onChange: (_, selection) => {
|
|
401
|
+
setSelectedPlace(selection);
|
|
402
|
+
if (selection) {
|
|
403
|
+
if (typeof selection.toPlace === 'function') {
|
|
404
|
+
const place = selection.toPlace();
|
|
405
|
+
place
|
|
406
|
+
.fetchFields({
|
|
407
|
+
fields: [
|
|
408
|
+
'displayName',
|
|
409
|
+
'formattedAddress',
|
|
410
|
+
'location',
|
|
411
|
+
'addressComponents',
|
|
412
|
+
],
|
|
413
|
+
})
|
|
414
|
+
.then(() => {
|
|
415
|
+
const lat = place.location.lat();
|
|
416
|
+
const lng = place.location.lng();
|
|
417
|
+
const detail = parseAddressComponents(place.addressComponents ?? []);
|
|
418
|
+
onChange?.({
|
|
419
|
+
source: LocationInputMode.GOOGLE_PLACES,
|
|
420
|
+
latitude: lat,
|
|
421
|
+
longitude: lng,
|
|
422
|
+
name: place.displayName ?? selection.mainText,
|
|
423
|
+
address: place.formattedAddress,
|
|
424
|
+
placeId: selection.place_id,
|
|
425
|
+
addressDetail: detail,
|
|
426
|
+
});
|
|
427
|
+
if (autocompleteSessionTokenClass) {
|
|
428
|
+
sessionTokenRef.current = new autocompleteSessionTokenClass();
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
.catch((err) => {
|
|
432
|
+
console.error('Error fetching place fields:', err);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
const geocoderInstance = geocoderRef.current ??
|
|
437
|
+
(geocoderClass
|
|
438
|
+
? new geocoderClass()
|
|
439
|
+
: win.google?.maps?.Geocoder
|
|
440
|
+
? new win.google.maps.Geocoder()
|
|
441
|
+
: null);
|
|
442
|
+
if (geocoderInstance) {
|
|
443
|
+
geocoderInstance.geocode({ placeId: selection.place_id }, (results, status) => {
|
|
444
|
+
if (status === 'OK' && results?.[0]) {
|
|
445
|
+
const result = results[0];
|
|
446
|
+
const lat = result.geometry.location.lat();
|
|
447
|
+
const lng = result.geometry.location.lng();
|
|
448
|
+
const detail = parseAddressComponents(result.address_components);
|
|
449
|
+
onChange?.({
|
|
450
|
+
source: LocationInputMode.GOOGLE_PLACES,
|
|
451
|
+
latitude: lat,
|
|
452
|
+
longitude: lng,
|
|
453
|
+
name: selection.mainText ?? selection.description,
|
|
454
|
+
address: result.formatted_address,
|
|
455
|
+
placeId: selection.place_id,
|
|
456
|
+
addressDetail: detail,
|
|
457
|
+
});
|
|
458
|
+
if (autocompleteSessionTokenClass) {
|
|
459
|
+
sessionTokenRef.current =
|
|
460
|
+
new autocompleteSessionTokenClass();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
onChange?.(null);
|
|
469
|
+
if (autocompleteSessionTokenClass) {
|
|
470
|
+
sessionTokenRef.current = new autocompleteSessionTokenClass();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}, renderInput: (params) => (_jsx(StyledTextField, { ...params, label: "Buscar direcci\u00F3n...", error: error, helperText: helperText, required: required, inputRef: inputRef })) })) }), allowedModes.includes(LocationInputMode.GPS) && (_jsx(Tooltip, { title: "Obtener ubicaci\u00F3n por GPS", children: _jsx(IconButton, { onClick: handleGPSFetch, disabled: gpsLoading, className: styles.actionButton, children: gpsLoading ? _jsx(CircularProgress, { size: 18 }) : _jsx(LocationIcon, { fill: "currentColor" }) }) })), allowedModes.includes(LocationInputMode.MANUAL) && (_jsx(Tooltip, { title: "Ingresar direcci\u00F3n manualmente", children: _jsx(IconButton, { onClick: () => setManualModalOpen(true), className: styles.actionButton, children: _jsx(EditIcon, { fill: "currentColor" }) }) }))] })) }), gpsError && (_jsx(Typography, { variant: "caption", color: "error", style: { display: 'block', marginTop: '4px' }, children: gpsError })), _jsxs(Dialog, { open: manualModalOpen, onClose: () => setManualModalOpen(false), maxWidth: "sm", fullWidth: true, slotProps: {
|
|
474
|
+
popper: { sx: { zIndex: 1400 } },
|
|
475
|
+
paper: {
|
|
476
|
+
style: {
|
|
477
|
+
borderRadius: '20px',
|
|
478
|
+
padding: '16px 24px',
|
|
479
|
+
backgroundColor: formStyle.stepBackgroundColor || '#fff',
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
}, children: [_jsx(DialogTitle, { style: {
|
|
483
|
+
fontWeight: 700,
|
|
484
|
+
padding: '0 0 16px 0',
|
|
485
|
+
color: formStyle.textColor,
|
|
486
|
+
fontFamily: 'inherit',
|
|
487
|
+
}, children: "Ingresar Direcci\u00F3n" }), _jsx(DialogContent, { style: {
|
|
488
|
+
padding: '16px 0',
|
|
489
|
+
borderTop: `1px solid ${formStyle.outlineColor}`,
|
|
490
|
+
borderBottom: `1px solid ${formStyle.outlineColor}`,
|
|
491
|
+
}, children: _jsxs("div", { className: styles.manualGrid, children: [_jsx("div", { className: styles.manualRow, children: _jsx("div", { className: styles.col12, children: _jsx(ErkTextField, { label: "Calle / Carrera / Avenida (L\u00EDnea 1)", value: localStreet, onChange: setLocalStreet, required: true }) }) }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col6, children: _jsx(ErkTextField, { label: "N\u00FAmero / Portal", value: localStreetNumber, onChange: setLocalStreetNumber }) }), _jsx("div", { className: styles.col6, children: _jsx(ErkTextField, { label: "Apto / Oficina / Interior", value: localUnit, onChange: setLocalUnit }) })] }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col6, children: _jsx(ErkTextField, { label: "Barrio / Zona", value: localNeighborhood, onChange: setLocalNeighborhood }) }), _jsx("div", { className: styles.col6, children: _jsx(ErkTextField, { label: "Ciudad", value: localCity, onChange: setLocalCity, required: true }) })] }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col4, children: _jsx(ErkTextField, { label: "Estado / Provincia / Dpto", value: localState, onChange: setLocalState }) }), _jsx("div", { className: styles.col4, children: _jsx(ErkTextField, { label: "C\u00F3digo Postal", value: localPostalCode, onChange: setLocalPostalCode }) }), _jsx("div", { className: styles.col4, children: _jsx(ErkTextField, { label: "Pa\u00EDs", value: localCountry, onChange: setLocalCountry, required: true }) })] })] }) }), _jsxs(DialogActions, { style: { padding: '16px 0 0 0', gap: '12px' }, children: [_jsx(ErkButton, { text: "Cancelar", variant: "outlined", onClick: () => setManualModalOpen(false), sx: {
|
|
492
|
+
borderColor: formStyle.outlineColor,
|
|
493
|
+
color: formStyle.textColor,
|
|
494
|
+
'&:hover': {
|
|
495
|
+
borderColor: formStyle.textColor,
|
|
496
|
+
backgroundColor: 'rgba(0, 0, 0, 0.04)',
|
|
497
|
+
},
|
|
498
|
+
} }), _jsx(ErkButton, { text: "Guardar", variant: "contained", onClick: handleSaveManual, disabled: !localStreet || !localCity || !localCountry })] })] })] }));
|
|
499
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
.actionButton {
|
|
17
|
+
border: 1px solid var(--eureka-outline) !important;
|
|
18
|
+
border-radius: 10px !important;
|
|
19
|
+
padding: 5px !important;
|
|
20
|
+
height: 33px !important;
|
|
21
|
+
width: 33px !important;
|
|
22
|
+
background-color: var(--eureka-bg) !important;
|
|
23
|
+
color: var(--eureka-text) !important;
|
|
24
|
+
flex-shrink: 0;
|
|
25
|
+
transition: all 0.2s ease-in-out;
|
|
26
|
+
margin-top: 5px !important;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.actionButton:hover:not(:disabled) {
|
|
30
|
+
background-color: rgba(0, 0, 0, 0.04) !important;
|
|
31
|
+
border-color: var(--eureka-primary) !important;
|
|
32
|
+
color: var(--eureka-primary) !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.actionButton:disabled {
|
|
36
|
+
opacity: 0.5;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.manualGrid {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: 12px;
|
|
43
|
+
width: 100%;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.manualRow {
|
|
47
|
+
display: flex;
|
|
48
|
+
gap: 12px;
|
|
49
|
+
width: 100%;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.col12 { width: 100%; }
|
|
53
|
+
.col8 { width: 66.666%; }
|
|
54
|
+
.col6 { width: 50%; }
|
|
55
|
+
.col4 { width: 33.333%; }
|
|
56
|
+
.col3 { width: 25%; }
|
|
57
|
+
|
|
58
|
+
@media (max-width: 600px) {
|
|
59
|
+
.manualRow {
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
gap: 12px;
|
|
62
|
+
}
|
|
63
|
+
.col8, .col6, .col4, .col3 {
|
|
64
|
+
width: 100%;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.readOnlyContainer {
|
|
69
|
+
width: 100%;
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.readOnlyCard {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: flex-start;
|
|
76
|
+
gap: 15px;
|
|
77
|
+
padding: 15px;
|
|
78
|
+
border: 1px solid var(--eureka-outline);
|
|
79
|
+
border-radius: 10px;
|
|
80
|
+
background-color: var(--eureka-bg);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.readOnlyIcon {
|
|
84
|
+
flex-shrink: 0;
|
|
85
|
+
margin-top: 2px;
|
|
86
|
+
width: 24px;
|
|
87
|
+
height: 24px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.readOnlyDetails {
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
gap: 4px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.readOnlyLabel {
|
|
97
|
+
font-size: 0.75rem !important;
|
|
98
|
+
color: #666;
|
|
99
|
+
font-weight: 500 !important;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.readOnlyName {
|
|
103
|
+
font-size: 1rem !important;
|
|
104
|
+
font-weight: 600 !important;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.readOnlyAddress {
|
|
108
|
+
font-size: 0.9rem !important;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.readOnlyCoords {
|
|
112
|
+
font-size: 0.8rem !important;
|
|
113
|
+
color: #888;
|
|
114
|
+
font-family: monospace;
|
|
115
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ErkNumberFieldProps {
|
|
3
|
+
/** Controlled numeric value. null = empty field. */
|
|
4
|
+
value?: number | null;
|
|
5
|
+
onChange?: (value: number | null) => void;
|
|
6
|
+
label?: string;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
/** Increment/decrement step. Defaults to 1. */
|
|
10
|
+
step?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Maximum decimal places.
|
|
13
|
+
* - Blocks extra decimal input live (rejects the keystroke)
|
|
14
|
+
* - Formats display on blur via Intl.NumberFormat
|
|
15
|
+
* - 0 = integers only
|
|
16
|
+
*/
|
|
17
|
+
decimals?: number;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
error?: boolean;
|
|
20
|
+
/** Show red border on error. */
|
|
21
|
+
errorBorder?: boolean;
|
|
22
|
+
helperText?: React.ReactNode;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
readOnly?: boolean;
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
size?: 'small' | 'medium';
|
|
27
|
+
fullWidth?: boolean;
|
|
28
|
+
/** Static text at the end of the field (e.g. "%", "USD"). */
|
|
29
|
+
suffix?: React.ReactNode;
|
|
30
|
+
/** Show increment/decrement spinner buttons. */
|
|
31
|
+
spinner?: boolean;
|
|
32
|
+
inputRef?: React.Ref<HTMLInputElement>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Pure number input built on Base UI NumberField + MUI styling.
|
|
36
|
+
* No form-step system dependency.
|
|
37
|
+
* Class wrapper for react-hook-form ref compatibility.
|
|
38
|
+
*/
|
|
39
|
+
declare class ErkNumberField extends React.Component<ErkNumberFieldProps> {
|
|
40
|
+
render(): JSX.Element;
|
|
41
|
+
}
|
|
42
|
+
export default ErkNumberField;
|