pixelon 0.5.0 → 0.5.2
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/components/molecules/chat/StructuredContent/components/LocationField/usePlaces.d.ts +11 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/ContextoDeLaAccion.d.ts +8 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/types.d.ts +28 -1
- package/dist/components/molecules/chat/StructuredContent/components/ServicesMap/types.d.ts +6 -0
- package/dist/components/organisms/chat/Chat/hooks/useAutoScroll.d.ts +8 -1
- package/dist/components/templates/lumi/Crud/helpers/confirmSummary.d.ts +13 -0
- package/dist/components/templates/lumi/Crud/types.d.ts +6 -0
- package/dist/index.esm.js +193 -16
- package/dist/index.js +193 -16
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
package/dist/components/molecules/chat/StructuredContent/components/LocationField/usePlaces.d.ts
CHANGED
|
@@ -11,6 +11,17 @@ export declare function usePlaceSuggestions(query: string, enabled: boolean): {
|
|
|
11
11
|
} | null>;
|
|
12
12
|
clear: () => void;
|
|
13
13
|
};
|
|
14
|
+
/** SPEC-131 T6 — la dirección formateada de Google repite tramos.
|
|
15
|
+
*
|
|
16
|
+
* Medido: el `formattedAddress` de una calle de El Poblado vuelve como
|
|
17
|
+
* «Cl 10 #43-20, El Poblado, Medellín, El Poblado, Medellín, Antioquia», porque el nombre del
|
|
18
|
+
* lugar ya trae el barrio y la ciudad que luego añade la parte administrativa. Esa cadena se
|
|
19
|
+
* guarda tal cual y aparece en el chat, en la ficha, en la tabla y en Conversaciones.
|
|
20
|
+
*
|
|
21
|
+
* Se comparan los tramos sin tildes ni mayúsculas, que es donde estaba el otro caso visto
|
|
22
|
+
* («Itagüí, Itagüi»), y se conserva el primero de cada uno: el orden de una dirección va de lo
|
|
23
|
+
* particular a lo general, así que el que sobra siempre es el segundo. */
|
|
24
|
+
export declare function limpiarDireccion(address: string): string;
|
|
14
25
|
/**
|
|
15
26
|
* Traduce una coordenada a una dirección legible.
|
|
16
27
|
* Requiere Geocoding habilitado en la clave del navegador; si no lo está, devuelve null y el
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChatEventBus } from '../../../../../../core/eventBus/chatEventBus';
|
|
2
|
+
import type { RowActionContexto } from './types';
|
|
3
|
+
export interface ContextoDeLaAccionProps {
|
|
4
|
+
contexto: RowActionContexto;
|
|
5
|
+
row: Record<string, unknown>;
|
|
6
|
+
eventBus?: ChatEventBus;
|
|
7
|
+
}
|
|
8
|
+
export declare function ContextoDeLaAccion({ contexto, row, eventBus }: ContextoDeLaAccionProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -140,10 +140,37 @@ export interface RowActionDef {
|
|
|
140
140
|
/** SPEC-002: varias llamadas en orden (ej. inspección = register-inspection foto + video). Si
|
|
141
141
|
* se define, tiene prioridad sobre `endpoint`; todos comparten el mismo contexto del form. */
|
|
142
142
|
endpoints?: EndpointRef[];
|
|
143
|
-
/** Sub-form declarativo para acciones con input (asignar→grúa, completar→precio). SPEC-001 T2.
|
|
143
|
+
/** Sub-form declarativo para acciones con input (asignar→grúa, completar→precio). SPEC-001 T2.
|
|
144
|
+
*
|
|
145
|
+
* SPEC-132 T5 — `contexto` es lo que se está decidiendo, en solo lectura y arriba del todo.
|
|
146
|
+
* Nació del drawer de «Confirmar pago»: pedía el medio y la referencia sin enseñar **ni el
|
|
147
|
+
* importe ni el comprobante** que el cliente acababa de subir. Para ver lo que estaba dando por
|
|
148
|
+
* bueno, la operadora tenía que cancelar, pulsar «Ver», mirar y volver a empezar. Confirmar un
|
|
149
|
+
* ingreso de dinero sin el dinero delante es firmar a ciegas. */
|
|
144
150
|
form?: {
|
|
145
151
|
fields: ReactiveCrudField[];
|
|
146
152
|
payloadMap?: Record<string, string>;
|
|
153
|
+
contexto?: RowActionContexto;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/** SPEC-132 T5 — lo que hay que tener delante para decidir. */
|
|
157
|
+
export interface RowActionContexto {
|
|
158
|
+
/** Campos de la FILA (no del formulario) que se enseñan como pares etiqueta/valor. */
|
|
159
|
+
campos?: Array<{
|
|
160
|
+
field: string;
|
|
161
|
+
label: string;
|
|
162
|
+
moleculeType?: string;
|
|
163
|
+
}>;
|
|
164
|
+
/** El archivo que se está dando por bueno: se resuelve su URL y se enseña. */
|
|
165
|
+
adjunto?: {
|
|
166
|
+
/** Campo de la fila con el id del documento. */
|
|
167
|
+
idField: string;
|
|
168
|
+
label?: string;
|
|
169
|
+
/** Endpoint que traduce ese id a una URL mirable. Igual que en la vista de detalle. */
|
|
170
|
+
media: {
|
|
171
|
+
service: string;
|
|
172
|
+
urlPath: string;
|
|
173
|
+
};
|
|
147
174
|
};
|
|
148
175
|
}
|
|
149
176
|
/** Vista de detalle ("Ver"): campos read-only + galería de archivos (fotos/videos/docs). */
|
|
@@ -15,6 +15,12 @@ export interface ServicesMapConfig {
|
|
|
15
15
|
data: {
|
|
16
16
|
list: EndpointRef;
|
|
17
17
|
};
|
|
18
|
+
/** SPEC-131 T6 — qué pide el mapa cuando nadie acota nada.
|
|
19
|
+
*
|
|
20
|
+
* Un mapa de seguimiento sin filtro devuelve el historial entero, y eso es lo que recibía quien
|
|
21
|
+
* preguntaba «¿ya viene mi grúa?». El `filter` de la instancia PISA esto: cuando quien emite el
|
|
22
|
+
* componente sí sabe qué acotar, manda él. */
|
|
23
|
+
defaultFilter?: Record<string, unknown>;
|
|
18
24
|
mapping: ServicesMapMapping;
|
|
19
25
|
/** Icono para todos los puntos cuando la fila no trae uno. */
|
|
20
26
|
defaultIcon?: string;
|
|
@@ -14,9 +14,16 @@ export interface UseAutoScrollOptions {
|
|
|
14
14
|
* desconectarlo), dos envíos seguidos dejaban dos observadores compitiendo por mover el scroll,
|
|
15
15
|
* y ninguno se limpiaba al desmontar.
|
|
16
16
|
*/
|
|
17
|
+
/** SPEC-131 T3 — cuánto espacio hay que reservar al final para que el objetivo pueda subir arriba.
|
|
18
|
+
*
|
|
19
|
+
* Lo que falte y nada más: si desde el objetivo hasta el final ya hay una pantalla de contenido,
|
|
20
|
+
* no hace falta reservar nada. Reservar `clientHeight` a ciegas mete una pantalla de blanco al
|
|
21
|
+
* final, y con el scroll topado eso deja la conversación entera por encima del borde superior. */
|
|
22
|
+
export declare function reservaNecesaria(clientHeight: number, scrollHeight: number, targetTop: number): number;
|
|
17
23
|
export declare const useAutoScroll: ({ messagesEndRef, messagesContainerRef, }: UseAutoScrollOptions) => {
|
|
18
24
|
scrollToLastUserMessage: () => void;
|
|
19
25
|
pinLastUserMessage: () => void;
|
|
20
26
|
scrollToProactive: () => void;
|
|
21
|
-
scrollToBottom: () => void;
|
|
27
|
+
scrollToBottom: (suave?: boolean) => void;
|
|
28
|
+
aterrizarAlFinal: () => void;
|
|
22
29
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface LineaResumen {
|
|
2
|
+
label: string;
|
|
3
|
+
valor: string;
|
|
4
|
+
}
|
|
5
|
+
interface CampoResumible {
|
|
6
|
+
name: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
moleculeType?: string;
|
|
9
|
+
uiProps?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
/** Lo que se va a enviar, en el orden del formulario y sin los campos vacíos. */
|
|
12
|
+
export declare function resumenParaConfirmar(campos: ReadonlyArray<CampoResumible>, datos: Record<string, unknown> | null | undefined): LineaResumen[];
|
|
13
|
+
export {};
|
|
@@ -51,6 +51,12 @@ export interface RenderFieldConfig {
|
|
|
51
51
|
formatValue?: (value: unknown, row: unknown) => React.ReactNode;
|
|
52
52
|
/** Función para parsear el valor antes de submit */
|
|
53
53
|
parseValue?: (value: unknown) => unknown;
|
|
54
|
+
/** SPEC-131 T7 — cuántas columnas ocupa el campo en el formulario.
|
|
55
|
+
*
|
|
56
|
+
* En un grid las celdas de una fila comparten altura, así que un campo que se despliega —el de
|
|
57
|
+
* ubicación, que abre un mapa de verificación al elegir la dirección— deja a su hermana con
|
|
58
|
+
* cientos de píxeles de blanco al lado. Los campos que crecen ocupan la fila entera. */
|
|
59
|
+
gridSpan?: number;
|
|
54
60
|
}
|
|
55
61
|
/**
|
|
56
62
|
* Estado de paginación
|
package/dist/index.esm.js
CHANGED
|
@@ -10924,6 +10924,7 @@ const Drawer = ({
|
|
|
10924
10924
|
"data-testid": dataTestId,
|
|
10925
10925
|
scrollIndicatorOffset = 1
|
|
10926
10926
|
}) => {
|
|
10927
|
+
const idTitulo = useId();
|
|
10927
10928
|
const [isMobile, setIsMobile] = useState(false);
|
|
10928
10929
|
const contentRef = useRef(null);
|
|
10929
10930
|
const [showScrollIndicator, setShowScrollIndicator] = useState(false);
|
|
@@ -11047,14 +11048,14 @@ const Drawer = ({
|
|
|
11047
11048
|
style: getDrawerStyles(),
|
|
11048
11049
|
role: "dialog",
|
|
11049
11050
|
"aria-modal": "true",
|
|
11050
|
-
"aria-labelledby": title ?
|
|
11051
|
+
"aria-labelledby": title ? idTitulo : void 0,
|
|
11051
11052
|
"data-testid": dataTestId,
|
|
11052
11053
|
children: /* @__PURE__ */ jsxs("div", { className: `${COMPONENT_PREFIX}drawer__viewport`, children: [
|
|
11053
11054
|
(title || showCloseButton || headerExtra) && /* @__PURE__ */ jsxs("div", { className: `${COMPONENT_PREFIX}drawer__header`, children: [
|
|
11054
11055
|
title && /* @__PURE__ */ jsx(
|
|
11055
11056
|
"h2",
|
|
11056
11057
|
{
|
|
11057
|
-
id:
|
|
11058
|
+
id: idTitulo,
|
|
11058
11059
|
className: `${COMPONENT_PREFIX}drawer__title`,
|
|
11059
11060
|
children: title
|
|
11060
11061
|
}
|
|
@@ -13167,7 +13168,9 @@ function mapFieldsToFormFields(fields, mode) {
|
|
|
13167
13168
|
readOnly: field.readOnly,
|
|
13168
13169
|
placeholder: field.placeholder,
|
|
13169
13170
|
uiProps: field.uiProps,
|
|
13170
|
-
transformOnSubmit: field.parseValue
|
|
13171
|
+
transformOnSubmit: field.parseValue,
|
|
13172
|
+
gridSpan: field.gridSpan
|
|
13173
|
+
// SPEC-131 T7
|
|
13171
13174
|
}));
|
|
13172
13175
|
}
|
|
13173
13176
|
|
|
@@ -13347,6 +13350,47 @@ function calculateTotalPages(totalRecords, limit) {
|
|
|
13347
13350
|
return Math.ceil(totalRecords / limit);
|
|
13348
13351
|
}
|
|
13349
13352
|
|
|
13353
|
+
function etiquetaDeOpcion(valor, uiProps) {
|
|
13354
|
+
const opciones = uiProps?.options;
|
|
13355
|
+
if (!Array.isArray(opciones)) return null;
|
|
13356
|
+
const encontrada = opciones.find((o) => o && String(o.value) === String(valor));
|
|
13357
|
+
return encontrada?.label != null ? String(encontrada.label) : null;
|
|
13358
|
+
}
|
|
13359
|
+
function esNumerico(moleculeType) {
|
|
13360
|
+
return moleculeType === "INPUT_CURRENCY" || moleculeType === "INPUT_NUMBER";
|
|
13361
|
+
}
|
|
13362
|
+
function aTexto$1(valor, uiProps, moleculeType) {
|
|
13363
|
+
if (valor == null || valor === "") return "";
|
|
13364
|
+
if (typeof valor === "boolean") return valor ? "S\xED" : "No";
|
|
13365
|
+
if (esNumerico(moleculeType)) {
|
|
13366
|
+
const n = Number(String(valor).replace(/[^\d.-]/g, ""));
|
|
13367
|
+
if (Number.isFinite(n)) return n.toLocaleString();
|
|
13368
|
+
}
|
|
13369
|
+
if (typeof File !== "undefined" && valor instanceof File) return valor.name;
|
|
13370
|
+
if (Array.isArray(valor)) {
|
|
13371
|
+
const partes = valor.map((v) => aTexto$1(v, uiProps, moleculeType)).filter(Boolean);
|
|
13372
|
+
return partes.join(", ");
|
|
13373
|
+
}
|
|
13374
|
+
if (typeof valor === "object") {
|
|
13375
|
+
const o = valor;
|
|
13376
|
+
for (const clave of ["label", "address", "name", "text", "value"]) {
|
|
13377
|
+
if (typeof o[clave] === "string" && o[clave]) return o[clave];
|
|
13378
|
+
}
|
|
13379
|
+
return "";
|
|
13380
|
+
}
|
|
13381
|
+
return etiquetaDeOpcion(valor, uiProps) ?? String(valor);
|
|
13382
|
+
}
|
|
13383
|
+
function resumenParaConfirmar(campos, datos) {
|
|
13384
|
+
if (!datos) return [];
|
|
13385
|
+
const lineas = [];
|
|
13386
|
+
for (const campo of campos) {
|
|
13387
|
+
const valor = aTexto$1(datos[campo.name], campo.uiProps, campo.moleculeType);
|
|
13388
|
+
if (!valor) continue;
|
|
13389
|
+
lineas.push({ label: campo.label ?? campo.name, valor });
|
|
13390
|
+
}
|
|
13391
|
+
return lineas;
|
|
13392
|
+
}
|
|
13393
|
+
|
|
13350
13394
|
const useAnchoPropio = () => {
|
|
13351
13395
|
const ref = useRef(null);
|
|
13352
13396
|
const [ancho, setAncho] = useState(null);
|
|
@@ -13791,6 +13835,10 @@ function Crud({
|
|
|
13791
13835
|
return "Formulario";
|
|
13792
13836
|
}
|
|
13793
13837
|
};
|
|
13838
|
+
const resumenConfirmacion = useMemo(
|
|
13839
|
+
() => confirmAction === "create" || confirmAction === "edit" ? resumenParaConfirmar(formFields, pendingFormData) : [],
|
|
13840
|
+
[confirmAction, formFields, pendingFormData]
|
|
13841
|
+
);
|
|
13794
13842
|
const getConfirmMessage = () => {
|
|
13795
13843
|
switch (confirmAction) {
|
|
13796
13844
|
case "create":
|
|
@@ -13930,6 +13978,10 @@ function Crud({
|
|
|
13930
13978
|
className: `${COMPONENT_PREFIX}crud__confirm-drawer`,
|
|
13931
13979
|
children: /* @__PURE__ */ jsxs("div", { className: `${COMPONENT_PREFIX}crud__confirm-content`, children: [
|
|
13932
13980
|
/* @__PURE__ */ jsx("p", { className: `${COMPONENT_PREFIX}crud__confirm-message`, children: getConfirmMessage() }),
|
|
13981
|
+
resumenConfirmacion.length > 0 && /* @__PURE__ */ jsx("dl", { className: `${COMPONENT_PREFIX}crud__confirm-summary`, children: resumenConfirmacion.map((linea) => /* @__PURE__ */ jsxs("div", { className: `${COMPONENT_PREFIX}crud__confirm-summary-row`, children: [
|
|
13982
|
+
/* @__PURE__ */ jsx("dt", { children: linea.label }),
|
|
13983
|
+
/* @__PURE__ */ jsx("dd", { children: linea.valor })
|
|
13984
|
+
] }, linea.label)) }),
|
|
13933
13985
|
/* @__PURE__ */ jsxs("div", { className: `${COMPONENT_PREFIX}crud__confirm-actions`, children: [
|
|
13934
13986
|
/* @__PURE__ */ jsx(
|
|
13935
13987
|
Button,
|
|
@@ -14104,7 +14156,7 @@ function getFormatter(key) {
|
|
|
14104
14156
|
}
|
|
14105
14157
|
|
|
14106
14158
|
const OPTIONS_LOAD_ERROR = "No se pudieron cargar las opciones. Int\xE9ntalo de nuevo.";
|
|
14107
|
-
function unwrap$
|
|
14159
|
+
function unwrap$5(raw) {
|
|
14108
14160
|
if (raw && typeof raw === "object" && "response" in raw) {
|
|
14109
14161
|
return raw.response;
|
|
14110
14162
|
}
|
|
@@ -14125,7 +14177,7 @@ function resolveBody(body, ctx) {
|
|
|
14125
14177
|
}
|
|
14126
14178
|
async function loadOptions(eventBus, ep, ctx) {
|
|
14127
14179
|
const raw = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, resolveBody(ep.body, ctx));
|
|
14128
|
-
let data = unwrap$
|
|
14180
|
+
let data = unwrap$5(raw);
|
|
14129
14181
|
if (ep.response_path && data && typeof data === "object" && !Array.isArray(data)) {
|
|
14130
14182
|
data = data[ep.response_path];
|
|
14131
14183
|
}
|
|
@@ -14163,7 +14215,7 @@ const mensajeDeError = (e, respaldo) => {
|
|
|
14163
14215
|
};
|
|
14164
14216
|
|
|
14165
14217
|
const REFRESH_INTERVAL_MS = 2e4;
|
|
14166
|
-
function unwrap$
|
|
14218
|
+
function unwrap$4(raw) {
|
|
14167
14219
|
if (raw && typeof raw === "object" && "response" in raw) {
|
|
14168
14220
|
return raw.response;
|
|
14169
14221
|
}
|
|
@@ -14212,7 +14264,7 @@ function useReactiveCrud(config, eventBus) {
|
|
|
14212
14264
|
filters
|
|
14213
14265
|
});
|
|
14214
14266
|
assertOk(raw);
|
|
14215
|
-
const data = unwrap$
|
|
14267
|
+
const data = unwrap$4(raw);
|
|
14216
14268
|
let rows = [];
|
|
14217
14269
|
let total = 0;
|
|
14218
14270
|
if (Array.isArray(data)) {
|
|
@@ -14416,6 +14468,72 @@ const FileField = ({
|
|
|
14416
14468
|
] });
|
|
14417
14469
|
};
|
|
14418
14470
|
|
|
14471
|
+
function unwrap$3(raw) {
|
|
14472
|
+
if (raw && typeof raw === "object" && "response" in raw) {
|
|
14473
|
+
return raw.response;
|
|
14474
|
+
}
|
|
14475
|
+
return raw;
|
|
14476
|
+
}
|
|
14477
|
+
function aTexto(valor, locale, moleculeType) {
|
|
14478
|
+
if (valor == null || valor === "") return "";
|
|
14479
|
+
if (moleculeType === "INPUT_CURRENCY" || moleculeType === "INPUT_NUMBER") {
|
|
14480
|
+
const n = Number(valor);
|
|
14481
|
+
if (!Number.isNaN(n)) return n.toLocaleString(locale || "es-CO");
|
|
14482
|
+
}
|
|
14483
|
+
if (typeof valor === "object") {
|
|
14484
|
+
const o = valor;
|
|
14485
|
+
for (const clave of ["label", "address", "name", "text", "value"]) {
|
|
14486
|
+
if (typeof o[clave] === "string" && o[clave]) return o[clave];
|
|
14487
|
+
}
|
|
14488
|
+
return "";
|
|
14489
|
+
}
|
|
14490
|
+
return String(valor);
|
|
14491
|
+
}
|
|
14492
|
+
function ContextoDeLaAccion({ contexto, row, eventBus }) {
|
|
14493
|
+
const { locale } = useChatConfig();
|
|
14494
|
+
const [urlAdjunto, setUrlAdjunto] = useState("");
|
|
14495
|
+
const [estadoAdjunto, setEstadoAdjunto] = useState("idle");
|
|
14496
|
+
const adjunto = contexto.adjunto;
|
|
14497
|
+
const idAdjunto = adjunto ? String(row[adjunto.idField] ?? "") : "";
|
|
14498
|
+
useEffect(() => {
|
|
14499
|
+
if (!adjunto || !idAdjunto || !eventBus) return;
|
|
14500
|
+
let vivo = true;
|
|
14501
|
+
setEstadoAdjunto("cargando");
|
|
14502
|
+
void (async () => {
|
|
14503
|
+
try {
|
|
14504
|
+
const crudo = unwrap$3(
|
|
14505
|
+
await fetchViaEventBus(eventBus, adjunto.media.service, adjunto.media.urlPath.replace("{id}", idAdjunto), "GET")
|
|
14506
|
+
);
|
|
14507
|
+
const url = typeof crudo === "string" ? crudo : String(crudo?.url ?? "");
|
|
14508
|
+
if (!vivo) return;
|
|
14509
|
+
setUrlAdjunto(url);
|
|
14510
|
+
setEstadoAdjunto(url ? "idle" : "error");
|
|
14511
|
+
} catch {
|
|
14512
|
+
if (vivo) setEstadoAdjunto("error");
|
|
14513
|
+
}
|
|
14514
|
+
})();
|
|
14515
|
+
return () => {
|
|
14516
|
+
vivo = false;
|
|
14517
|
+
};
|
|
14518
|
+
}, [adjunto, idAdjunto, eventBus]);
|
|
14519
|
+
const lineas = (contexto.campos ?? []).map((c) => ({ label: c.label, valor: aTexto(row[c.field], locale, c.moleculeType) })).filter((l) => l.valor);
|
|
14520
|
+
if (lineas.length === 0 && !adjunto) return null;
|
|
14521
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-chat-reactive-crud__contexto", children: [
|
|
14522
|
+
lineas.length > 0 && /* @__PURE__ */ jsx("dl", { className: "px-chat-reactive-crud__contexto-datos", children: lineas.map((l) => /* @__PURE__ */ jsxs("div", { className: "px-chat-reactive-crud__contexto-linea", children: [
|
|
14523
|
+
/* @__PURE__ */ jsx("dt", { children: l.label }),
|
|
14524
|
+
/* @__PURE__ */ jsx("dd", { children: l.valor })
|
|
14525
|
+
] }, l.label)) }),
|
|
14526
|
+
adjunto && /* @__PURE__ */ jsxs("div", { className: "px-chat-reactive-crud__contexto-adjunto", children: [
|
|
14527
|
+
adjunto.label && /* @__PURE__ */ jsx("span", { className: "px-chat-reactive-crud__contexto-adjunto-label", children: adjunto.label }),
|
|
14528
|
+
estadoAdjunto === "cargando" && /* @__PURE__ */ jsx("span", { className: "px-chat-reactive-crud__contexto-nota", children: "Cargando\u2026" }),
|
|
14529
|
+
estadoAdjunto === "error" && /* @__PURE__ */ jsx("span", { role: "alert", className: "px-chat-reactive-crud__contexto-nota", children: "No se pudo cargar el archivo. M\xEDralo en \xABVer\xBB antes de confirmar." }),
|
|
14530
|
+
urlAdjunto && estadoAdjunto === "idle" && // Se abre aparte porque el comprobante de una transferencia puede ser un PDF o una
|
|
14531
|
+
// captura larga, y aquí solo cabe un vistazo.
|
|
14532
|
+
/* @__PURE__ */ jsx("a", { href: urlAdjunto, target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsx("img", { src: urlAdjunto, alt: adjunto.label ?? "Archivo adjunto", className: "px-chat-reactive-crud__contexto-imagen" }) })
|
|
14533
|
+
] })
|
|
14534
|
+
] });
|
|
14535
|
+
}
|
|
14536
|
+
|
|
14419
14537
|
const RESPALDO = { miles: ".", decimal: "," };
|
|
14420
14538
|
function separadoresDelLocale(locale) {
|
|
14421
14539
|
if (!locale) return RESPALDO;
|
|
@@ -14571,6 +14689,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14571
14689
|
};
|
|
14572
14690
|
const uploading = Object.values(fileEntries).some((list) => list.some((e) => e.status === "uploading"));
|
|
14573
14691
|
return /* @__PURE__ */ jsx(Drawer$1, { isOpen: true, onClose, title: action.label, placement: "right", children: loadingOptions ? /* @__PURE__ */ jsx("div", { className: "px-chat-reactive-crud__action-loading", children: "Cargando\u2026" }) : /* @__PURE__ */ jsxs("div", { className: "px-chat-reactive-crud__action-form", children: [
|
|
14692
|
+
action.form?.contexto && /* @__PURE__ */ jsx(ContextoDeLaAccion, { contexto: action.form.contexto, row, eventBus }),
|
|
14574
14693
|
fileFields.map((f) => /* @__PURE__ */ jsx(
|
|
14575
14694
|
FileField,
|
|
14576
14695
|
{
|
|
@@ -15875,7 +15994,8 @@ function usePlaceSuggestions(query, enabled) {
|
|
|
15875
15994
|
return {
|
|
15876
15995
|
lat: typeof loc.lat === "function" ? loc.lat() : loc.lat,
|
|
15877
15996
|
lng: typeof loc.lng === "function" ? loc.lng() : loc.lng,
|
|
15878
|
-
address: String(place.formattedAddress ?? "")
|
|
15997
|
+
address: limpiarDireccion(String(place.formattedAddress ?? ""))
|
|
15998
|
+
// SPEC-131 T6
|
|
15879
15999
|
};
|
|
15880
16000
|
} catch {
|
|
15881
16001
|
return null;
|
|
@@ -15885,13 +16005,27 @@ function usePlaceSuggestions(query, enabled) {
|
|
|
15885
16005
|
);
|
|
15886
16006
|
return { suggestions, resolve, clear: () => setSuggestions([]) };
|
|
15887
16007
|
}
|
|
16008
|
+
function limpiarDireccion(address) {
|
|
16009
|
+
const normal = (s) => s.trim().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
16010
|
+
const vistos = /* @__PURE__ */ new Set();
|
|
16011
|
+
const tramos = [];
|
|
16012
|
+
for (const tramo of address.split(",")) {
|
|
16013
|
+
const limpio = tramo.trim();
|
|
16014
|
+
if (!limpio) continue;
|
|
16015
|
+
const clave = normal(limpio);
|
|
16016
|
+
if (vistos.has(clave)) continue;
|
|
16017
|
+
vistos.add(clave);
|
|
16018
|
+
tramos.push(limpio);
|
|
16019
|
+
}
|
|
16020
|
+
return tramos.join(", ");
|
|
16021
|
+
}
|
|
15888
16022
|
function reverseGeocode(lat, lng) {
|
|
15889
16023
|
return new Promise((resolve) => {
|
|
15890
16024
|
const maps = window.google?.maps;
|
|
15891
16025
|
if (!maps?.Geocoder) return resolve(null);
|
|
15892
16026
|
new maps.Geocoder().geocode({ location: { lat, lng } }, (results, status) => {
|
|
15893
16027
|
if (status !== "OK" || !results?.length) return resolve(null);
|
|
15894
|
-
resolve(String(results[0].formatted_address ?? "") || null);
|
|
16028
|
+
resolve(limpiarDireccion(String(results[0].formatted_address ?? "")) || null);
|
|
15895
16029
|
});
|
|
15896
16030
|
});
|
|
15897
16031
|
}
|
|
@@ -16048,6 +16182,10 @@ function toRenderField(f, loaded, eventBus, mapsApiKey, loadError) {
|
|
|
16048
16182
|
showInModes: f.showInModes,
|
|
16049
16183
|
required: f.required,
|
|
16050
16184
|
placeholder: f.placeholder,
|
|
16185
|
+
// SPEC-131 T7 — la fila entera: al elegir la dirección se despliega el mapa de verificación
|
|
16186
|
+
// y, a media fila, su campo hermano se quedaba con ~400px de blanco al lado. Medido entre
|
|
16187
|
+
// «¿En qué situación está?» y «¿A dónde lo llevamos?» del formulario de solicitud.
|
|
16188
|
+
gridSpan: 2,
|
|
16051
16189
|
uiProps: {
|
|
16052
16190
|
// SPEC-074 — `error` viene del render de CUSTOM (DynamicForm ya lo pasaba) y aquí se
|
|
16053
16191
|
// desechaba: el campo obligatorio más importante era el único mudo.
|
|
@@ -16672,6 +16810,8 @@ const ServicesMap = ({ config, eventBus, apiKey, filter, fill }) => {
|
|
|
16672
16810
|
const { service, url, method, payloadMap } = config.data.list;
|
|
16673
16811
|
void fetchViaEventBus(eventBus, service, url, method ?? "POST", {
|
|
16674
16812
|
...payloadMap,
|
|
16813
|
+
// SPEC-131 T6 — el del config primero, el de la instancia después: quien acota manda.
|
|
16814
|
+
...config.defaultFilter ?? {},
|
|
16675
16815
|
...filter ?? {}
|
|
16676
16816
|
}).then((res) => {
|
|
16677
16817
|
if (!alive) return;
|
|
@@ -18825,6 +18965,10 @@ const useInputFocus = ({
|
|
|
18825
18965
|
};
|
|
18826
18966
|
};
|
|
18827
18967
|
|
|
18968
|
+
function reservaNecesaria(clientHeight, scrollHeight, targetTop) {
|
|
18969
|
+
const debajoDelObjetivo = scrollHeight - targetTop;
|
|
18970
|
+
return Math.max(0, clientHeight - debajoDelObjetivo);
|
|
18971
|
+
}
|
|
18828
18972
|
const useAutoScroll = ({
|
|
18829
18973
|
messagesEndRef,
|
|
18830
18974
|
messagesContainerRef
|
|
@@ -18843,11 +18987,12 @@ const useAutoScroll = ({
|
|
|
18843
18987
|
const container = messagesContainerRef.current;
|
|
18844
18988
|
const spacer = messagesEndRef.current;
|
|
18845
18989
|
if (!container || !spacer || !target) return;
|
|
18846
|
-
spacer.style.minHeight =
|
|
18990
|
+
spacer.style.minHeight = "0px";
|
|
18847
18991
|
const containerRect = container.getBoundingClientRect();
|
|
18848
18992
|
const msgRect = target.getBoundingClientRect();
|
|
18849
18993
|
const padTop = parseFloat(getComputedStyle(container).paddingTop) || 0;
|
|
18850
18994
|
const targetTop = msgRect.top - containerRect.top + container.scrollTop - padTop;
|
|
18995
|
+
spacer.style.minHeight = `${reservaNecesaria(container.clientHeight, container.scrollHeight, targetTop)}px`;
|
|
18851
18996
|
container.scrollTo({ top: targetTop, behavior: "smooth" });
|
|
18852
18997
|
}, [messagesContainerRef, messagesEndRef]);
|
|
18853
18998
|
const last = useCallback((selector) => {
|
|
@@ -18889,15 +19034,33 @@ const useAutoScroll = ({
|
|
|
18889
19034
|
});
|
|
18890
19035
|
}, 150);
|
|
18891
19036
|
}, [doScroll]);
|
|
18892
|
-
const
|
|
19037
|
+
const aterrizarAlFinal = useCallback(() => {
|
|
19038
|
+
const container = messagesContainerRef.current;
|
|
19039
|
+
if (!container) return;
|
|
19040
|
+
resetSpacer();
|
|
19041
|
+
const hasta = Date.now() + 1e3;
|
|
19042
|
+
let ultimoAlto = -1;
|
|
19043
|
+
const pegar = () => {
|
|
19044
|
+
const c = messagesContainerRef.current;
|
|
19045
|
+
if (!c) return;
|
|
19046
|
+
if (c.scrollHeight !== ultimoAlto) {
|
|
19047
|
+
ultimoAlto = c.scrollHeight;
|
|
19048
|
+
c.scrollTop = c.scrollHeight;
|
|
19049
|
+
}
|
|
19050
|
+
if (Date.now() < hasta) requestAnimationFrame(pegar);
|
|
19051
|
+
};
|
|
19052
|
+
requestAnimationFrame(pegar);
|
|
19053
|
+
}, [messagesContainerRef, resetSpacer]);
|
|
19054
|
+
const scrollToBottom = useCallback((suave = true) => {
|
|
18893
19055
|
cancelPending();
|
|
18894
19056
|
resetSpacer();
|
|
18895
19057
|
requestAnimationFrame(() => {
|
|
19058
|
+
const comportamiento = suave ? "smooth" : "auto";
|
|
18896
19059
|
const container = messagesContainerRef.current;
|
|
18897
19060
|
if (container) {
|
|
18898
|
-
container.scrollTo({ top: container.scrollHeight, behavior:
|
|
19061
|
+
container.scrollTo({ top: container.scrollHeight, behavior: comportamiento });
|
|
18899
19062
|
} else {
|
|
18900
|
-
messagesEndRef.current?.scrollIntoView({ behavior:
|
|
19063
|
+
messagesEndRef.current?.scrollIntoView({ behavior: comportamiento, block: "end" });
|
|
18901
19064
|
}
|
|
18902
19065
|
});
|
|
18903
19066
|
}, [cancelPending, resetSpacer, messagesContainerRef, messagesEndRef]);
|
|
@@ -18905,7 +19068,9 @@ const useAutoScroll = ({
|
|
|
18905
19068
|
scrollToLastUserMessage,
|
|
18906
19069
|
pinLastUserMessage,
|
|
18907
19070
|
scrollToProactive,
|
|
18908
|
-
scrollToBottom
|
|
19071
|
+
scrollToBottom,
|
|
19072
|
+
aterrizarAlFinal
|
|
19073
|
+
// SPEC-131 T3
|
|
18909
19074
|
};
|
|
18910
19075
|
};
|
|
18911
19076
|
|
|
@@ -19458,7 +19623,7 @@ const Chat = ({
|
|
|
19458
19623
|
usePreventOverscroll();
|
|
19459
19624
|
const { showThinking, scrollButtonState, scrollToTopPending } = useChatStore();
|
|
19460
19625
|
const { attachments, handleAttachmentsChange, clearAttachments } = useAttachments();
|
|
19461
|
-
const { scrollToBottom, scrollToLastUserMessage, pinLastUserMessage, scrollToProactive } = useAutoScroll({
|
|
19626
|
+
const { scrollToBottom, scrollToLastUserMessage, pinLastUserMessage, scrollToProactive, aterrizarAlFinal } = useAutoScroll({
|
|
19462
19627
|
messagesEndRef,
|
|
19463
19628
|
messagesContainerRef
|
|
19464
19629
|
});
|
|
@@ -19486,11 +19651,23 @@ const Chat = ({
|
|
|
19486
19651
|
}
|
|
19487
19652
|
}, [scrollToTopPending, scrollToLastUserMessage]);
|
|
19488
19653
|
useEffect(() => {
|
|
19654
|
+
if (messages[messages.length - 1]?.proactive) return;
|
|
19489
19655
|
const spacer = messagesEndRef.current;
|
|
19490
19656
|
if (spacer && spacer.style.minHeight && spacer.style.minHeight !== "0px") {
|
|
19491
19657
|
pinLastUserMessage();
|
|
19492
19658
|
}
|
|
19493
19659
|
}, [messages]);
|
|
19660
|
+
const yaAterrizo = useRef(false);
|
|
19661
|
+
useEffect(() => {
|
|
19662
|
+
if (messages.length === 0) {
|
|
19663
|
+
yaAterrizo.current = false;
|
|
19664
|
+
return;
|
|
19665
|
+
}
|
|
19666
|
+
if (yaAterrizo.current) return;
|
|
19667
|
+
yaAterrizo.current = true;
|
|
19668
|
+
if (messages[messages.length - 1]?.proactive) return;
|
|
19669
|
+
aterrizarAlFinal();
|
|
19670
|
+
}, [messages, aterrizarAlFinal]);
|
|
19494
19671
|
const lastProactiveIdRef = useRef(void 0);
|
|
19495
19672
|
useEffect(() => {
|
|
19496
19673
|
const last = messages[messages.length - 1];
|
|
@@ -19608,7 +19785,7 @@ const Chat = ({
|
|
|
19608
19785
|
{
|
|
19609
19786
|
className: `${COMPONENT_PREFIX$1}chat__scroll-to-bottom`,
|
|
19610
19787
|
variant: "circle",
|
|
19611
|
-
onClick: scrollToBottom,
|
|
19788
|
+
onClick: () => scrollToBottom(),
|
|
19612
19789
|
"aria-label": "Ir al final",
|
|
19613
19790
|
icon: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
19614
19791
|
/* @__PURE__ */ jsx("path", { d: "M12 8v8", strokeWidth: "2", strokeLinecap: "round" }),
|