pixelon 0.4.3 → 0.5.0
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/ReactiveCrud/ReactiveCrud.d.ts +1 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/TranscriptSection.d.ts +10 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/types.d.ts +40 -1
- package/dist/components/molecules/chat/StructuredContent/components/ServicesMap/types.d.ts +3 -0
- package/dist/components/molecules/chat/StructuredContent/components/index.d.ts +0 -1
- package/dist/core/chat/ChatConfigContext.d.ts +25 -0
- package/dist/core/i18n/separadores.d.ts +12 -0
- package/dist/index.esm.js +171 -32
- package/dist/index.js +171 -32
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
- package/src/styles/index.scss +1 -0
package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/ReactiveCrud.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface ReactiveCrudProps {
|
|
|
14
14
|
onCreateClose?: () => void;
|
|
15
15
|
}
|
|
16
16
|
export declare function matchesVisibleWhen(a: RowActionDef, row: Record<string, unknown>): boolean;
|
|
17
|
+
export declare function laQueTocaAhora(actions: RowActionDef[], row: Record<string, unknown>): RowActionDef | undefined;
|
|
17
18
|
export declare function matchesDisabledWhen(a: RowActionDef, row: Record<string, unknown>): boolean;
|
|
18
19
|
export declare function disabledReasonFor(a: RowActionDef, row: Record<string, unknown>): string | undefined;
|
|
19
20
|
export declare function matchesDoneWhen(a: RowActionDef, row: Record<string, unknown>): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ChatEventBus } from '../../../../../../core/eventBus/chatEventBus';
|
|
2
|
+
import type { ReactiveCrudTranscript } from './types';
|
|
3
|
+
export interface TranscriptSectionProps {
|
|
4
|
+
transcript: ReactiveCrudTranscript;
|
|
5
|
+
/** Valores del registro (fila + detalle): de ahí sale el id de la conversación. */
|
|
6
|
+
values: Record<string, unknown>;
|
|
7
|
+
eventBus?: ChatEventBus;
|
|
8
|
+
}
|
|
9
|
+
export declare function TranscriptSection({ transcript, values, eventBus }: TranscriptSectionProps): import("react/jsx-runtime").JSX.Element | null;
|
|
10
|
+
export default TranscriptSection;
|
|
@@ -114,8 +114,28 @@ export interface RowActionDef {
|
|
|
114
114
|
* SPEC-113: es el respaldo — una condición con `reason` propio manda sobre este. */
|
|
115
115
|
disabledReason?: string;
|
|
116
116
|
/** SPEC-007: muestra un ✓ al final cuando `String(row[field])` está en `in` (ej. inspección ya
|
|
117
|
-
* registrada). Indicador de "hecho"; no bloquea reejecutar la acción.
|
|
117
|
+
* registrada). Indicador de "hecho"; no bloquea reejecutar la acción.
|
|
118
|
+
*
|
|
119
|
+
* SPEC-129 T6: y es lo que decide qué queda escrito en una tarjeta ya gastada. Ahí solo se
|
|
120
|
+
* muestra lo que CUMPLE esta condición, porque es lo único que consta que se hizo: si el paso se
|
|
121
|
+
* cerró por otro camino —el barrido liberó la grúa, la operadora canceló— no hay nada que marcar
|
|
122
|
+
* y la tarjeta se queda con su frase, sin inventar un ✓. */
|
|
118
123
|
doneWhen?: RowActionVisibleWhen;
|
|
124
|
+
/** SPEC-129 T6: cómo se llama esto una vez hecho. «Registrar inspección» → «Inspección
|
|
125
|
+
* registrada». En una tarjeta gastada el botón ya no es una oferta sino un acta, y un acta se
|
|
126
|
+
* escribe en pasado. Sin esto se cae a `label`, que sigue leyéndose bien con el ✓ al lado. */
|
|
127
|
+
doneLabel?: string;
|
|
128
|
+
/** SPEC-127 T4: cuándo esta acción es LA QUE TOCA AHORA. La que cumple va primera y destacada;
|
|
129
|
+
* el resto pasa a un segundo plano, disponible pero sin competir.
|
|
130
|
+
*
|
|
131
|
+
* No es lo mismo que `intent: 'primary'`, que describe la acción SIEMPRE («asignar es una acción
|
|
132
|
+
* importante»). Esto describe el MOMENTO: el mensaje que acompaña la tarjeta dice «Confírmalo
|
|
133
|
+
* para que la grúa salga», y entonces confirmar el pago es lo único que se le está pidiendo,
|
|
134
|
+
* aunque a su alrededor quepan siete cosas más.
|
|
135
|
+
*
|
|
136
|
+
* Si ninguna acción la cumple —o dos la cumplen— la tarjeta se comporta como antes: ninguna se
|
|
137
|
+
* destaca. Es preferible no señalar nada que señalar mal. */
|
|
138
|
+
ahoraToca?: RowActionVisibleWhen | RowActionVisibleWhen[];
|
|
119
139
|
endpoint: EndpointRef;
|
|
120
140
|
/** SPEC-002: varias llamadas en orden (ej. inspección = register-inspection foto + video). Si
|
|
121
141
|
* se define, tiene prioridad sobre `endpoint`; todos comparten el mismo contexto del form. */
|
|
@@ -142,6 +162,23 @@ export interface ReactiveCrudDetail {
|
|
|
142
162
|
/** Campo de la fila para el título del drawer (ej. 'service_number'). */
|
|
143
163
|
titleField?: string;
|
|
144
164
|
}
|
|
165
|
+
export interface ReactiveCrudTranscript {
|
|
166
|
+
/** Campo de la fila con el id de la conversación (ej. 'origin_chat_id'). */
|
|
167
|
+
chatIdField: string;
|
|
168
|
+
/** Endpoint que devuelve `{ lines, total, from_user }`; recibe `{ [param]: <chatIdField> }`. */
|
|
169
|
+
get: EndpointRef;
|
|
170
|
+
/** Clave del cuerpo donde viaja el id (default 'chat_id'). */
|
|
171
|
+
param?: string;
|
|
172
|
+
/** Título de la sección. Ej: 'Conversación con el cliente'. */
|
|
173
|
+
title?: string;
|
|
174
|
+
/** Qué decir cuando la otra parte no ha escrito nada. Es una respuesta, no un hueco. */
|
|
175
|
+
silentText?: string;
|
|
176
|
+
/** Con qué nombre se pinta cada lado. */
|
|
177
|
+
labels?: {
|
|
178
|
+
user?: string;
|
|
179
|
+
assistant?: string;
|
|
180
|
+
};
|
|
181
|
+
}
|
|
145
182
|
export interface ReactiveCrudConfig {
|
|
146
183
|
title?: string;
|
|
147
184
|
/** Campo id de la fila (getRowKey de LumiCrud es obligatorio). */
|
|
@@ -154,6 +191,8 @@ export interface ReactiveCrudConfig {
|
|
|
154
191
|
};
|
|
155
192
|
/** Vista "Ver" con campos + galería de archivos. */
|
|
156
193
|
detail?: ReactiveCrudDetail;
|
|
194
|
+
/** SPEC-128 T5 — conversación en solo lectura dentro de «Ver». Ver `ReactiveCrudTranscript`. */
|
|
195
|
+
transcript?: ReactiveCrudTranscript;
|
|
157
196
|
rowActions?: RowActionDef[];
|
|
158
197
|
filters?: FilterFieldConfig[];
|
|
159
198
|
/** SPEC-014 — con qué filtros NACE la tabla. `filters` (arriba) dice qué se PUEDE filtrar; esto,
|
|
@@ -48,6 +48,9 @@ export interface ServicesMapConfig {
|
|
|
48
48
|
emptyText?: string;
|
|
49
49
|
/** Aviso de las filas que no se pudieron ubicar. `{n}` se reemplaza por el número. */
|
|
50
50
|
missingText?: string;
|
|
51
|
+
/** SPEC-127 — el mismo aviso cuando falta UNA sola. Sin esto se lee «1 servicios sin
|
|
52
|
+
* ubicación registrada». Opcional: sin él se usa `missingText` para cualquier número. */
|
|
53
|
+
missingTextOne?: string;
|
|
51
54
|
outsideText?: string;
|
|
52
55
|
loadingText?: string;
|
|
53
56
|
};
|
|
@@ -22,7 +22,6 @@ export { ServicesMap } from './ServicesMap/ServicesMap';
|
|
|
22
22
|
export { ServicesMapChat } from './ServicesMap/ServicesMapChat';
|
|
23
23
|
export type { ServicesMapConfig } from './ServicesMap/types';
|
|
24
24
|
export { ServiceShortcuts } from './ServiceShortcuts/ServiceShortcuts';
|
|
25
|
-
export { ServiceShortcutsChat } from './ServiceShortcuts/ServiceShortcutsChat';
|
|
26
25
|
export { ChatNavigator } from './ChatNavigator/ChatNavigator';
|
|
27
26
|
export { ChatNavigatorChat } from './ChatNavigator/ChatNavigatorChat';
|
|
28
27
|
export type { ChatNavigatorConfig, ChatNavigatorItem, ChatNavigatorEndpoint } from './ChatNavigator/types';
|
|
@@ -19,6 +19,17 @@ export interface ChatConfig {
|
|
|
19
19
|
* sola es preferible a un panel que se apaga por una conexión mala; el backend sigue rechazando
|
|
20
20
|
* lo que no toca. */
|
|
21
21
|
chatIsForeign?: boolean;
|
|
22
|
+
/** SPEC-128 — DE QUIÉN es esta conversación (el agente que la atiende).
|
|
23
|
+
*
|
|
24
|
+
* `chatIsForeign` dice «esto es de otro»; esto dice **de quién**. No es lo mismo, y hasta ahora
|
|
25
|
+
* solo existía lo primero: la operadora abría la conversación del solicitante y sus componentes
|
|
26
|
+
* se pintaban con el rol de ELLA, así que veía «Gestionar servicios» donde al cliente se le
|
|
27
|
+
* ofreció «Solicitar una grúa». Un mensaje del pasado no es una pregunta sobre quién mira: es el
|
|
28
|
+
* registro de lo que se dijo, y para pintarlo bien hay que saber a quién se le dijo.
|
|
29
|
+
*
|
|
30
|
+
* El host lo pone porque es quien sabe qué conversación está abriendo. Opcional: sin él, los
|
|
31
|
+
* componentes se comportan como antes (deciden por quien mira). */
|
|
32
|
+
chatAgentName?: string;
|
|
22
33
|
/** IANA timezone identifier (e.g. "America/Bogota") */
|
|
23
34
|
timezone: string;
|
|
24
35
|
/** BCP 47 locale string (e.g. "es-CO") */
|
|
@@ -59,6 +70,20 @@ export interface ChatConfig {
|
|
|
59
70
|
* Se tipa como bag genérico para no acoplar `core` a los tipos del componente.
|
|
60
71
|
*/
|
|
61
72
|
resolveModuleConfig?: (module: string) => Record<string, unknown> | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* SPEC-128: qué acciones toca mostrar en un MOMENTO del flujo. Lo provee el host, que es quien
|
|
75
|
+
* sabe qué significa «comprobante_subido» en su producto — aquí solo se sabe que hay momentos.
|
|
76
|
+
*
|
|
77
|
+
* Existe porque la tarjeta tenía que ADIVINAR en qué punto estaba, mirando campos de la fila y
|
|
78
|
+
* decidiendo con cuatro capas de condiciones cuáles de las quince acciones mostrar. Salían ocho
|
|
79
|
+
* botones para una decisión, y pasos futuros apagados ocupando tres cuartas partes. Y cuando dos
|
|
80
|
+
* momentos comparten los mismos datos, adivinar es imposible.
|
|
81
|
+
*
|
|
82
|
+
* Devolver `undefined` significa «no sé de ese momento»: entonces se decide por el estado, como
|
|
83
|
+
* siempre. Una lista VACÍA es distinto — es «en este momento no hay nada que hacer», y la tarjeta
|
|
84
|
+
* enseña solo «Ver».
|
|
85
|
+
*/
|
|
86
|
+
resolveMomento?: (momento: string) => string[] | undefined;
|
|
62
87
|
/**
|
|
63
88
|
* SPEC-022: clave de Google Maps JavaScript API para el componente `map_view`. La provee el
|
|
64
89
|
* HOST desde su entorno; no viaja en el payload del LLM (es configuración de despliegue, y el
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface Separadores {
|
|
2
|
+
/** El que agrupa los miles: `.` en es-CO, `,` en en-US. */
|
|
3
|
+
miles: string;
|
|
4
|
+
/** El que abre los decimales: `,` en es-CO, `.` en en-US. */
|
|
5
|
+
decimal: string;
|
|
6
|
+
}
|
|
7
|
+
/** Cómo se escriben los números en ese locale, según el propio navegador.
|
|
8
|
+
*
|
|
9
|
+
* Se deriva en vez de tabularse: una tabla de locales se queda corta en cuanto alguien vende en
|
|
10
|
+
* otro país, y `Intl` ya lo sabe. Si el locale no se entiende, se responde el formato de la casa
|
|
11
|
+
* en vez de reventar — un número mal separado es feo; un formulario que no pinta, inservible. */
|
|
12
|
+
export declare function separadoresDelLocale(locale?: string): Separadores;
|
package/dist/index.esm.js
CHANGED
|
@@ -12817,29 +12817,22 @@ const FormDrawer = ({
|
|
|
12817
12817
|
onClose();
|
|
12818
12818
|
methods.reset();
|
|
12819
12819
|
}, [onClose, onBeforeClose, methods]);
|
|
12820
|
-
const [invalidMsg, setInvalidMsg] = useState(null);
|
|
12821
|
-
useEffect(() => {
|
|
12822
|
-
if (!isOpen) setInvalidMsg(null);
|
|
12823
|
-
}, [isOpen]);
|
|
12824
|
-
useEffect(() => {
|
|
12825
|
-
const sub = methods.watch(() => setInvalidMsg(null));
|
|
12826
|
-
return () => sub.unsubscribe();
|
|
12827
|
-
}, [methods.watch]);
|
|
12828
12820
|
const handleFormSubmit = useCallback(
|
|
12829
12821
|
async (data) => {
|
|
12830
|
-
setInvalidMsg(null);
|
|
12831
12822
|
await onSubmit(data);
|
|
12832
12823
|
},
|
|
12833
12824
|
[onSubmit]
|
|
12834
12825
|
);
|
|
12835
12826
|
const handleInvalid = useCallback(
|
|
12836
12827
|
(formErrors) => {
|
|
12837
|
-
const
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
);
|
|
12828
|
+
const primero = Object.keys(formErrors)[0];
|
|
12829
|
+
if (!primero) return;
|
|
12830
|
+
const nodo = document.querySelector(`[name="${CSS.escape(primero)}"]`);
|
|
12831
|
+
if (!nodo) return;
|
|
12832
|
+
nodo.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
12833
|
+
window.setTimeout(() => nodo.focus({ preventScroll: true }), 300);
|
|
12841
12834
|
},
|
|
12842
|
-
[
|
|
12835
|
+
[]
|
|
12843
12836
|
);
|
|
12844
12837
|
const handleSubmitClick = methods.handleSubmit(handleFormSubmit, handleInvalid);
|
|
12845
12838
|
const drawerTitle = title || DEFAULT_TITLES[mode];
|
|
@@ -12897,7 +12890,6 @@ const FormDrawer = ({
|
|
|
12897
12890
|
) }) }),
|
|
12898
12891
|
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__footer`, children: [
|
|
12899
12892
|
footerExtra && /* @__PURE__ */ jsx("div", { className: `${baseClass}__footer-extra`, children: footerExtra }),
|
|
12900
|
-
invalidMsg && mode !== "view" && /* @__PURE__ */ jsx("p", { className: `${baseClass}__invalid`, role: "alert", children: invalidMsg }),
|
|
12901
12893
|
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__actions`, children: [
|
|
12902
12894
|
/* @__PURE__ */ jsx(
|
|
12903
12895
|
Button,
|
|
@@ -14112,7 +14104,7 @@ function getFormatter(key) {
|
|
|
14112
14104
|
}
|
|
14113
14105
|
|
|
14114
14106
|
const OPTIONS_LOAD_ERROR = "No se pudieron cargar las opciones. Int\xE9ntalo de nuevo.";
|
|
14115
|
-
function unwrap$
|
|
14107
|
+
function unwrap$4(raw) {
|
|
14116
14108
|
if (raw && typeof raw === "object" && "response" in raw) {
|
|
14117
14109
|
return raw.response;
|
|
14118
14110
|
}
|
|
@@ -14133,7 +14125,7 @@ function resolveBody(body, ctx) {
|
|
|
14133
14125
|
}
|
|
14134
14126
|
async function loadOptions(eventBus, ep, ctx) {
|
|
14135
14127
|
const raw = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, resolveBody(ep.body, ctx));
|
|
14136
|
-
let data = unwrap$
|
|
14128
|
+
let data = unwrap$4(raw);
|
|
14137
14129
|
if (ep.response_path && data && typeof data === "object" && !Array.isArray(data)) {
|
|
14138
14130
|
data = data[ep.response_path];
|
|
14139
14131
|
}
|
|
@@ -14171,7 +14163,7 @@ const mensajeDeError = (e, respaldo) => {
|
|
|
14171
14163
|
};
|
|
14172
14164
|
|
|
14173
14165
|
const REFRESH_INTERVAL_MS = 2e4;
|
|
14174
|
-
function unwrap$
|
|
14166
|
+
function unwrap$3(raw) {
|
|
14175
14167
|
if (raw && typeof raw === "object" && "response" in raw) {
|
|
14176
14168
|
return raw.response;
|
|
14177
14169
|
}
|
|
@@ -14220,7 +14212,7 @@ function useReactiveCrud(config, eventBus) {
|
|
|
14220
14212
|
filters
|
|
14221
14213
|
});
|
|
14222
14214
|
assertOk(raw);
|
|
14223
|
-
const data = unwrap$
|
|
14215
|
+
const data = unwrap$3(raw);
|
|
14224
14216
|
let rows = [];
|
|
14225
14217
|
let total = 0;
|
|
14226
14218
|
if (Array.isArray(data)) {
|
|
@@ -14424,6 +14416,20 @@ const FileField = ({
|
|
|
14424
14416
|
] });
|
|
14425
14417
|
};
|
|
14426
14418
|
|
|
14419
|
+
const RESPALDO = { miles: ".", decimal: "," };
|
|
14420
|
+
function separadoresDelLocale(locale) {
|
|
14421
|
+
if (!locale) return RESPALDO;
|
|
14422
|
+
try {
|
|
14423
|
+
const partes = new Intl.NumberFormat(locale).formatToParts(123456789e-2);
|
|
14424
|
+
const miles = partes.find((p) => p.type === "group")?.value;
|
|
14425
|
+
const decimal = partes.find((p) => p.type === "decimal")?.value;
|
|
14426
|
+
if (!miles || !decimal) return RESPALDO;
|
|
14427
|
+
return { miles, decimal };
|
|
14428
|
+
} catch {
|
|
14429
|
+
return RESPALDO;
|
|
14430
|
+
}
|
|
14431
|
+
}
|
|
14432
|
+
|
|
14427
14433
|
const CRUD_TO_FORM_MOLECULE = {
|
|
14428
14434
|
INPUT: "INPUT",
|
|
14429
14435
|
INPUT_NUMBER: "INPUT_NUMBER",
|
|
@@ -14480,6 +14486,8 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14480
14486
|
alive = false;
|
|
14481
14487
|
};
|
|
14482
14488
|
}, [eventBus, action.key, normalFields]);
|
|
14489
|
+
const { locale } = useChatConfig();
|
|
14490
|
+
const separadores = useMemo(() => separadoresDelLocale(locale), [locale]);
|
|
14483
14491
|
const fields = useMemo(
|
|
14484
14492
|
() => normalFields.map((f) => ({
|
|
14485
14493
|
name: f.name,
|
|
@@ -14490,9 +14498,19 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14490
14498
|
options: f.options ?? optionsByField[f.name],
|
|
14491
14499
|
// SPEC-106 T8 — `uiProps` llega tal cual a la molécula, así que el desplegable dice lo
|
|
14492
14500
|
// que pasó de verdad en vez del «no hay» de siempre. Sin error, se queda el de siempre.
|
|
14493
|
-
...optionsErrorByField[f.name] ? { uiProps: { notFoundContent: optionsErrorByField[f.name] } } : {}
|
|
14501
|
+
...optionsErrorByField[f.name] ? { uiProps: { notFoundContent: optionsErrorByField[f.name] } } : {},
|
|
14502
|
+
// SPEC-128 — el dinero se escribe como se escribe AQUÍ.
|
|
14503
|
+
//
|
|
14504
|
+
// Los campos de moneda caían a `thousandSeparator ?? ','`, un valor por defecto en formato
|
|
14505
|
+
// inglés: al teclear 180000 el campo mostraba «180,000» mientras la tarjeta que recibe el
|
|
14506
|
+
// cliente decía «$ 180.000,00 COP». El mismo importe escrito de una manera y leído de otra.
|
|
14507
|
+
//
|
|
14508
|
+
// Se deriva del locale que el host ya declara en la config del chat, no de un valor fijo ni
|
|
14509
|
+
// del navegador: `navigator.language` puede ser `es-419` —español, pero con coma para los
|
|
14510
|
+
// miles— y entonces el resultado dependería del portátil de cada quien.
|
|
14511
|
+
...f.moleculeType === "INPUT_CURRENCY" ? { uiProps: { thousandSeparator: separadores.miles, decimalSeparator: separadores.decimal } } : {}
|
|
14494
14512
|
})),
|
|
14495
|
-
[normalFields, optionsByField, optionsErrorByField]
|
|
14513
|
+
[normalFields, optionsByField, optionsErrorByField, separadores]
|
|
14496
14514
|
);
|
|
14497
14515
|
const validationSchema = useMemo(() => buildCrudYupSchema(normalFields), [normalFields]);
|
|
14498
14516
|
const uploadOne = async (f, file, idx) => {
|
|
@@ -15151,6 +15169,95 @@ const FileViewer = ({
|
|
|
15151
15169
|
] });
|
|
15152
15170
|
};
|
|
15153
15171
|
|
|
15172
|
+
function unwrap$2(raw) {
|
|
15173
|
+
if (raw && typeof raw === "object" && "response" in raw) {
|
|
15174
|
+
return raw.response;
|
|
15175
|
+
}
|
|
15176
|
+
return raw;
|
|
15177
|
+
}
|
|
15178
|
+
function hora(iso) {
|
|
15179
|
+
if (!iso) return "";
|
|
15180
|
+
const d = new Date(iso);
|
|
15181
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
15182
|
+
return d.toLocaleString(void 0, { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" });
|
|
15183
|
+
}
|
|
15184
|
+
function TranscriptSection({ transcript, values, eventBus }) {
|
|
15185
|
+
const chatId = values[transcript.chatIdField];
|
|
15186
|
+
const id = chatId == null || chatId === "" ? "" : String(chatId);
|
|
15187
|
+
const [datos, setDatos] = useState(null);
|
|
15188
|
+
const [cargando, setCargando] = useState(true);
|
|
15189
|
+
const [fallo, setFallo] = useState(false);
|
|
15190
|
+
useEffect(() => {
|
|
15191
|
+
if (!eventBus || !id) {
|
|
15192
|
+
setCargando(false);
|
|
15193
|
+
return;
|
|
15194
|
+
}
|
|
15195
|
+
let vivo = true;
|
|
15196
|
+
setCargando(true);
|
|
15197
|
+
setFallo(false);
|
|
15198
|
+
void (async () => {
|
|
15199
|
+
try {
|
|
15200
|
+
const raw = await fetchViaEventBus(
|
|
15201
|
+
eventBus,
|
|
15202
|
+
transcript.get.service,
|
|
15203
|
+
transcript.get.url,
|
|
15204
|
+
transcript.get.method ?? "POST",
|
|
15205
|
+
{ [transcript.param ?? "chat_id"]: id }
|
|
15206
|
+
);
|
|
15207
|
+
if (vivo) {
|
|
15208
|
+
setDatos(unwrap$2(raw) ?? {});
|
|
15209
|
+
setCargando(false);
|
|
15210
|
+
}
|
|
15211
|
+
} catch {
|
|
15212
|
+
if (vivo) {
|
|
15213
|
+
setFallo(true);
|
|
15214
|
+
setCargando(false);
|
|
15215
|
+
}
|
|
15216
|
+
}
|
|
15217
|
+
})();
|
|
15218
|
+
return () => {
|
|
15219
|
+
vivo = false;
|
|
15220
|
+
};
|
|
15221
|
+
}, [eventBus, id, transcript]);
|
|
15222
|
+
if (!id) return null;
|
|
15223
|
+
const lineas = datos?.lines ?? [];
|
|
15224
|
+
const dijoAlgo = (datos?.from_user ?? 0) > 0;
|
|
15225
|
+
const nombres = {
|
|
15226
|
+
user: transcript.labels?.user ?? "Cliente",
|
|
15227
|
+
assistant: transcript.labels?.assistant ?? "Asistente"
|
|
15228
|
+
};
|
|
15229
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-chat-crud-transcript", children: [
|
|
15230
|
+
/* @__PURE__ */ jsx("h4", { className: "px-chat-crud-transcript__title", children: transcript.title ?? "Conversaci\xF3n" }),
|
|
15231
|
+
cargando && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-transcript__note", children: "Cargando\u2026" }),
|
|
15232
|
+
!cargando && fallo && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-transcript__note", children: "No se pudo cargar la conversaci\xF3n." }),
|
|
15233
|
+
!cargando && !fallo && !dijoAlgo && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-transcript__note", children: transcript.silentText ?? "Todav\xEDa no ha escrito nada." }),
|
|
15234
|
+
!cargando && !fallo && lineas.length > 0 && /* @__PURE__ */ jsx("ol", { className: "px-chat-crud-transcript__lines", children: lineas.map((l, i) => /* @__PURE__ */ jsxs(
|
|
15235
|
+
"li",
|
|
15236
|
+
{
|
|
15237
|
+
className: [
|
|
15238
|
+
"px-chat-crud-transcript__line",
|
|
15239
|
+
l.rol === "user" ? "is-user" : "is-assistant"
|
|
15240
|
+
].join(" "),
|
|
15241
|
+
children: [
|
|
15242
|
+
/* @__PURE__ */ jsxs("span", { className: "px-chat-crud-transcript__who", children: [
|
|
15243
|
+
l.rol === "user" ? nombres.user : nombres.assistant,
|
|
15244
|
+
hora(l.created_date) && /* @__PURE__ */ jsx("span", { className: "px-chat-crud-transcript__when", children: hora(l.created_date) })
|
|
15245
|
+
] }),
|
|
15246
|
+
/* @__PURE__ */ jsx("span", { className: "px-chat-crud-transcript__text", children: l.text })
|
|
15247
|
+
]
|
|
15248
|
+
},
|
|
15249
|
+
`${i}-${l.created_date ?? ""}`
|
|
15250
|
+
)) }),
|
|
15251
|
+
!cargando && !fallo && (datos?.total ?? 0) > lineas.length && /* @__PURE__ */ jsxs("div", { className: "px-chat-crud-transcript__note", children: [
|
|
15252
|
+
"Se muestran los \xFAltimos ",
|
|
15253
|
+
lineas.length,
|
|
15254
|
+
" de ",
|
|
15255
|
+
datos?.total,
|
|
15256
|
+
" mensajes."
|
|
15257
|
+
] })
|
|
15258
|
+
] });
|
|
15259
|
+
}
|
|
15260
|
+
|
|
15154
15261
|
function unwrap$1(raw) {
|
|
15155
15262
|
if (raw && typeof raw === "object" && "response" in raw) {
|
|
15156
15263
|
return raw.response;
|
|
@@ -15279,6 +15386,7 @@ function DetailDrawer({ row, config, eventBus, onClose, loadedOptions }) {
|
|
|
15279
15386
|
] })
|
|
15280
15387
|
] }, f.name);
|
|
15281
15388
|
}) }),
|
|
15389
|
+
config.transcript && /* @__PURE__ */ jsx(TranscriptSection, { transcript: config.transcript, values, eventBus }),
|
|
15282
15390
|
/* @__PURE__ */ jsxs("div", { className: "px-chat-reactive-crud-detail__photos", children: [
|
|
15283
15391
|
/* @__PURE__ */ jsx("h4", { className: "px-chat-reactive-crud-detail__photos-title", children: "Fotos y documentos" }),
|
|
15284
15392
|
loading ? /* @__PURE__ */ jsx("div", { className: "px-chat-reactive-crud-detail__empty", children: "Cargando\u2026" }) : files.length ? /* @__PURE__ */ jsx(FileViewer, { files }) : /* @__PURE__ */ jsx("div", { className: "px-chat-reactive-crud-detail__empty", children: "Este servicio no tiene archivos." })
|
|
@@ -15985,6 +16093,10 @@ function matchesVisibleWhen(a, row) {
|
|
|
15985
16093
|
if (!a.visibleWhen) return true;
|
|
15986
16094
|
return cumplenTodas(a.visibleWhen, row);
|
|
15987
16095
|
}
|
|
16096
|
+
function laQueTocaAhora(actions, row) {
|
|
16097
|
+
const candidatas = actions.filter((a) => a.ahoraToca && cumplenTodas(a.ahoraToca, row));
|
|
16098
|
+
return candidatas.length === 1 ? candidatas[0] : void 0;
|
|
16099
|
+
}
|
|
15988
16100
|
function condicionesDeshabilitado(a) {
|
|
15989
16101
|
if (!a.disabledWhen) return [];
|
|
15990
16102
|
return Array.isArray(a.disabledWhen) ? a.disabledWhen : [a.disabledWhen];
|
|
@@ -16305,7 +16417,7 @@ function DeadlineCountdown({ deadline, label, expiredLabel }) {
|
|
|
16305
16417
|
}
|
|
16306
16418
|
|
|
16307
16419
|
function ReactiveCrudItemChat({ component, eventBus }) {
|
|
16308
|
-
const { resolveModuleConfig } = useChatConfig();
|
|
16420
|
+
const { resolveModuleConfig, resolveMomento } = useChatConfig();
|
|
16309
16421
|
const props = component.props ?? {};
|
|
16310
16422
|
const top = component;
|
|
16311
16423
|
const pick = (key) => props[key] !== void 0 ? props[key] : top[key];
|
|
@@ -16313,7 +16425,11 @@ function ReactiveCrudItemChat({ component, eventBus }) {
|
|
|
16313
16425
|
const recordId = pick("record_id") ?? pick("recordId") ?? pick("id") ?? pick("service_id");
|
|
16314
16426
|
const recordLabel = pick("record_label") ?? pick("label");
|
|
16315
16427
|
const allowedRaw = pick("actions");
|
|
16316
|
-
const
|
|
16428
|
+
const allowedLegacy = Array.isArray(allowedRaw) && allowedRaw.length > 0 ? allowedRaw : void 0;
|
|
16429
|
+
const momento = pick("momento");
|
|
16430
|
+
const delMomento = momento && resolveMomento ? resolveMomento(momento) : void 0;
|
|
16431
|
+
const allowed = delMomento ?? allowedLegacy;
|
|
16432
|
+
const sinAcciones = delMomento?.length === 0;
|
|
16317
16433
|
const config = typeof moduleKey === "string" && resolveModuleConfig ? resolveModuleConfig(moduleKey) : void 0;
|
|
16318
16434
|
if (!config || recordId == null || recordId === "") return null;
|
|
16319
16435
|
return /* @__PURE__ */ jsx(
|
|
@@ -16323,11 +16439,12 @@ function ReactiveCrudItemChat({ component, eventBus }) {
|
|
|
16323
16439
|
recordId: String(recordId),
|
|
16324
16440
|
recordLabel,
|
|
16325
16441
|
allowed: Array.isArray(allowed) ? allowed.map(String) : void 0,
|
|
16442
|
+
sinAcciones,
|
|
16326
16443
|
eventBus
|
|
16327
16444
|
}
|
|
16328
16445
|
);
|
|
16329
16446
|
}
|
|
16330
|
-
function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus }) {
|
|
16447
|
+
function ServiceActionsPanel({ config, recordId, recordLabel, allowed, sinAcciones, eventBus }) {
|
|
16331
16448
|
const { chatIsForeign } = useChatConfig();
|
|
16332
16449
|
const crud = useReactiveCrud(config, eventBus);
|
|
16333
16450
|
const [activeAction, setActiveAction] = useState(null);
|
|
@@ -16376,9 +16493,26 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16376
16493
|
}, [recordId]);
|
|
16377
16494
|
const gastada = !esUltima;
|
|
16378
16495
|
const actions = useMemo(() => {
|
|
16379
|
-
if (!row
|
|
16380
|
-
|
|
16496
|
+
if (!row) return [];
|
|
16497
|
+
if (gastada) {
|
|
16498
|
+
const hechas = (config.rowActions ?? []).filter((a) => !allowed || allowed.includes(a.key)).filter((a) => matchesDoneWhen(a, row));
|
|
16499
|
+
const vistas = /* @__PURE__ */ new Set();
|
|
16500
|
+
return hechas.filter((a) => {
|
|
16501
|
+
const linea = a.doneLabel ?? a.label;
|
|
16502
|
+
if (vistas.has(linea)) return false;
|
|
16503
|
+
vistas.add(linea);
|
|
16504
|
+
return true;
|
|
16505
|
+
});
|
|
16506
|
+
}
|
|
16507
|
+
const visibles = (config.rowActions ?? []).filter((a) => !allowed || allowed.includes(a.key)).filter((a) => matchesVisibleWhen(a, row));
|
|
16508
|
+
const toca = laQueTocaAhora(visibles, row);
|
|
16509
|
+
if (!toca) return visibles;
|
|
16510
|
+
return [toca, ...visibles.filter((a) => a.key !== toca.key)];
|
|
16381
16511
|
}, [config.rowActions, row, allowed, gastada]);
|
|
16512
|
+
const claveQueToca = useMemo(
|
|
16513
|
+
() => row && !gastada ? laQueTocaAhora(actions, row)?.key : void 0,
|
|
16514
|
+
[actions, row, gastada]
|
|
16515
|
+
);
|
|
16382
16516
|
const title = recordLabel ?? config.title ?? "Servicio";
|
|
16383
16517
|
return /* @__PURE__ */ jsxs("div", { className: "px-chat-crud-item", ref: raiz, "data-panel-record": recordId, children: [
|
|
16384
16518
|
/* @__PURE__ */ jsx("div", { className: "px-chat-crud-item__header", children: title }),
|
|
@@ -16389,7 +16523,7 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16389
16523
|
crud.loading && !row && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-item__hint", children: "Cargando\u2026" }),
|
|
16390
16524
|
!crud.loading && !row && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-item__hint", children: "No se encontr\xF3 el servicio." }),
|
|
16391
16525
|
gastada && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-item__hint", children: "Este paso ya se gestion\xF3. Contin\xFAa en el mensaje m\xE1s reciente." }),
|
|
16392
|
-
row && config.deadline && typeof row[config.deadline.field] === "string" && /* @__PURE__ */ jsx(
|
|
16526
|
+
row && !gastada && config.deadline && typeof row[config.deadline.field] === "string" && /* @__PURE__ */ jsx(
|
|
16393
16527
|
DeadlineCountdown,
|
|
16394
16528
|
{
|
|
16395
16529
|
deadline: row[config.deadline.field],
|
|
@@ -16400,7 +16534,7 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16400
16534
|
row && /* @__PURE__ */ jsxs("div", { className: "px-chat-crud-item__actions", children: [
|
|
16401
16535
|
config.detail && /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-crud-item__action", onClick: () => setDetailRow(row), children: "Ver" }),
|
|
16402
16536
|
actions.map((a) => {
|
|
16403
|
-
const disabled = Boolean(chatIsForeign) || matchesDisabledWhen(a, row);
|
|
16537
|
+
const disabled = gastada || Boolean(chatIsForeign) || matchesDisabledWhen(a, row);
|
|
16404
16538
|
const motivo = chatIsForeign ? "Esta conversaci\xF3n es de otra persona" : disabledReasonFor(a, row);
|
|
16405
16539
|
const done = matchesDoneWhen(a, row);
|
|
16406
16540
|
return /* @__PURE__ */ jsxs(
|
|
@@ -16410,13 +16544,18 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16410
16544
|
className: [
|
|
16411
16545
|
"px-chat-crud-item__action",
|
|
16412
16546
|
a.intent === "danger" ? "is-danger" : "",
|
|
16413
|
-
disabled && motivo ? "is-con-motivo" : ""
|
|
16547
|
+
disabled && motivo ? "is-con-motivo" : "",
|
|
16548
|
+
// SPEC-129 T6 — lo hecho se pinta como un acta, no como un botón apagado.
|
|
16549
|
+
gastada ? "is-hecho" : "",
|
|
16550
|
+
// SPEC-127 T4 — señalada, pero solo si de verdad se puede pulsar: destacar una
|
|
16551
|
+
// acción apagada sería dirigir a alguien a una puerta cerrada.
|
|
16552
|
+
a.key === claveQueToca && !disabled ? "is-ahora" : ""
|
|
16414
16553
|
].filter(Boolean).join(" "),
|
|
16415
16554
|
disabled,
|
|
16416
16555
|
onClick: () => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] }),
|
|
16417
16556
|
children: [
|
|
16418
16557
|
/* @__PURE__ */ jsxs("span", { className: "px-chat-crud-item__action-texts", children: [
|
|
16419
|
-
/* @__PURE__ */ jsx("span", { children: a.label }),
|
|
16558
|
+
/* @__PURE__ */ jsx("span", { children: gastada && a.doneLabel ? a.doneLabel : a.label }),
|
|
16420
16559
|
disabled && motivo && /* @__PURE__ */ jsx("span", { className: "px-chat-crud-item__action-reason", children: motivo })
|
|
16421
16560
|
] }),
|
|
16422
16561
|
done && /* @__PURE__ */ jsx(Icon$1, { type: "check-circle", width: 18, height: 18, className: "px-chat-crud-item__done", "aria-label": "Hecho" })
|
|
@@ -16425,7 +16564,7 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16425
16564
|
a.key
|
|
16426
16565
|
);
|
|
16427
16566
|
}),
|
|
16428
|
-
actions.length === 0 && !config.detail && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-item__hint", children: "Sin acciones disponibles." })
|
|
16567
|
+
actions.length === 0 && !config.detail && !sinAcciones && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-item__hint", children: "Sin acciones disponibles." })
|
|
16429
16568
|
] }),
|
|
16430
16569
|
activeAction && /* @__PURE__ */ jsx(
|
|
16431
16570
|
ActionFormDrawer,
|
|
@@ -16645,7 +16784,7 @@ const ServicesMap = ({ config, eventBus, apiKey, filter, fill }) => {
|
|
|
16645
16784
|
),
|
|
16646
16785
|
simulated > 0 && ` \xB7 ${config.live?.labels?.simulatedText ?? "incluye posiciones simuladas"}`
|
|
16647
16786
|
] }),
|
|
16648
|
-
missing > 0 && /* @__PURE__ */ jsx("p", { className: `${CLASS$6}__note`, children: (labels.missingText ?? "{n} sin ubicaci\xF3n registrada").replace("{n}", String(missing)) }),
|
|
16787
|
+
missing > 0 && /* @__PURE__ */ jsx("p", { className: `${CLASS$6}__note`, children: ((missing === 1 ? labels.missingTextOne : void 0) ?? labels.missingText ?? "{n} sin ubicaci\xF3n registrada").replace("{n}", String(missing)) }),
|
|
16649
16788
|
outside > 0 && /* @__PURE__ */ jsx("p", { className: `${CLASS$6}__note`, children: (labels.outsideText ?? "{n} fuera de la zona (aleja el mapa para verlos)").replace(
|
|
16650
16789
|
"{n}",
|
|
16651
16790
|
String(outside)
|