@arquimedes.co/eureka-forms 3.0.55-new-steps → 3.0.57-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 +1 -1
- package/dist/@Types/BookingFormStep.d.ts +1 -1
- package/dist/@Types/CalendarFormStep.d.ts +9 -12
- package/dist/@Types/CalendarFormStep.js +4 -7
- package/dist/FormSteps/AvailabilityStep/AvailabilityStep.d.ts +7 -0
- package/dist/FormSteps/{CalendarStep/CalendarStep.js → AvailabilityStep/AvailabilityStep.js} +4 -4
- package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.d.ts +8 -0
- package/dist/FormSteps/{CalendarStep/MaterialCalendarStep/MaterialCalendarStep.js → AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.js} +3 -3
- package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.js +9 -2
- package/dist/FormSteps/Step.js +3 -3
- package/dist/Shared/ErkLocationField/ErkLocationField.d.ts +5 -4
- package/dist/Shared/ErkLocationField/ErkLocationField.js +83 -31
- package/dist/Shared/ErkLocationField/ErkLocationField.module.css +132 -8
- package/dist/Shared/ErkLocationField/ErkLocationField.test.d.ts +1 -0
- package/dist/Shared/ErkLocationField/ErkLocationField.test.js +82 -0
- package/dist/constants/FormStepTypes.d.ts +1 -1
- package/dist/constants/FormStepTypes.js +1 -1
- package/package.json +1 -1
- package/dist/FormSteps/CalendarStep/CalendarStep.d.ts +0 -7
- package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.d.ts +0 -8
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Este archivo existe solo para compatibilidad retroactiva.
|
|
4
4
|
* Usar `CalendarFormStep.ts` directamente.
|
|
5
5
|
*/
|
|
6
|
-
export type {
|
|
6
|
+
export type { AvailabilityStep } from './CalendarFormStep';
|
|
7
7
|
export type { CalendarValue as AvailabilityValue } from './CalendarFormStep';
|
|
8
8
|
export { CalendarEventCategory as AvailabilityEventCategory } from './CalendarFormStep';
|
|
9
9
|
export { CalendarDefaultView } from './CalendarFormStep';
|
|
@@ -117,7 +117,7 @@ export interface BookingStep extends GBaseStep {
|
|
|
117
117
|
* se usa para filtrar los slots. Útil cuando el dueño del calendario también
|
|
118
118
|
* llena el formulario y define su disponibilidad en el mismo flujo.
|
|
119
119
|
*/
|
|
120
|
-
|
|
120
|
+
availabilityRef?: DynamicRef<CalendarValue, 'AVAILABILITY'>;
|
|
121
121
|
size: 1 | 2 | 3 | 4;
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Tipos del paso AVAILABILITY (pregunta de disponibilidad) y del valor de
|
|
3
|
+
* calendario compartido con el paso BOOKING.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* bloques de disponibilidad por modalidad, bloquear horas y más.
|
|
7
|
-
*
|
|
8
|
-
* Renombrado desde `AvailabilityFormStep`. El archivo anterior re-exporta
|
|
9
|
-
* desde aquí por compatibilidad retroactiva.
|
|
5
|
+
* El calendario completo (Google-like) vive en el admin; el form solo captura
|
|
6
|
+
* ventanas de disponibilidad con `ErkAvailabilityPicker` (grid semanal compacto).
|
|
10
7
|
*/
|
|
11
8
|
import { GBaseStep } from './GenericFormSteps';
|
|
12
9
|
import FormStepTypes from '../constants/FormStepTypes';
|
|
@@ -94,11 +91,11 @@ export interface CalendarEventTemplate {
|
|
|
94
91
|
maxConcurrentBookings?: number;
|
|
95
92
|
}
|
|
96
93
|
/**
|
|
97
|
-
* Paso de tipo `
|
|
98
|
-
*
|
|
94
|
+
* Paso de tipo `AVAILABILITY`: pregunta de disponibilidad compacta (grid
|
|
95
|
+
* semanal día × hora). Las ventanas capturadas pueden alimentar un paso BOOKING.
|
|
99
96
|
*/
|
|
100
|
-
export interface
|
|
101
|
-
type: FormStepTypes.
|
|
97
|
+
export interface AvailabilityStep extends GBaseStep {
|
|
98
|
+
type: FormStepTypes.AVAILABILITY;
|
|
102
99
|
label: string;
|
|
103
100
|
description: string | null;
|
|
104
101
|
required: boolean;
|
|
@@ -208,5 +205,5 @@ export interface CalendarValue {
|
|
|
208
205
|
/** Zona horaria en la que se capturaron los datos. */
|
|
209
206
|
timezone?: string;
|
|
210
207
|
}
|
|
211
|
-
export type {
|
|
208
|
+
export type { AvailabilityStep as CalendarStep };
|
|
212
209
|
export type { CalendarValue as AvailabilityValue };
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Tipos del paso AVAILABILITY (pregunta de disponibilidad) y del valor de
|
|
3
|
+
* calendario compartido con el paso BOOKING.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* bloques de disponibilidad por modalidad, bloquear horas y más.
|
|
7
|
-
*
|
|
8
|
-
* Renombrado desde `AvailabilityFormStep`. El archivo anterior re-exporta
|
|
9
|
-
* desde aquí por compatibilidad retroactiva.
|
|
5
|
+
* El calendario completo (Google-like) vive en el admin; el form solo captura
|
|
6
|
+
* ventanas de disponibilidad con `ErkAvailabilityPicker` (grid semanal compacto).
|
|
10
7
|
*/
|
|
11
8
|
// ===========================================================================
|
|
12
9
|
// Enums
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AvailabilityStep } from '../../@Types/CalendarFormStep';
|
|
2
|
+
import { StepProps } from '../Step';
|
|
3
|
+
export interface AvailabilityStepProps extends StepProps {
|
|
4
|
+
step: AvailabilityStep;
|
|
5
|
+
}
|
|
6
|
+
declare function AvailabilityStepComponent(props: AvailabilityStepProps): JSX.Element;
|
|
7
|
+
export default AvailabilityStepComponent;
|
package/dist/FormSteps/{CalendarStep/CalendarStep.js → AvailabilityStep/AvailabilityStep.js}
RENAMED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { FormStyleTypes } from '../../constants/FormStepTypes';
|
|
3
|
-
import
|
|
3
|
+
import MaterialAvailabilityStep from './MaterialAvailabilityStep/MaterialAvailabilityStep';
|
|
4
4
|
import { useAppSelector } from '../../hooks';
|
|
5
|
-
function
|
|
5
|
+
function AvailabilityStepComponent(props) {
|
|
6
6
|
const { formStyle } = useAppSelector((state) => state.global);
|
|
7
7
|
switch (formStyle.type) {
|
|
8
8
|
case FormStyleTypes.MATERIAL:
|
|
9
9
|
default: {
|
|
10
|
-
return _jsx(
|
|
10
|
+
return _jsx(MaterialAvailabilityStep, { ...props });
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
export default
|
|
14
|
+
export default AvailabilityStepComponent;
|
package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AvailabilityStepProps } from '../AvailabilityStep';
|
|
2
|
+
/**
|
|
3
|
+
* Conector del paso AVAILABILITY: pregunta de disponibilidad compacta
|
|
4
|
+
* (grid semanal día × hora). El calendario completo vive en el admin; el form
|
|
5
|
+
* solo captura ventanas disponibles que un paso BOOKING puede consumir.
|
|
6
|
+
*/
|
|
7
|
+
declare function MaterialAvailabilityStep({ step, editable }: AvailabilityStepProps): JSX.Element;
|
|
8
|
+
export default MaterialAvailabilityStep;
|
|
@@ -6,11 +6,11 @@ import { selectBreakPoint, useAppSelector } from '../../../hooks';
|
|
|
6
6
|
import { calcStepWidth } from '../../StepFunctions';
|
|
7
7
|
import ErkAvailabilityPicker from '../../../Shared/ErkAvailabilityPicker/ErkAvailabilityPicker';
|
|
8
8
|
/**
|
|
9
|
-
* Conector del paso
|
|
9
|
+
* Conector del paso AVAILABILITY: pregunta de disponibilidad compacta
|
|
10
10
|
* (grid semanal día × hora). El calendario completo vive en el admin; el form
|
|
11
11
|
* solo captura ventanas disponibles que un paso BOOKING puede consumir.
|
|
12
12
|
*/
|
|
13
|
-
function
|
|
13
|
+
function MaterialAvailabilityStep({ step, editable }) {
|
|
14
14
|
const currentBreakPoint = useAppSelector(selectBreakPoint);
|
|
15
15
|
const { postview, formStyle } = useAppSelector((state) => state.global);
|
|
16
16
|
const formCtx = useContext(FormContext);
|
|
@@ -29,4 +29,4 @@ function MaterialCalendarStep({ step, editable }) {
|
|
|
29
29
|
});
|
|
30
30
|
return (_jsx("div", { ref: ref, style: cssVars, children: _jsx(ErkAvailabilityPicker, { value: value, onChange: onChange, label: step.label, description: step.description, minTime: step.minTime, maxTime: step.maxTime, showWeekends: step.showWeekends, timezone: step.timezone, readOnly: isReadOnly, error: error?.message }) }));
|
|
31
31
|
}
|
|
32
|
-
export default
|
|
32
|
+
export default MaterialAvailabilityStep;
|
|
@@ -11,7 +11,7 @@ import { validateRestrictions } from '../../../Shared/ErkLocationField/locationR
|
|
|
11
11
|
import styles from './MaterialLocationStep.module.css';
|
|
12
12
|
function MaterialLocationStep({ step, editable }) {
|
|
13
13
|
const currentBreakPoint = useAppSelector(selectBreakPoint);
|
|
14
|
-
const { postview } = useAppSelector((state) => state.global);
|
|
14
|
+
const { postview, formStyle } = useAppSelector((state) => state.global);
|
|
15
15
|
const form = useContext(FormContext);
|
|
16
16
|
const isReadOnly = !editable || postview;
|
|
17
17
|
const widthStyle = currentBreakPoint <= step.size ? '100%' : calcStepWidth(step.size, form.size);
|
|
@@ -42,6 +42,13 @@ function MaterialLocationStep({ step, editable }) {
|
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
});
|
|
45
|
-
return (_jsxs("div", { className: styles.container, style: {
|
|
45
|
+
return (_jsxs("div", { className: styles.container, style: {
|
|
46
|
+
width: widthStyle,
|
|
47
|
+
'--eureka-outline': formStyle.outlineColor,
|
|
48
|
+
'--eureka-primary': formStyle.primaryColor,
|
|
49
|
+
'--eureka-text': formStyle.textColor,
|
|
50
|
+
'--eureka-error': formStyle.errorColor,
|
|
51
|
+
'--eureka-bg': formStyle.standAloneBackgroundColor ?? '#fff',
|
|
52
|
+
}, children: [step.label && _jsxs("div", { className: styles.label, children: [step.label, step.required && _jsx("span", { className: styles.required, children: " *" })] }), _jsx(ErkLocationField, { value: value, onChange: onChange, allowedModes: step.allowedModes, defaultMode: step.defaultMode, addressFields: step.addressFields, helperText: error?.message ?? step.description, required: step.required, error: !!error, readOnly: isReadOnly, inputRef: ref })] }));
|
|
46
53
|
}
|
|
47
54
|
export default MaterialLocationStep;
|
package/dist/FormSteps/Step.js
CHANGED
|
@@ -27,7 +27,7 @@ import PhoneInputStep from './PhoneInputStep/PhoneInputStep';
|
|
|
27
27
|
import LocationStepComponent from './LocationStep/LocationStep';
|
|
28
28
|
import NumberStepComponent from './NumberStep/NumberStep';
|
|
29
29
|
import BookingStepComponent from './BookingStep/BookingStep';
|
|
30
|
-
import
|
|
30
|
+
import AvailabilityStepComponent from './AvailabilityStep/AvailabilityStep';
|
|
31
31
|
function StepComponent({ step, ...props }) {
|
|
32
32
|
const { postview, partial } = useAppSelector((state) => state.global);
|
|
33
33
|
const { customSteps, customClientInfoStep } = useContext(CustomContext);
|
|
@@ -87,8 +87,8 @@ function StepComponent({ step, ...props }) {
|
|
|
87
87
|
case FormStepTypes.BOOKING: {
|
|
88
88
|
return _jsx(BookingStepComponent, { ...props, step: step, editable: editable });
|
|
89
89
|
}
|
|
90
|
-
case FormStepTypes.
|
|
91
|
-
return _jsx(
|
|
90
|
+
case FormStepTypes.AVAILABILITY: {
|
|
91
|
+
return _jsx(AvailabilityStepComponent, { ...props, step: step, editable: editable });
|
|
92
92
|
}
|
|
93
93
|
case FormStepTypes.DATEPICKER_RANGE: {
|
|
94
94
|
return _jsx(DatePickerRangeStep, { ...props, step: step, editable: editable });
|
|
@@ -15,9 +15,10 @@ export interface ErkLocationFieldProps {
|
|
|
15
15
|
inputRef?: React.Ref<HTMLInputElement>;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Campo de ubicación
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Campo de ubicación: una sola fila con el buscador de direcciones (Base UI +
|
|
19
|
+
* Google Places) y botones de acción — GPS y edición manual (en modal). Al
|
|
20
|
+
* seleccionar, el campo muestra la dirección con opciones de editar/limpiar.
|
|
21
|
+
* La config por campo (fijo / omitir) se aplica a todos los modos.
|
|
21
22
|
*/
|
|
22
|
-
declare function ErkLocationField({ value, onChange, allowedModes,
|
|
23
|
+
declare function ErkLocationField({ value, onChange, allowedModes, addressFields, regionCodes, error, helperText, required, readOnly, inputRef, }: ErkLocationFieldProps): JSX.Element;
|
|
23
24
|
export default ErkLocationField;
|
|
@@ -1,29 +1,47 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useState } from 'react';
|
|
3
|
+
import { CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Tooltip } from '@mui/material';
|
|
3
4
|
import { AddressFieldMode, LocationInputMode, } from '../../@Types/LocationFormStep';
|
|
4
5
|
import ErkAddressAutocomplete from '../ErkAddressAutocomplete/ErkAddressAutocomplete';
|
|
5
6
|
import { reverseGeocode } from '../ErkAddressAutocomplete/googlePlaces';
|
|
6
|
-
import {
|
|
7
|
+
import { applyAddressFields, fieldConfig } from './addressFields';
|
|
7
8
|
import ErkTextField from '../ErkTextField/ErkTextField';
|
|
8
9
|
import ErkButton from '../ErkButton/ErkButton';
|
|
9
10
|
import LocationIcon from '../../Icons/LocationIcon';
|
|
11
|
+
import EditIcon from '../../Icons/EditIcon';
|
|
12
|
+
import CloseIcon from '../../Icons/CloseIcon';
|
|
10
13
|
import styles from './ErkLocationField.module.css';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/** Claves que el formulario manual exige (si no están omitidas ni fijas). */
|
|
15
|
+
const MANUAL_REQUIRED_KEYS = ['street', 'city', 'country'];
|
|
16
|
+
/** Dirección legible a partir del desglose, en el orden natural de una dirección. */
|
|
17
|
+
function composeAddress(detail) {
|
|
18
|
+
return [
|
|
19
|
+
detail.street,
|
|
20
|
+
detail.streetNumber,
|
|
21
|
+
detail.unit ? `Apto/Int ${detail.unit}` : '',
|
|
22
|
+
detail.neighborhood,
|
|
23
|
+
detail.city,
|
|
24
|
+
detail.state,
|
|
25
|
+
detail.country,
|
|
26
|
+
]
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.join(', ');
|
|
29
|
+
}
|
|
16
30
|
/**
|
|
17
|
-
* Campo de ubicación
|
|
18
|
-
*
|
|
19
|
-
*
|
|
31
|
+
* Campo de ubicación: una sola fila con el buscador de direcciones (Base UI +
|
|
32
|
+
* Google Places) y botones de acción — GPS y edición manual (en modal). Al
|
|
33
|
+
* seleccionar, el campo muestra la dirección con opciones de editar/limpiar.
|
|
34
|
+
* La config por campo (fijo / omitir) se aplica a todos los modos.
|
|
20
35
|
*/
|
|
21
|
-
function ErkLocationField({ value, onChange, allowedModes,
|
|
36
|
+
function ErkLocationField({ value, onChange, allowedModes, addressFields, regionCodes = ['co'], error = false, helperText, required = false, readOnly = false, inputRef, }) {
|
|
22
37
|
const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? '';
|
|
23
|
-
const
|
|
24
|
-
const
|
|
38
|
+
const canGoogle = allowedModes.includes(LocationInputMode.GOOGLE_PLACES);
|
|
39
|
+
const canGps = allowedModes.includes(LocationInputMode.GPS);
|
|
40
|
+
const canManual = allowedModes.includes(LocationInputMode.MANUAL);
|
|
25
41
|
const [gpsLoading, setGpsLoading] = useState(false);
|
|
26
42
|
const [gpsError, setGpsError] = useState(null);
|
|
43
|
+
const [manualOpen, setManualOpen] = useState(false);
|
|
44
|
+
const [manualDetail, setManualDetail] = useState({});
|
|
27
45
|
const handleGoogle = useCallback((resolved) => {
|
|
28
46
|
onChange({ ...resolved, addressDetail: applyAddressFields(resolved.addressDetail ?? {}, addressFields) });
|
|
29
47
|
}, [onChange, addressFields]);
|
|
@@ -48,26 +66,60 @@ function ErkLocationField({ value, onChange, allowedModes, defaultMode, addressF
|
|
|
48
66
|
});
|
|
49
67
|
})
|
|
50
68
|
.finally(() => setGpsLoading(false));
|
|
51
|
-
}, () => {
|
|
52
|
-
|
|
69
|
+
}, (err) => {
|
|
70
|
+
let message = 'No se pudo obtener la ubicación GPS.';
|
|
71
|
+
if (err.code === err.PERMISSION_DENIED) {
|
|
72
|
+
message =
|
|
73
|
+
'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).';
|
|
74
|
+
}
|
|
75
|
+
else if (err.code === err.TIMEOUT) {
|
|
76
|
+
message =
|
|
77
|
+
'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.';
|
|
78
|
+
}
|
|
79
|
+
setGpsError(message);
|
|
53
80
|
setGpsLoading(false);
|
|
54
81
|
}, { enableHighAccuracy: false, timeout: 15000, maximumAge: 300000 });
|
|
55
82
|
}, [apiKey, onChange, addressFields]);
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
const openManual = useCallback(() => {
|
|
84
|
+
setManualDetail(value?.addressDetail ?? {});
|
|
85
|
+
setManualOpen(true);
|
|
86
|
+
}, [value]);
|
|
87
|
+
const manualFieldValue = useCallback((key) => {
|
|
88
|
+
const config = fieldConfig(addressFields, key);
|
|
89
|
+
if (config.mode === AddressFieldMode.FIXED)
|
|
90
|
+
return config.fixedValue ?? '';
|
|
91
|
+
return manualDetail[key] ?? '';
|
|
92
|
+
}, [addressFields, manualDetail]);
|
|
93
|
+
const canSaveManual = MANUAL_REQUIRED_KEYS.every((key) => {
|
|
94
|
+
const config = fieldConfig(addressFields, key);
|
|
95
|
+
if (config.mode === AddressFieldMode.OMIT)
|
|
96
|
+
return true;
|
|
97
|
+
if (config.mode === AddressFieldMode.FIXED)
|
|
98
|
+
return !!config.fixedValue;
|
|
99
|
+
return !!manualDetail[key];
|
|
100
|
+
});
|
|
101
|
+
const saveManual = () => {
|
|
102
|
+
if (!canSaveManual)
|
|
103
|
+
return;
|
|
104
|
+
const detail = applyAddressFields(manualDetail, addressFields);
|
|
105
|
+
onChange({ source: LocationInputMode.MANUAL, address: composeAddress(detail), addressDetail: detail });
|
|
106
|
+
setManualOpen(false);
|
|
107
|
+
};
|
|
108
|
+
const clearValue = () => {
|
|
109
|
+
onChange(null);
|
|
110
|
+
setGpsError(null);
|
|
111
|
+
};
|
|
112
|
+
const manualField = (key, label, requiredField = false) => {
|
|
113
|
+
const config = fieldConfig(addressFields, key);
|
|
114
|
+
if (config.mode === AddressFieldMode.OMIT)
|
|
115
|
+
return null;
|
|
116
|
+
const fixed = config.mode === AddressFieldMode.FIXED;
|
|
117
|
+
return (_jsx(ErkTextField, { label: fixed ? `${label} (fijo)` : label, value: manualFieldValue(key), readOnly: fixed, required: requiredField && !fixed, onChange: (text) => setManualDetail((detail) => ({ ...detail, [key]: text || undefined })) }));
|
|
118
|
+
};
|
|
119
|
+
// Vista de solo lectura: tarjeta con el resumen de la ubicación
|
|
120
|
+
if (readOnly) {
|
|
121
|
+
return (_jsx("div", { className: styles.readOnlyContainer, children: _jsxs("div", { className: styles.readOnlyCard, children: [_jsx("div", { className: styles.readOnlyIcon, children: _jsx(LocationIcon, { fill: "var(--eureka-primary)" }) }), _jsxs("div", { className: styles.readOnlyDetails, children: [value?.name && _jsx("span", { className: styles.readOnlyName, children: value.name }), _jsx("span", { className: styles.readOnlyAddress, children: value?.address ?? 'Ubicación no proporcionada' }), value?.latitude !== undefined && value?.longitude !== undefined && (_jsxs("span", { className: styles.readOnlyCoords, children: ["Coordenadas: ", value.latitude.toFixed(6), ", ", value.longitude.toFixed(6)] }))] })] }) }));
|
|
122
|
+
}
|
|
123
|
+
return (_jsxs("div", { className: styles.container, children: [_jsx("div", { className: styles.inputRow, children: value?.address ? (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.grow, children: _jsx(ErkTextField, { label: "Ubicaci\u00F3n seleccionada", value: value.address, readOnly: true, error: error, helperText: helperText, required: required, inputRef: inputRef }) }), canManual && (_jsx(Tooltip, { title: "Editar direcci\u00F3n manualmente", children: _jsx(IconButton, { "aria-label": "Editar direcci\u00F3n manualmente", className: styles.actionButton, onClick: openManual, children: _jsx(EditIcon, { fill: "currentColor" }) }) })), _jsx(Tooltip, { title: "Limpiar selecci\u00F3n", children: _jsx(IconButton, { "aria-label": "Limpiar selecci\u00F3n", className: styles.actionButton, onClick: clearValue, children: _jsx(CloseIcon, { fill: "currentColor" }) }) })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.grow, children: canGoogle ? (_jsx(ErkAddressAutocomplete, { value: value, onChange: handleGoogle, regionCodes: regionCodes, error: error, required: required, inputRef: inputRef })) : canManual ? (_jsx(ErkTextField, { label: "Ingresa una ubicaci\u00F3n...", value: value?.address ?? '', onChange: (text) => onChange(text ? { source: LocationInputMode.MANUAL, address: text } : null), error: error, helperText: helperText, required: required, inputRef: inputRef })) : (_jsx(ErkTextField, { label: "Usa el bot\u00F3n para obtener tu ubicaci\u00F3n", value: "", readOnly: true, error: error, helperText: helperText, required: required, inputRef: inputRef })) }), canGps && (_jsx(Tooltip, { title: "Obtener ubicaci\u00F3n por GPS", children: _jsx(IconButton, { "aria-label": "Obtener ubicaci\u00F3n por GPS", className: styles.actionButton, disabled: gpsLoading, onClick: handleGps, children: gpsLoading ? _jsx(CircularProgress, { size: 18 }) : _jsx(LocationIcon, { fill: "currentColor" }) }) })), canManual && (_jsx(Tooltip, { title: "Ingresar direcci\u00F3n manualmente", children: _jsx(IconButton, { "aria-label": "Ingresar direcci\u00F3n manualmente", className: styles.actionButton, onClick: openManual, children: _jsx(EditIcon, { fill: "currentColor" }) }) }))] })) }), (gpsError ?? (!value?.address && canGoogle && helperText)) && (_jsx("div", { className: error || gpsError ? styles.errorText : styles.helperText, children: gpsError ?? helperText })), _jsxs(Dialog, { open: manualOpen, onClose: () => setManualOpen(false), maxWidth: "sm", fullWidth: true, slotProps: { paper: { className: styles.dialogPaper } }, children: [_jsx(DialogTitle, { className: styles.dialogTitle, children: "Ingresar Direcci\u00F3n" }), _jsx(DialogContent, { className: styles.dialogContent, children: _jsxs("div", { className: styles.manualGrid, children: [_jsx("div", { className: styles.manualRow, children: _jsx("div", { className: styles.col12, children: manualField('street', 'Calle / Carrera / Avenida (Línea 1)', true) }) }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col6, children: manualField('streetNumber', 'Número / Portal') }), _jsx("div", { className: styles.col6, children: manualField('unit', 'Apto / Oficina / Interior') })] }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col6, children: manualField('neighborhood', 'Barrio / Zona') }), _jsx("div", { className: styles.col6, children: manualField('city', 'Ciudad', true) })] }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col4, children: manualField('state', 'Estado / Provincia / Dpto') }), _jsx("div", { className: styles.col4, children: manualField('postalCode', 'Código Postal') }), _jsx("div", { className: styles.col4, children: manualField('country', 'País', true) })] })] }) }), _jsxs(DialogActions, { style: { padding: '16px 0 0 0', gap: '12px' }, children: [_jsx(ErkButton, { text: "Cancelar", variant: "outlined", onClick: () => setManualOpen(false) }), _jsx(ErkButton, { text: "Guardar", variant: "contained", disabled: !canSaveManual, onClick: saveManual })] })] })] }));
|
|
72
124
|
}
|
|
73
125
|
export default ErkLocationField;
|
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
.container {
|
|
2
|
+
width: 100%;
|
|
2
3
|
display: flex;
|
|
3
4
|
flex-direction: column;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.inputRow {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: flex-start;
|
|
4
12
|
gap: 10px;
|
|
5
13
|
width: 100%;
|
|
6
14
|
}
|
|
7
15
|
|
|
8
|
-
.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
flex-wrap: wrap;
|
|
16
|
+
.grow {
|
|
17
|
+
flex: 1;
|
|
18
|
+
min-width: 0;
|
|
12
19
|
}
|
|
13
20
|
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
.actionButton {
|
|
22
|
+
border: 1px solid var(--eureka-outline, #d9dee6) !important;
|
|
23
|
+
border-radius: 10px !important;
|
|
24
|
+
padding: 5px !important;
|
|
25
|
+
height: 33px !important;
|
|
26
|
+
width: 33px !important;
|
|
27
|
+
background-color: var(--eureka-bg, #fff) !important;
|
|
28
|
+
color: var(--eureka-text, #333) !important;
|
|
29
|
+
flex-shrink: 0;
|
|
30
|
+
transition: all 0.2s ease-in-out;
|
|
31
|
+
margin-top: 5px !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.actionButton:hover:not(:disabled) {
|
|
35
|
+
background-color: rgba(0, 0, 0, 0.04) !important;
|
|
36
|
+
border-color: var(--eureka-primary, #1976d2) !important;
|
|
37
|
+
color: var(--eureka-primary, #1976d2) !important;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.actionButton:disabled {
|
|
41
|
+
opacity: 0.5;
|
|
18
42
|
}
|
|
19
43
|
|
|
20
44
|
.helperText {
|
|
@@ -27,3 +51,103 @@
|
|
|
27
51
|
font-size: 12px;
|
|
28
52
|
color: var(--eureka-error, #e53935);
|
|
29
53
|
}
|
|
54
|
+
|
|
55
|
+
/* Formulario manual (modal) */
|
|
56
|
+
.manualGrid {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
gap: 12px;
|
|
60
|
+
width: 100%;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.manualRow {
|
|
64
|
+
display: flex;
|
|
65
|
+
gap: 12px;
|
|
66
|
+
width: 100%;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.col12 {
|
|
70
|
+
width: 100%;
|
|
71
|
+
}
|
|
72
|
+
.col6 {
|
|
73
|
+
width: 50%;
|
|
74
|
+
}
|
|
75
|
+
.col4 {
|
|
76
|
+
width: 33.333%;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@media (max-width: 600px) {
|
|
80
|
+
.manualRow {
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
gap: 12px;
|
|
83
|
+
}
|
|
84
|
+
.col6,
|
|
85
|
+
.col4 {
|
|
86
|
+
width: 100%;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Vista de solo lectura */
|
|
91
|
+
.readOnlyContainer {
|
|
92
|
+
width: 100%;
|
|
93
|
+
box-sizing: border-box;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.readOnlyCard {
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: flex-start;
|
|
99
|
+
gap: 15px;
|
|
100
|
+
padding: 15px;
|
|
101
|
+
border: 1px solid var(--eureka-outline, #d9dee6);
|
|
102
|
+
border-radius: 10px;
|
|
103
|
+
background-color: var(--eureka-bg, #fff);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.readOnlyIcon {
|
|
107
|
+
flex-shrink: 0;
|
|
108
|
+
margin-top: 2px;
|
|
109
|
+
width: 24px;
|
|
110
|
+
height: 24px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.readOnlyDetails {
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-direction: column;
|
|
116
|
+
gap: 4px;
|
|
117
|
+
color: var(--eureka-text, #333);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.readOnlyName {
|
|
121
|
+
font-size: 1rem;
|
|
122
|
+
font-weight: 600;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.readOnlyAddress {
|
|
126
|
+
font-size: 0.9rem;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.readOnlyCoords {
|
|
130
|
+
font-size: 0.8rem;
|
|
131
|
+
opacity: 0.65;
|
|
132
|
+
font-family: monospace;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Modal de dirección manual (portal: usa fallbacks del theme del form) */
|
|
136
|
+
.dialogPaper {
|
|
137
|
+
border-radius: 20px !important;
|
|
138
|
+
padding: 16px 24px;
|
|
139
|
+
background-color: var(--eureka-bg, #fff) !important;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.dialogTitle {
|
|
143
|
+
font-weight: 700 !important;
|
|
144
|
+
padding: 0 0 16px 0 !important;
|
|
145
|
+
color: var(--eureka-text, #333);
|
|
146
|
+
font-family: inherit !important;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.dialogContent {
|
|
150
|
+
padding: 16px 0 !important;
|
|
151
|
+
border-top: 1px solid var(--eureka-outline, #d9dee6);
|
|
152
|
+
border-bottom: 1px solid var(--eureka-outline, #d9dee6);
|
|
153
|
+
}
|
|
@@ -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
|
+
});
|
|
@@ -27,7 +27,7 @@ export var FormStepTypes;
|
|
|
27
27
|
FormStepTypes["API_SELECTOR"] = "API_SELECTOR";
|
|
28
28
|
FormStepTypes["LOCATION"] = "LOCATION";
|
|
29
29
|
FormStepTypes["NUMBER"] = "NUMBER";
|
|
30
|
-
FormStepTypes["
|
|
30
|
+
FormStepTypes["AVAILABILITY"] = "AVAILABILITY";
|
|
31
31
|
FormStepTypes["BOOKING"] = "BOOKING";
|
|
32
32
|
})(FormStepTypes || (FormStepTypes = {}));
|
|
33
33
|
export var OptionTypes;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arquimedes.co/eureka-forms",
|
|
3
3
|
"repository": "git://github.com/Arquimede5/Eureka-Forms.git",
|
|
4
|
-
"version":"3.0.
|
|
4
|
+
"version":"3.0.57-new-steps",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"watch": "tsgo --noEmit --watch --project tsconfig.app.json",
|
|
7
7
|
"start": "vite",
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { CalendarStep } from '../../@Types/CalendarFormStep';
|
|
2
|
-
import { StepProps } from '../Step';
|
|
3
|
-
export interface CalendarStepProps extends StepProps {
|
|
4
|
-
step: CalendarStep;
|
|
5
|
-
}
|
|
6
|
-
declare function CalendarStepComponent(props: CalendarStepProps): JSX.Element;
|
|
7
|
-
export default CalendarStepComponent;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { CalendarStepProps } from '../CalendarStep';
|
|
2
|
-
/**
|
|
3
|
-
* Conector del paso CALENDAR/Availability: pregunta de disponibilidad compacta
|
|
4
|
-
* (grid semanal día × hora). El calendario completo vive en el admin; el form
|
|
5
|
-
* solo captura ventanas disponibles que un paso BOOKING puede consumir.
|
|
6
|
-
*/
|
|
7
|
-
declare function MaterialCalendarStep({ step, editable }: CalendarStepProps): JSX.Element;
|
|
8
|
-
export default MaterialCalendarStep;
|