@arquimedes.co/eureka-forms 3.0.48-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.
Files changed (140) hide show
  1. package/dist/@Types/AvailabilityFormStep.d.ts +48 -0
  2. package/dist/@Types/AvailabilityFormStep.js +24 -0
  3. package/dist/@Types/BankAccountFormStep.d.ts +67 -0
  4. package/dist/@Types/BankAccountFormStep.js +14 -0
  5. package/dist/@Types/BookingFormStep.d.ts +156 -0
  6. package/dist/@Types/BookingFormStep.js +44 -0
  7. package/dist/@Types/CalendarFormStep.d.ts +192 -0
  8. package/dist/@Types/CalendarFormStep.js +48 -0
  9. package/dist/@Types/CommunicationChannelFormStep.d.ts +51 -0
  10. package/dist/@Types/CommunicationChannelFormStep.js +15 -0
  11. package/dist/@Types/Condition.d.ts +4 -6
  12. package/dist/@Types/ConsentFormStep.d.ts +36 -0
  13. package/dist/@Types/ConsentFormStep.js +8 -0
  14. package/dist/@Types/EntityFormFormStep.d.ts +66 -0
  15. package/dist/@Types/EntityFormFormStep.js +20 -0
  16. package/dist/@Types/EventFormStep.d.ts +52 -0
  17. package/dist/@Types/EventFormStep.js +13 -0
  18. package/dist/@Types/ExternalReferenceFormStep.d.ts +74 -0
  19. package/dist/@Types/ExternalReferenceFormStep.js +20 -0
  20. package/dist/@Types/Form.d.ts +0 -1
  21. package/dist/@Types/FormStep.d.ts +8 -10
  22. package/dist/@Types/FormStep.js +2 -1
  23. package/dist/@Types/LocationFormStep.d.ts +159 -0
  24. package/dist/@Types/LocationFormStep.js +46 -0
  25. package/dist/@Types/NumericFormSteps.d.ts +108 -0
  26. package/dist/@Types/NumericFormSteps.js +20 -0
  27. package/dist/@Types/PredefinedSelectorFormStep.d.ts +51 -0
  28. package/dist/@Types/PredefinedSelectorFormStep.js +39 -0
  29. package/dist/@Types/StepRef.d.ts +135 -0
  30. package/dist/@Types/StepRef.js +49 -0
  31. package/dist/App/App.js +1 -1
  32. package/dist/App/AppFunctions.js +3 -8
  33. package/dist/App/AppHooks.js +1 -20
  34. package/dist/Form/Form.js +2 -6
  35. package/dist/Form/Form.module.css +39 -48
  36. package/dist/FormSteps/ApiSelectorStep/MaterialApiSelectorStep/MaterialApiSelectorStep.js +4 -8
  37. package/dist/FormSteps/BookingStep/BookingStep.d.ts +17 -0
  38. package/dist/FormSteps/BookingStep/BookingStep.js +14 -0
  39. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.d.ts +3 -0
  40. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +362 -0
  41. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.module.css +174 -0
  42. package/dist/FormSteps/CBRStepMapper.js +1 -1
  43. package/dist/FormSteps/CalendarStep/CalendarStep.d.ts +7 -0
  44. package/dist/FormSteps/CalendarStep/CalendarStep.js +14 -0
  45. package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.d.ts +3 -0
  46. package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.js +269 -0
  47. package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.module.css +531 -0
  48. package/dist/FormSteps/ClassifierSelectorStep/MaterialClassifierSelectorStep/MaterialClassifierSelectorStep.js +2 -2
  49. package/dist/FormSteps/EntityValueStep/MaterialEntityValuePickerStep/MaterialEntityValuePickerStep.js +4 -52
  50. package/dist/FormSteps/LocationStep/LocationStep.d.ts +10 -0
  51. package/dist/FormSteps/LocationStep/LocationStep.js +14 -0
  52. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.d.ts +3 -0
  53. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.js +47 -0
  54. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.module.css +18 -0
  55. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.d.ts +3 -0
  56. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.js +168 -0
  57. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.module.css +94 -0
  58. package/dist/FormSteps/NumberStep/NumberStep.d.ts +10 -0
  59. package/dist/FormSteps/NumberStep/NumberStep.js +14 -0
  60. package/dist/FormSteps/Step.js +16 -0
  61. package/dist/FormSteps/StepFunctions.js +23 -6
  62. package/dist/FormSteps/StepFunctions.test.js +57 -3
  63. package/dist/FormSteps/TitleStep/MaterialTitleStep/MaterialTitleStep.js +1 -2
  64. package/dist/Icons/@ErkIcon.d.ts +2 -2
  65. package/dist/Icons/@ErkIcon.js +2 -2
  66. package/dist/Icons/AddIcon.d.ts +3 -0
  67. package/dist/Icons/AddIcon.js +7 -0
  68. package/dist/Icons/EditIcon.d.ts +3 -0
  69. package/dist/Icons/{EmailIcon.js → EditIcon.js} +3 -3
  70. package/dist/Icons/RemoveIcon.d.ts +3 -0
  71. package/dist/Icons/RemoveIcon.js +7 -0
  72. package/dist/Icons/ViewDayIcon.d.ts +3 -0
  73. package/dist/Icons/ViewDayIcon.js +7 -0
  74. package/dist/Icons/ViewWeekIcon.d.ts +3 -0
  75. package/dist/Icons/ViewWeekIcon.js +7 -0
  76. package/dist/Services/DraftService.js +1 -4
  77. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.d.ts +19 -0
  78. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.js +80 -0
  79. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.module.css +82 -0
  80. package/dist/Shared/ErkAddressAutocomplete/googlePlaces.d.ts +29 -0
  81. package/dist/Shared/ErkAddressAutocomplete/googlePlaces.js +139 -0
  82. package/dist/Shared/ErkDatePicker/ErkDatePicker.js +1 -3
  83. package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.d.ts +2 -3
  84. package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.js +1 -2
  85. package/dist/Shared/ErkLocationField/ErkLocationField.d.ts +23 -0
  86. package/dist/Shared/ErkLocationField/ErkLocationField.js +73 -0
  87. package/dist/Shared/ErkLocationField/ErkLocationField.module.css +29 -0
  88. package/dist/Shared/ErkLocationField/addressFields.d.ts +12 -0
  89. package/dist/Shared/ErkLocationField/addressFields.js +34 -0
  90. package/dist/Shared/ErkLocationField/locationRestrictions.d.ts +6 -0
  91. package/dist/Shared/ErkLocationField/locationRestrictions.js +51 -0
  92. package/dist/Shared/ErkLocationInput/ErkLocationInput.d.ts +15 -0
  93. package/dist/Shared/ErkLocationInput/ErkLocationInput.js +499 -0
  94. package/dist/Shared/ErkLocationInput/ErkLocationInput.module.css +115 -0
  95. package/dist/Shared/ErkNumberField/ErkNumberField.d.ts +42 -0
  96. package/dist/Shared/ErkNumberField/ErkNumberField.js +170 -0
  97. package/dist/Shared/ErkSelect/ErkSelect.d.ts +2 -2
  98. package/dist/Shared/InputIcon/InputIcon.js +0 -6
  99. package/dist/Shared/Navbar/Navbar.d.ts +1 -2
  100. package/dist/Shared/Navbar/Navbar.js +3 -22
  101. package/dist/Shared/Navbar/Navbar.module.css +8 -22
  102. package/dist/Shared/SmartDraftRenderer/SmartDraftRenderer.js +2 -8
  103. package/dist/States/GlobalSlice.d.ts +1 -4
  104. package/dist/States/GlobalSlice.js +1 -8
  105. package/dist/constants/ErkIconTypes.d.ts +1 -3
  106. package/dist/constants/ErkIconTypes.js +0 -2
  107. package/dist/constants/FormStepTypes.d.ts +6 -3
  108. package/dist/constants/FormStepTypes.js +4 -1
  109. package/dist/hooks.d.ts +0 -3
  110. package/dist/hooks.js +1 -17
  111. package/package.json +3 -9
  112. package/dist/@Types/ErkValue.d.ts +0 -16
  113. package/dist/@Types/ErkValue.js +0 -1
  114. package/dist/@Types/User.d.ts +0 -13
  115. package/dist/@Types/User.js +0 -1
  116. package/dist/Icons/EmailIcon.d.ts +0 -3
  117. package/dist/Icons/Entities/SchoolIcon.d.ts +0 -3
  118. package/dist/Icons/Entities/SchoolIcon.js +0 -7
  119. package/dist/Icons/GroupIcon.d.ts +0 -3
  120. package/dist/Icons/GroupIcon.js +0 -7
  121. package/dist/Icons/LockedIcon.d.ts +0 -3
  122. package/dist/Icons/LockedIcon.js +0 -7
  123. package/dist/Login/Login.module.css +0 -135
  124. package/dist/Login/LoginLayout.module.css +0 -68
  125. package/dist/Login/LoginPage.d.ts +0 -10
  126. package/dist/Login/LoginPage.js +0 -101
  127. package/dist/Login/LoginTextField.d.ts +0 -14
  128. package/dist/Login/LoginTextField.js +0 -29
  129. package/dist/Services/ApiSelectorService.d.ts +0 -366
  130. package/dist/Services/ApiSelectorService.js +0 -173
  131. package/dist/Services/ApiSelectorService.test.d.ts +0 -1
  132. package/dist/Services/ApiSelectorService.test.js +0 -87
  133. package/dist/Services/IntegrationService.d.ts +0 -21
  134. package/dist/Services/IntegrationService.js +0 -22
  135. package/dist/Services/UserService.d.ts +0 -10
  136. package/dist/Services/UserService.js +0 -21
  137. package/dist/Shared/SmartDraftRenderer/LinkDecorator.d.ts +0 -14
  138. package/dist/Shared/SmartDraftRenderer/LinkDecorator.js +0 -48
  139. package/dist/constants/ErkValueTypes.d.ts +0 -6
  140. package/dist/constants/ErkValueTypes.js +0 -7
@@ -0,0 +1,3 @@
1
+ import { IconProps } from './@ErkIcon';
2
+ declare function ViewWeekIcon(props: IconProps): JSX.Element;
3
+ export default ViewWeekIcon;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useIconProps } from './@ErkIcon';
3
+ function ViewWeekIcon(props) {
4
+ const iconProps = useIconProps(props);
5
+ return (_jsx("svg", { ...iconProps, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M6 5H3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h3c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zm14 0h-3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h3c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zm-7 0h-3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h3c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1z" }) }));
6
+ }
7
+ export default ViewWeekIcon;
@@ -21,10 +21,7 @@ export const DraftApi = RootApi.injectEndpoints({
21
21
  });
22
22
  }
23
23
  else {
24
- response = await axiosInstance.post(`/form/draft/${apiKey}`, {
25
- property,
26
- dependencies,
27
- });
24
+ response = await axiosInstance.post(`/form/draft/${apiKey}`, dependencies);
28
25
  }
29
26
  return { data: response.data };
30
27
  },
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { LocationStepValue } from '../../@Types/LocationFormStep';
3
+ export interface ErkAddressAutocompleteProps {
4
+ value?: LocationStepValue | null;
5
+ onChange: (value: LocationStepValue) => void;
6
+ /** ISO-2 para acotar las sugerencias (ej. ['co']). */
7
+ regionCodes?: string[];
8
+ placeholder?: string;
9
+ error?: boolean;
10
+ required?: boolean;
11
+ readOnly?: boolean;
12
+ inputRef?: React.Ref<HTMLInputElement>;
13
+ }
14
+ /**
15
+ * Buscador de direcciones con el `Autocomplete` de Base UI + Google Places.
16
+ * Emite un `LocationStepValue` (source GOOGLE_PLACES) con el desglose estructurado.
17
+ */
18
+ declare function ErkAddressAutocomplete({ value, onChange, regionCodes, placeholder, error, required, readOnly, inputRef, }: ErkAddressAutocompleteProps): JSX.Element;
19
+ export default ErkAddressAutocomplete;
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { Autocomplete } from '@base-ui/react/autocomplete';
4
+ import { fetchAddressSuggestions, loadPlaces, newSessionToken, resolvePrediction, } from './googlePlaces';
5
+ import styles from './ErkAddressAutocomplete.module.css';
6
+ /**
7
+ * Buscador de direcciones con el `Autocomplete` de Base UI + Google Places.
8
+ * Emite un `LocationStepValue` (source GOOGLE_PLACES) con el desglose estructurado.
9
+ */
10
+ function ErkAddressAutocomplete({ value, onChange, regionCodes = ['co'], placeholder = 'Buscar dirección…', error = false, required = false, readOnly = false, inputRef, }) {
11
+ const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? '';
12
+ const [ready, setReady] = useState(false);
13
+ const [inputValue, setInputValue] = useState(value?.address ?? value?.name ?? '');
14
+ const [options, setOptions] = useState([]);
15
+ const [loading, setLoading] = useState(false);
16
+ const [hasSearched, setHasSearched] = useState(false);
17
+ const debounceRef = useRef(undefined);
18
+ const sessionTokenRef = useRef(undefined);
19
+ useEffect(() => {
20
+ if (readOnly)
21
+ return;
22
+ let active = true;
23
+ void loadPlaces(apiKey)
24
+ .then(() => {
25
+ if (!active)
26
+ return;
27
+ sessionTokenRef.current = newSessionToken();
28
+ setReady(true);
29
+ })
30
+ .catch(() => {
31
+ if (active)
32
+ setReady(false);
33
+ });
34
+ return () => {
35
+ active = false;
36
+ };
37
+ }, [apiKey, readOnly]);
38
+ const handleInput = useCallback((text) => {
39
+ setInputValue(text);
40
+ if (debounceRef.current)
41
+ clearTimeout(debounceRef.current);
42
+ if (!ready || !text || text.length < 3) {
43
+ setOptions([]);
44
+ setHasSearched(false);
45
+ return;
46
+ }
47
+ setLoading(true);
48
+ debounceRef.current = setTimeout(() => {
49
+ void (async () => {
50
+ try {
51
+ setOptions(await fetchAddressSuggestions(text, {
52
+ sessionToken: sessionTokenRef.current,
53
+ regionCodes,
54
+ }));
55
+ }
56
+ catch {
57
+ setOptions([]);
58
+ }
59
+ finally {
60
+ setLoading(false);
61
+ setHasSearched(true);
62
+ }
63
+ })();
64
+ }, 300);
65
+ }, [ready, regionCodes]);
66
+ const handleSelect = useCallback(async (prediction) => {
67
+ try {
68
+ const stepValue = await resolvePrediction(prediction);
69
+ setInputValue(stepValue.address ?? stepValue.name ?? prediction.label);
70
+ setOptions([]);
71
+ onChange(stepValue);
72
+ }
73
+ finally {
74
+ // Cada selección cierra la sesión de billing de Google.
75
+ sessionTokenRef.current = newSessionToken();
76
+ }
77
+ }, [onChange]);
78
+ return (_jsxs(Autocomplete.Root, { items: options, mode: "none", value: inputValue, onValueChange: handleInput, disabled: readOnly, children: [_jsx(Autocomplete.Input, { ref: inputRef, placeholder: placeholder, required: required, readOnly: readOnly, className: error ? `${styles.input} ${styles.inputError}` : styles.input }), _jsx(Autocomplete.Portal, { children: _jsx(Autocomplete.Positioner, { className: styles.positioner, sideOffset: 4, children: _jsxs(Autocomplete.Popup, { className: styles.popup, children: [loading && _jsx("div", { className: styles.status, children: "Buscando\u2026" }), _jsx(Autocomplete.Empty, { className: styles.status, children: hasSearched ? 'No se encontraron resultados' : 'Escribe para buscar…' }), _jsx(Autocomplete.List, { className: styles.list, children: (option) => (_jsxs(Autocomplete.Item, { value: option, className: styles.item, onClick: () => void handleSelect(option), children: [_jsx("span", { className: styles.optionLabel, children: option.label }), option.description && (_jsx("span", { className: styles.optionDescription, children: option.description }))] }, option.placeId)) })] }) }) })] }));
79
+ }
80
+ export default ErkAddressAutocomplete;
@@ -0,0 +1,82 @@
1
+ .input {
2
+ width: 100%;
3
+ box-sizing: border-box;
4
+ height: 40px;
5
+ padding: 6px 12px;
6
+ font-size: 1rem;
7
+ color: var(--eureka-text, #333);
8
+ background-color: var(--eureka-bg, #fff);
9
+ border: 1px solid var(--eureka-outline, #ccc);
10
+ border-radius: 10px;
11
+ outline: none;
12
+ }
13
+
14
+ .input:hover:not(:disabled) {
15
+ border-color: var(--eureka-primary, #1976d2);
16
+ }
17
+
18
+ .input:focus {
19
+ border-color: var(--eureka-primary, #1976d2);
20
+ border-width: 2px;
21
+ padding: 5px 11px;
22
+ }
23
+
24
+ .input:disabled {
25
+ opacity: 0.5;
26
+ }
27
+
28
+ .inputError {
29
+ border-color: var(--eureka-error, #e53935);
30
+ }
31
+
32
+ .positioner {
33
+ z-index: 1400;
34
+ width: var(--anchor-width);
35
+ }
36
+
37
+ .popup {
38
+ box-sizing: border-box;
39
+ max-height: 320px;
40
+ overflow-y: auto;
41
+ padding: 4px;
42
+ background-color: var(--eureka-bg, #fff);
43
+ border: 1px solid var(--eureka-outline, #ccc);
44
+ border-radius: 10px;
45
+ box-shadow: 0 4px 16px rgb(0 0 0 / 12%);
46
+ }
47
+
48
+ .list {
49
+ display: flex;
50
+ flex-direction: column;
51
+ }
52
+
53
+ .status {
54
+ padding: 10px 12px;
55
+ font-size: 0.875rem;
56
+ color: var(--eureka-text, #666);
57
+ opacity: 0.7;
58
+ }
59
+
60
+ .item {
61
+ display: flex;
62
+ flex-direction: column;
63
+ gap: 2px;
64
+ padding: 8px 12px;
65
+ border-radius: 8px;
66
+ cursor: pointer;
67
+ }
68
+
69
+ .item[data-highlighted],
70
+ .item:hover {
71
+ background-color: var(--eureka-primary, #1976d2);
72
+ color: #fff;
73
+ }
74
+
75
+ .optionLabel {
76
+ font-size: 0.9375rem;
77
+ }
78
+
79
+ .optionDescription {
80
+ font-size: 0.8125rem;
81
+ opacity: 0.7;
82
+ }
@@ -0,0 +1,29 @@
1
+ import { LocationAddressDetail, LocationStepValue } from '../../@Types/LocationFormStep';
2
+ /** Asegura el script cargado y las clases de Places listas. Idempotente. */
3
+ export declare function loadPlaces(apiKey: string): Promise<void>;
4
+ /** Token de sesión nuevo (mejora el billing/relevancia de Google). */
5
+ export declare function newSessionToken(): any;
6
+ /** Reverse-geocoding para el modo GPS: coordenadas → dirección estructurada. */
7
+ export declare function reverseGeocode(apiKey: string, lat: number, lng: number): Promise<{
8
+ address?: string;
9
+ addressDetail: LocationAddressDetail;
10
+ }>;
11
+ export interface AddressPrediction {
12
+ placeId: string;
13
+ label: string;
14
+ description: string;
15
+ toPlace: () => any;
16
+ }
17
+ export interface FetchSuggestionsOptions {
18
+ sessionToken?: any;
19
+ /** ISO-2, ej. ['co'] */
20
+ regionCodes?: string[];
21
+ }
22
+ export declare function fetchAddressSuggestions(input: string, options?: FetchSuggestionsOptions): Promise<AddressPrediction[]>;
23
+ /**
24
+ * Descompone los `addressComponents` de un Place en el shape estructurado del step.
25
+ * Acepta el formato nuevo (`longText`/`shortText`) y el legacy (`long_name`/`short_name`).
26
+ */
27
+ export declare function parseAddressComponents(components: any[] | null | undefined): LocationAddressDetail;
28
+ /** Resuelve una predicción a un `LocationStepValue` completo (fetchFields + parse). */
29
+ export declare function resolvePrediction(prediction: AddressPrediction): Promise<LocationStepValue>;
@@ -0,0 +1,139 @@
1
+ import { LocationInputMode } from '../../@Types/LocationFormStep';
2
+ /**
3
+ * Carga y consulta de Google Places (nueva API `AutocompleteSuggestion`).
4
+ * Encapsula el script loader + importLibrary que antes vivía inline en ErkLocationInput,
5
+ * para reutilizarlo desde el autocomplete de Base UI sin duplicar lógica.
6
+ */
7
+ // Google's new Places classes (AutocompleteSuggestion/SessionToken) are untyped in the
8
+ // installed @types/google.maps, so they're kept as `any` — same approach as ErkLocationInput.
9
+ let loadPromise = null;
10
+ let suggestionClass = null;
11
+ let sessionTokenClass = null;
12
+ let geocoderClass = null;
13
+ function loadScript(apiKey) {
14
+ if (typeof window === 'undefined')
15
+ return Promise.resolve();
16
+ const win = window;
17
+ if (win.google?.maps?.importLibrary)
18
+ return Promise.resolve();
19
+ if (loadPromise)
20
+ return loadPromise;
21
+ loadPromise = new Promise((resolve, reject) => {
22
+ const script = document.createElement('script');
23
+ script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&loading=async`;
24
+ script.async = true;
25
+ script.defer = true;
26
+ script.onload = () => resolve();
27
+ script.onerror = () => {
28
+ loadPromise = null;
29
+ reject(new Error('Google Maps script failed to load.'));
30
+ };
31
+ document.head.appendChild(script);
32
+ });
33
+ return loadPromise;
34
+ }
35
+ /** Asegura el script cargado y las clases de Places listas. Idempotente. */
36
+ export async function loadPlaces(apiKey) {
37
+ await loadScript(apiKey);
38
+ const win = window;
39
+ if (win.google?.maps?.importLibrary && !suggestionClass) {
40
+ const places = await win.google.maps.importLibrary('places');
41
+ suggestionClass = places.AutocompleteSuggestion;
42
+ sessionTokenClass = places.AutocompleteSessionToken;
43
+ }
44
+ }
45
+ /** Token de sesión nuevo (mejora el billing/relevancia de Google). */
46
+ export function newSessionToken() {
47
+ return sessionTokenClass ? new sessionTokenClass() : undefined;
48
+ }
49
+ /** Reverse-geocoding para el modo GPS: coordenadas → dirección estructurada. */
50
+ export async function reverseGeocode(apiKey, lat, lng) {
51
+ await loadScript(apiKey);
52
+ const win = window;
53
+ if (win.google?.maps?.importLibrary && !geocoderClass) {
54
+ const geocoding = await win.google.maps.importLibrary('geocoding');
55
+ geocoderClass = geocoding.Geocoder;
56
+ }
57
+ else if (!geocoderClass && win.google?.maps?.Geocoder) {
58
+ geocoderClass = win.google.maps.Geocoder;
59
+ }
60
+ if (!geocoderClass)
61
+ return { addressDetail: {} };
62
+ const geocoder = new geocoderClass();
63
+ return new Promise((resolve) => {
64
+ geocoder.geocode({ location: { lat, lng } }, (results, status) => {
65
+ if (status === 'OK' && results?.[0]) {
66
+ resolve({
67
+ address: results[0].formatted_address,
68
+ addressDetail: parseAddressComponents(results[0].address_components),
69
+ });
70
+ }
71
+ else {
72
+ resolve({ addressDetail: {} });
73
+ }
74
+ });
75
+ });
76
+ }
77
+ export async function fetchAddressSuggestions(input, options) {
78
+ if (!suggestionClass)
79
+ return [];
80
+ const request = { input };
81
+ if (options?.sessionToken)
82
+ request.sessionToken = options.sessionToken;
83
+ if (options?.regionCodes)
84
+ request.includedRegionCodes = options.regionCodes;
85
+ const { suggestions } = await suggestionClass.fetchAutocompleteSuggestions(request);
86
+ return suggestions
87
+ .filter((s) => s.placePrediction)
88
+ .map((s) => ({
89
+ placeId: s.placePrediction.placeId,
90
+ label: s.placePrediction.mainText?.text ?? s.placePrediction.text.text,
91
+ description: s.placePrediction.secondaryText?.text ?? s.placePrediction.text.text,
92
+ toPlace: () => s.placePrediction.toPlace(),
93
+ }));
94
+ }
95
+ /**
96
+ * Descompone los `addressComponents` de un Place en el shape estructurado del step.
97
+ * Acepta el formato nuevo (`longText`/`shortText`) y el legacy (`long_name`/`short_name`).
98
+ */
99
+ export function parseAddressComponents(components) {
100
+ const detail = {};
101
+ for (const component of components ?? []) {
102
+ const types = component.types;
103
+ const longName = component.longText ?? component.long_name;
104
+ const shortName = component.shortText ?? component.short_name;
105
+ if (types.includes('route'))
106
+ detail.street = longName;
107
+ else if (types.includes('street_number'))
108
+ detail.streetNumber = longName;
109
+ else if (types.includes('subpremise'))
110
+ detail.unit = longName;
111
+ else if (types.includes('neighborhood') || types.includes('sublocality'))
112
+ detail.neighborhood = longName;
113
+ else if (types.includes('locality'))
114
+ detail.city = longName;
115
+ else if (types.includes('administrative_area_level_1'))
116
+ detail.state = longName;
117
+ else if (types.includes('country')) {
118
+ detail.country = longName;
119
+ detail.countryCode = shortName;
120
+ }
121
+ else if (types.includes('postal_code'))
122
+ detail.postalCode = longName;
123
+ }
124
+ return detail;
125
+ }
126
+ /** Resuelve una predicción a un `LocationStepValue` completo (fetchFields + parse). */
127
+ export async function resolvePrediction(prediction) {
128
+ const place = prediction.toPlace();
129
+ await place.fetchFields({ fields: ['displayName', 'formattedAddress', 'location', 'addressComponents'] });
130
+ return {
131
+ source: LocationInputMode.GOOGLE_PLACES,
132
+ latitude: place.location?.lat(),
133
+ longitude: place.location?.lng(),
134
+ name: place.displayName ?? prediction.label,
135
+ address: place.formattedAddress ?? undefined,
136
+ placeId: prediction.placeId,
137
+ addressDetail: parseAddressComponents(place.addressComponents),
138
+ };
139
+ }
@@ -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 { MobileDateRangePickerProps as MuiDateRangePickerProps } from '@mui/x-date-pickers-pro/MobileDateRangePicker';
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/MobileDateRangePicker';
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,23 @@
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 (rehecho con Base UI). Ofrece los modos habilitados
19
+ * (buscador de Google Places, GPS, entrada manual) y aplica la config por campo
20
+ * (fijo / omitir) al valor resultante.
21
+ */
22
+ declare function ErkLocationField({ value, onChange, allowedModes, defaultMode, addressFields, regionCodes, error, helperText, required, readOnly, inputRef, }: ErkLocationFieldProps): JSX.Element;
23
+ export default ErkLocationField;
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useState } from 'react';
3
+ import { AddressFieldMode, LocationInputMode, } from '../../@Types/LocationFormStep';
4
+ import ErkAddressAutocomplete from '../ErkAddressAutocomplete/ErkAddressAutocomplete';
5
+ import { reverseGeocode } from '../ErkAddressAutocomplete/googlePlaces';
6
+ import { ADDRESS_FIELDS, applyAddressFields, fieldConfig } from './addressFields';
7
+ import ErkTextField from '../ErkTextField/ErkTextField';
8
+ import ErkButton from '../ErkButton/ErkButton';
9
+ import LocationIcon from '../../Icons/LocationIcon';
10
+ import styles from './ErkLocationField.module.css';
11
+ const MODE_LABELS = {
12
+ [LocationInputMode.GOOGLE_PLACES]: 'Buscar',
13
+ [LocationInputMode.GPS]: 'Mi ubicación',
14
+ [LocationInputMode.MANUAL]: 'Manual',
15
+ };
16
+ /**
17
+ * Campo de ubicación (rehecho con Base UI). Ofrece los modos habilitados
18
+ * (buscador de Google Places, GPS, entrada manual) y aplica la config por campo
19
+ * (fijo / omitir) al valor resultante.
20
+ */
21
+ function ErkLocationField({ value, onChange, allowedModes, defaultMode, addressFields, regionCodes = ['co'], error = false, helperText, required = false, readOnly = false, inputRef, }) {
22
+ const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? '';
23
+ const initialMode = defaultMode && allowedModes.includes(defaultMode) ? defaultMode : allowedModes[0];
24
+ const [mode, setMode] = useState(initialMode);
25
+ const [gpsLoading, setGpsLoading] = useState(false);
26
+ const [gpsError, setGpsError] = useState(null);
27
+ const handleGoogle = useCallback((resolved) => {
28
+ onChange({ ...resolved, addressDetail: applyAddressFields(resolved.addressDetail ?? {}, addressFields) });
29
+ }, [onChange, addressFields]);
30
+ const handleGps = useCallback(() => {
31
+ if (!navigator.geolocation) {
32
+ setGpsError('La geolocalización no es compatible con este navegador.');
33
+ return;
34
+ }
35
+ setGpsLoading(true);
36
+ setGpsError(null);
37
+ navigator.geolocation.getCurrentPosition((position) => {
38
+ const lat = position.coords.latitude;
39
+ const lng = position.coords.longitude;
40
+ void reverseGeocode(apiKey, lat, lng)
41
+ .then(({ address, addressDetail }) => {
42
+ onChange({
43
+ source: LocationInputMode.GPS,
44
+ latitude: lat,
45
+ longitude: lng,
46
+ address: address ?? `${lat.toFixed(6)}, ${lng.toFixed(6)}`,
47
+ addressDetail: applyAddressFields(addressDetail, addressFields),
48
+ });
49
+ })
50
+ .finally(() => setGpsLoading(false));
51
+ }, () => {
52
+ setGpsError('No se pudo obtener la ubicación.');
53
+ setGpsLoading(false);
54
+ }, { enableHighAccuracy: false, timeout: 15000, maximumAge: 300000 });
55
+ }, [apiKey, onChange, addressFields]);
56
+ const updateManualField = useCallback((key, text) => {
57
+ const base = value?.source === LocationInputMode.MANUAL ? (value.addressDetail ?? {}) : {};
58
+ const nextDetail = applyAddressFields({ ...base, [key]: text || undefined }, addressFields);
59
+ const address = ADDRESS_FIELDS.map(({ key: k }) => nextDetail[k])
60
+ .filter(Boolean)
61
+ .join(', ');
62
+ onChange({ source: LocationInputMode.MANUAL, address, addressDetail: nextDetail });
63
+ }, [value, onChange, addressFields]);
64
+ const manualDetail = value?.source === LocationInputMode.MANUAL ? (value.addressDetail ?? {}) : {};
65
+ return (_jsxs("div", { className: styles.container, children: [allowedModes.length > 1 && (_jsx("div", { className: styles.modeSwitch, children: allowedModes.map((m) => (_jsx(ErkButton, { color: m === mode ? 'primary' : 'secondary', disabled: readOnly, onClick: () => setMode(m), children: MODE_LABELS[m] }, m))) })), mode === LocationInputMode.GOOGLE_PLACES && (_jsx(ErkAddressAutocomplete, { value: value, onChange: handleGoogle, regionCodes: regionCodes, error: error, required: required, readOnly: readOnly, inputRef: inputRef })), mode === LocationInputMode.GPS && (_jsxs(ErkButton, { color: "primary", loading: gpsLoading, disabled: readOnly, onClick: handleGps, children: [_jsx(LocationIcon, { fill: "currentColor" }), " Usar mi ubicaci\u00F3n actual"] })), mode === LocationInputMode.MANUAL && (_jsx("div", { className: styles.manualGrid, children: ADDRESS_FIELDS.map(({ key, label }) => {
66
+ const config = fieldConfig(addressFields, key);
67
+ if (config.mode === AddressFieldMode.OMIT)
68
+ return null;
69
+ const fixed = config.mode === AddressFieldMode.FIXED;
70
+ return (_jsx(ErkTextField, { label: fixed ? `${label} (fijo)` : label, value: fixed ? (config.fixedValue ?? '') : (manualDetail[key] ?? ''), readOnly: fixed || readOnly, onChange: (text) => updateManualField(key, text) }, key));
71
+ }) })), (gpsError ?? helperText) && (_jsx("div", { className: error || gpsError ? styles.errorText : styles.helperText, children: gpsError ?? helperText }))] }));
72
+ }
73
+ export default ErkLocationField;
@@ -0,0 +1,29 @@
1
+ .container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 10px;
5
+ width: 100%;
6
+ }
7
+
8
+ .modeSwitch {
9
+ display: flex;
10
+ gap: 8px;
11
+ flex-wrap: wrap;
12
+ }
13
+
14
+ .manualGrid {
15
+ display: grid;
16
+ grid-template-columns: repeat(2, 1fr);
17
+ gap: 10px;
18
+ }
19
+
20
+ .helperText {
21
+ font-size: 12px;
22
+ color: var(--eureka-text, #666);
23
+ opacity: 0.7;
24
+ }
25
+
26
+ .errorText {
27
+ font-size: 12px;
28
+ color: var(--eureka-error, #e53935);
29
+ }
@@ -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
+ }