pixelon 0.3.80 → 0.3.82
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/atoms/lumi/Icon/Icon.d.ts +1 -0
- package/dist/components/atoms/lumi/Select/Select.d.ts +6 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/optionsLoader.d.ts +1 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/types.d.ts +7 -1
- package/dist/components/organisms/lumi/DynamicForm/types.d.ts +4 -0
- package/dist/index.esm.js +83 -19
- package/dist/index.js +83 -19
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
|
@@ -42,6 +42,7 @@ export declare const LUMI_ICON_TYPES: {
|
|
|
42
42
|
readonly MAP_PIN: "map-pin";
|
|
43
43
|
readonly USER_X: "user-x";
|
|
44
44
|
readonly REFRESH: "refresh";
|
|
45
|
+
readonly TRUCK: "truck";
|
|
45
46
|
readonly MESSAGE_SQUARE: "message-square";
|
|
46
47
|
readonly MESSAGE_CIRCLE: "message-circle";
|
|
47
48
|
readonly MAP: "map";
|
|
@@ -5,6 +5,12 @@ export interface SelectOption {
|
|
|
5
5
|
label: string;
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
icon?: ReactNode;
|
|
8
|
+
/** SPEC-104: segunda línea, en gris y más pequeña. Para el PORQUÉ de la opción —quién la lleva,
|
|
9
|
+
* a qué distancia, si hay algún aviso— sin que compita con su nombre. */
|
|
10
|
+
sublabel?: string;
|
|
11
|
+
/** SPEC-104: icono por nombre del set de lumi. Existe además de `icon` porque las opciones que
|
|
12
|
+
* llegan de un endpoint son JSON y no pueden traer un nodo de React. */
|
|
13
|
+
iconName?: string;
|
|
8
14
|
}
|
|
9
15
|
/** Tipo de valor según el modo */
|
|
10
16
|
export type SelectValue = string | number | (string | number)[];
|
package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/optionsLoader.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SelectOption } from '../../../../../organisms/lumi/DynamicForm/types';
|
|
2
2
|
import type { ChatEventBus } from '../../../../../../core/eventBus/chatEventBus';
|
|
3
3
|
import type { CrudFieldOptionsEndpoint } from './types';
|
|
4
|
+
export declare const OPTIONS_LOAD_ERROR = "No se pudieron cargar las opciones. Int\u00E9ntalo de nuevo.";
|
|
4
5
|
export declare function unwrap(raw: unknown): unknown;
|
|
5
6
|
export declare function resolveBody(body: Record<string, unknown> | undefined, ctx?: Record<string, unknown>): Record<string, unknown>;
|
|
6
7
|
export declare function loadOptions(eventBus: ChatEventBus, ep: CrudFieldOptionsEndpoint, ctx?: Record<string, unknown>): Promise<SelectOption[]>;
|
|
@@ -89,7 +89,13 @@ export interface RowActionDef {
|
|
|
89
89
|
label: string;
|
|
90
90
|
icon?: string;
|
|
91
91
|
intent?: 'primary' | 'secondary' | 'danger';
|
|
92
|
-
|
|
92
|
+
/** SPEC-103: una condición, o VARIAS unidas por «y». */
|
|
93
|
+
visibleWhen?: RowActionVisibleWhen | RowActionVisibleWhen[];
|
|
94
|
+
/** SPEC-103: la excepción. Si se cumplen TODAS, la acción no se muestra aunque `visibleWhen` dé
|
|
95
|
+
* por buena la fila. Es para las reglas con forma de «se ve en estos estados, salvo este caso»
|
|
96
|
+
* —p.ej. asignar grúa en PENDING_APPROVAL, salvo si quien pide es un invitado que todavía no ha
|
|
97
|
+
* aceptado el precio—, que con condiciones unidas por «y» no se pueden escribir. */
|
|
98
|
+
hiddenWhen?: RowActionVisibleWhen | RowActionVisibleWhen[];
|
|
93
99
|
/** SPEC-007: la acción se muestra pero queda DESHABILITADA cuando `String(row[field])` está en
|
|
94
100
|
* `in` (ej. "Iniciar" deshabilitado hasta que `has_inspection` sea true). */
|
|
95
101
|
disabledWhen?: RowActionVisibleWhen;
|
|
@@ -10,6 +10,10 @@ export interface SelectOption {
|
|
|
10
10
|
value: string | number;
|
|
11
11
|
label: string;
|
|
12
12
|
disabled?: boolean;
|
|
13
|
+
/** SPEC-104: segunda línea en gris — el porqué de la opción, sin competir con su nombre. */
|
|
14
|
+
sublabel?: string;
|
|
15
|
+
/** SPEC-104: nombre de icono del set de lumi (no un nodo: esto viaja como JSON desde el backend). */
|
|
16
|
+
iconName?: string;
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Configuración de un campo del formulario
|
package/dist/index.esm.js
CHANGED
|
@@ -5739,6 +5739,9 @@ const LUMI_ICON_TYPES = {
|
|
|
5739
5739
|
// Iconos para Agenda
|
|
5740
5740
|
USER_X: "user-x",
|
|
5741
5741
|
REFRESH: "refresh",
|
|
5742
|
+
// SPEC-104 — grúas: el selector de despacho necesitaba distinguir de un vistazo qué es cada
|
|
5743
|
+
// opción, y no había ningún icono de vehículo en el set.
|
|
5744
|
+
TRUCK: "truck",
|
|
5742
5745
|
// Iconos para Chat
|
|
5743
5746
|
MESSAGE_SQUARE: "message-square",
|
|
5744
5747
|
MESSAGE_CIRCLE: "message-circle",
|
|
@@ -6449,6 +6452,27 @@ const BuildingIcon = (props) => /* @__PURE__ */ jsxs(
|
|
|
6449
6452
|
]
|
|
6450
6453
|
}
|
|
6451
6454
|
);
|
|
6455
|
+
const TruckIcon = (props) => /* @__PURE__ */ jsxs(
|
|
6456
|
+
"svg",
|
|
6457
|
+
{
|
|
6458
|
+
width: "16",
|
|
6459
|
+
height: "16",
|
|
6460
|
+
viewBox: "0 0 24 24",
|
|
6461
|
+
fill: "none",
|
|
6462
|
+
stroke: "currentColor",
|
|
6463
|
+
strokeWidth: "2",
|
|
6464
|
+
strokeLinecap: "round",
|
|
6465
|
+
strokeLinejoin: "round",
|
|
6466
|
+
...props,
|
|
6467
|
+
children: [
|
|
6468
|
+
/* @__PURE__ */ jsx("path", { d: "M2 17V8h10v9" }),
|
|
6469
|
+
/* @__PURE__ */ jsx("path", { d: "M12 11h4l3 3v3" }),
|
|
6470
|
+
/* @__PURE__ */ jsx("circle", { cx: "7", cy: "18", r: "2" }),
|
|
6471
|
+
/* @__PURE__ */ jsx("circle", { cx: "17", cy: "18", r: "2" }),
|
|
6472
|
+
/* @__PURE__ */ jsx("path", { d: "M9 18h6" })
|
|
6473
|
+
]
|
|
6474
|
+
}
|
|
6475
|
+
);
|
|
6452
6476
|
const MapPinIcon = (props) => /* @__PURE__ */ jsxs(
|
|
6453
6477
|
"svg",
|
|
6454
6478
|
{
|
|
@@ -6567,6 +6591,7 @@ const lumiIconMap = {
|
|
|
6567
6591
|
// Iconos para Servicios
|
|
6568
6592
|
[LUMI_ICON_TYPES.BUILDING]: BuildingIcon,
|
|
6569
6593
|
[LUMI_ICON_TYPES.MAP_PIN]: MapPinIcon,
|
|
6594
|
+
[LUMI_ICON_TYPES.TRUCK]: TruckIcon,
|
|
6570
6595
|
// Iconos para Agenda
|
|
6571
6596
|
[LUMI_ICON_TYPES.USER_X]: UserXIcon,
|
|
6572
6597
|
[LUMI_ICON_TYPES.REFRESH]: RefreshIcon,
|
|
@@ -7738,8 +7763,11 @@ const Select$1 = forwardRef(
|
|
|
7738
7763
|
`${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__checkbox`,
|
|
7739
7764
|
isSelected && `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__checkbox--checked`
|
|
7740
7765
|
].filter(Boolean).join(" "), children: isSelected && /* @__PURE__ */ jsx(Icon, { type: LUMI_ICON_TYPES.CHECK, size: "small" }) }) }),
|
|
7741
|
-
option.icon && /* @__PURE__ */ jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-icon`, children: option.icon }),
|
|
7742
|
-
/* @__PURE__ */
|
|
7766
|
+
(option.icon || option.iconName) && /* @__PURE__ */ jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-icon`, children: option.icon ?? /* @__PURE__ */ jsx(Icon, { type: option.iconName }) }),
|
|
7767
|
+
/* @__PURE__ */ jsxs("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-texts`, children: [
|
|
7768
|
+
/* @__PURE__ */ jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-label`, children: option.label }),
|
|
7769
|
+
option.sublabel && /* @__PURE__ */ jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-sublabel`, children: option.sublabel })
|
|
7770
|
+
] }),
|
|
7743
7771
|
mode === "single" && isSelected && /* @__PURE__ */ jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-check`, children: /* @__PURE__ */ jsx(Icon, { type: LUMI_ICON_TYPES.CHECK, size: "small" }) })
|
|
7744
7772
|
]
|
|
7745
7773
|
},
|
|
@@ -14083,6 +14111,7 @@ function getFormatter(key) {
|
|
|
14083
14111
|
return key ? CRUD_FORMATTERS[key] : void 0;
|
|
14084
14112
|
}
|
|
14085
14113
|
|
|
14114
|
+
const OPTIONS_LOAD_ERROR = "No se pudieron cargar las opciones. Int\xE9ntalo de nuevo.";
|
|
14086
14115
|
function unwrap$3(raw) {
|
|
14087
14116
|
if (raw && typeof raw === "object" && "response" in raw) {
|
|
14088
14117
|
return raw.response;
|
|
@@ -14111,7 +14140,9 @@ async function loadOptions(eventBus, ep, ctx) {
|
|
|
14111
14140
|
const arr = Array.isArray(data) ? data : [];
|
|
14112
14141
|
return arr.map((item) => ({
|
|
14113
14142
|
value: String(item[ep.value_field] ?? ""),
|
|
14114
|
-
label: String(item[ep.label_field] ?? "")
|
|
14143
|
+
label: String(item[ep.label_field] ?? ""),
|
|
14144
|
+
...item.sublabel ? { sublabel: String(item.sublabel) } : {},
|
|
14145
|
+
...item.icon ? { iconName: String(item.icon) } : {}
|
|
14115
14146
|
}));
|
|
14116
14147
|
}
|
|
14117
14148
|
|
|
@@ -14418,6 +14449,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14418
14449
|
const fileFields = useMemo(() => formFields.filter((f) => f.moleculeType === "FILE"), [formFields]);
|
|
14419
14450
|
const normalFields = useMemo(() => formFields.filter((f) => f.moleculeType !== "FILE"), [formFields]);
|
|
14420
14451
|
const [optionsByField, setOptionsByField] = useState({});
|
|
14452
|
+
const [optionsErrorByField, setOptionsErrorByField] = useState({});
|
|
14421
14453
|
const [loadingOptions, setLoadingOptions] = useState(false);
|
|
14422
14454
|
const [submitting, setSubmitting] = useState(false);
|
|
14423
14455
|
const [fileEntries, setFileEntries] = useState({});
|
|
@@ -14431,14 +14463,17 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14431
14463
|
toLoad.map(async (f) => {
|
|
14432
14464
|
try {
|
|
14433
14465
|
const opts = await loadOptions(eventBus, f.optionsEndpoint, { ...row, id: row[idField] });
|
|
14434
|
-
return [f.name, opts];
|
|
14466
|
+
return [f.name, opts, null];
|
|
14435
14467
|
} catch {
|
|
14436
|
-
return [f.name, []];
|
|
14468
|
+
return [f.name, [], OPTIONS_LOAD_ERROR];
|
|
14437
14469
|
}
|
|
14438
14470
|
})
|
|
14439
14471
|
).then((entries) => {
|
|
14440
14472
|
if (!alive) return;
|
|
14441
|
-
setOptionsByField(Object.fromEntries(entries));
|
|
14473
|
+
setOptionsByField(Object.fromEntries(entries.map(([n, o]) => [n, o])));
|
|
14474
|
+
setOptionsErrorByField(
|
|
14475
|
+
Object.fromEntries(entries.filter(([, , e]) => e).map(([n, , e]) => [n, e]))
|
|
14476
|
+
);
|
|
14442
14477
|
setLoadingOptions(false);
|
|
14443
14478
|
});
|
|
14444
14479
|
return () => {
|
|
@@ -14452,9 +14487,12 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14452
14487
|
moleculeType: CRUD_TO_FORM_MOLECULE[f.moleculeType] ?? "INPUT",
|
|
14453
14488
|
required: f.required,
|
|
14454
14489
|
placeholder: f.placeholder,
|
|
14455
|
-
options: f.options ?? optionsByField[f.name]
|
|
14490
|
+
options: f.options ?? optionsByField[f.name],
|
|
14491
|
+
// SPEC-106 T8 — `uiProps` llega tal cual a la molécula, así que el desplegable dice lo
|
|
14492
|
+
// 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] } } : {}
|
|
14456
14494
|
})),
|
|
14457
|
-
[normalFields, optionsByField]
|
|
14495
|
+
[normalFields, optionsByField, optionsErrorByField]
|
|
14458
14496
|
);
|
|
14459
14497
|
const validationSchema = useMemo(() => buildCrudYupSchema(normalFields), [normalFields]);
|
|
14460
14498
|
const uploadOne = async (f, file, idx) => {
|
|
@@ -15889,7 +15927,7 @@ function chatLinkFormatter(field, eventBus) {
|
|
|
15889
15927
|
return createElement(ChatLinkCell, { targets, eventBus });
|
|
15890
15928
|
};
|
|
15891
15929
|
}
|
|
15892
|
-
function toRenderField(f, loaded, eventBus, mapsApiKey) {
|
|
15930
|
+
function toRenderField(f, loaded, eventBus, mapsApiKey, loadError) {
|
|
15893
15931
|
const fmt = getFormatter(f.format);
|
|
15894
15932
|
const options = f.options ?? loaded[f.name];
|
|
15895
15933
|
if (f.moleculeType === "LOCATION") {
|
|
@@ -15934,13 +15972,18 @@ function toRenderField(f, loaded, eventBus, mapsApiKey) {
|
|
|
15934
15972
|
columnWidth: f.columnWidth,
|
|
15935
15973
|
fixed: f.fixed,
|
|
15936
15974
|
align: f.align,
|
|
15937
|
-
|
|
15975
|
+
// SPEC-106 T8 — con un fallo de carga, el desplegable dice que no se pudo preguntar en vez
|
|
15976
|
+
// de «no hay opciones disponibles», que es una respuesta y no la tenemos.
|
|
15977
|
+
uiProps: loadError ? { options: options ?? [], notFoundContent: loadError } : options ? { options } : void 0,
|
|
15938
15978
|
formatValue
|
|
15939
15979
|
};
|
|
15940
15980
|
}
|
|
15981
|
+
const cumpleCondicion = (c, row) => c.in.includes(String(row[c.field]));
|
|
15982
|
+
const cumplenTodas = (c, row) => (Array.isArray(c) ? c : [c]).every((x) => cumpleCondicion(x, row));
|
|
15941
15983
|
function matchesVisibleWhen(a, row) {
|
|
15984
|
+
if (a.hiddenWhen && cumplenTodas(a.hiddenWhen, row)) return false;
|
|
15942
15985
|
if (!a.visibleWhen) return true;
|
|
15943
|
-
return a.visibleWhen
|
|
15986
|
+
return cumplenTodas(a.visibleWhen, row);
|
|
15944
15987
|
}
|
|
15945
15988
|
function matchesDisabledWhen(a, row) {
|
|
15946
15989
|
if (!a.disabledWhen) return false;
|
|
@@ -15981,6 +16024,7 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
15981
16024
|
const [activeAction, setActiveAction] = useState(null);
|
|
15982
16025
|
const [detailRow, setDetailRow] = useState(null);
|
|
15983
16026
|
const [optionsByField, setOptionsByField] = useState({});
|
|
16027
|
+
const [optionsErrorByField, setOptionsErrorByField] = useState({});
|
|
15984
16028
|
useEffect(() => {
|
|
15985
16029
|
let alive = true;
|
|
15986
16030
|
const toLoad = config.fields.filter((f) => f.optionsEndpoint);
|
|
@@ -15988,21 +16032,25 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
15988
16032
|
void Promise.all(
|
|
15989
16033
|
toLoad.map(async (f) => {
|
|
15990
16034
|
try {
|
|
15991
|
-
return [f.name, await loadOptions(eventBus, f.optionsEndpoint)];
|
|
16035
|
+
return [f.name, await loadOptions(eventBus, f.optionsEndpoint), null];
|
|
15992
16036
|
} catch {
|
|
15993
|
-
return [f.name, []];
|
|
16037
|
+
return [f.name, [], OPTIONS_LOAD_ERROR];
|
|
15994
16038
|
}
|
|
15995
16039
|
})
|
|
15996
16040
|
).then((entries) => {
|
|
15997
|
-
if (alive)
|
|
16041
|
+
if (!alive) return;
|
|
16042
|
+
setOptionsByField(Object.fromEntries(entries.map(([n, o]) => [n, o])));
|
|
16043
|
+
setOptionsErrorByField(
|
|
16044
|
+
Object.fromEntries(entries.filter(([, , e]) => e).map(([n, , e]) => [n, e]))
|
|
16045
|
+
);
|
|
15998
16046
|
});
|
|
15999
16047
|
return () => {
|
|
16000
16048
|
alive = false;
|
|
16001
16049
|
};
|
|
16002
16050
|
}, [eventBus, config.fields]);
|
|
16003
16051
|
const renderByFields = useMemo(
|
|
16004
|
-
() => config.fields.map((f) => toRenderField(f, optionsByField, eventBus, googleMapsApiKey)),
|
|
16005
|
-
[config.fields, optionsByField, eventBus]
|
|
16052
|
+
() => config.fields.map((f) => toRenderField(f, optionsByField, eventBus, googleMapsApiKey, optionsErrorByField[f.name])),
|
|
16053
|
+
[config.fields, optionsByField, optionsErrorByField, eventBus]
|
|
16006
16054
|
);
|
|
16007
16055
|
const validationSchema = useMemo(() => buildCrudYupSchema(config.fields), [config.fields]);
|
|
16008
16056
|
const rowActions = useMemo(() => {
|
|
@@ -16297,12 +16345,27 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16297
16345
|
() => crud.rows.find((r) => String(r[config.idField]) === recordId),
|
|
16298
16346
|
[crud.rows, config.idField, recordId]
|
|
16299
16347
|
);
|
|
16348
|
+
const raiz = useRef(null);
|
|
16349
|
+
const [esUltima, setEsUltima] = useState(true);
|
|
16350
|
+
useEffect(() => {
|
|
16351
|
+
const calcular = () => {
|
|
16352
|
+
const el = raiz.current;
|
|
16353
|
+
if (!el) return;
|
|
16354
|
+
const hermanas = document.querySelectorAll(`[data-panel-record="${recordId}"]`);
|
|
16355
|
+
setEsUltima(hermanas.length === 0 || hermanas[hermanas.length - 1] === el);
|
|
16356
|
+
};
|
|
16357
|
+
calcular();
|
|
16358
|
+
const observador = new MutationObserver(calcular);
|
|
16359
|
+
observador.observe(document.body, { childList: true, subtree: true });
|
|
16360
|
+
return () => observador.disconnect();
|
|
16361
|
+
}, [recordId]);
|
|
16362
|
+
const gastada = !esUltima;
|
|
16300
16363
|
const actions = useMemo(() => {
|
|
16301
|
-
if (!row) return [];
|
|
16364
|
+
if (!row || gastada) return [];
|
|
16302
16365
|
return (config.rowActions ?? []).filter((a) => !allowed || allowed.includes(a.key)).filter((a) => matchesVisibleWhen(a, row));
|
|
16303
|
-
}, [config.rowActions, row, allowed]);
|
|
16366
|
+
}, [config.rowActions, row, allowed, gastada]);
|
|
16304
16367
|
const title = recordLabel ?? config.title ?? "Servicio";
|
|
16305
|
-
return /* @__PURE__ */ jsxs("div", { className: "px-chat-crud-item", children: [
|
|
16368
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-chat-crud-item", ref: raiz, "data-panel-record": recordId, children: [
|
|
16306
16369
|
/* @__PURE__ */ jsx("div", { className: "px-chat-crud-item__header", children: title }),
|
|
16307
16370
|
crud.actionError && /* @__PURE__ */ jsxs("div", { className: "px-chat-crud-item__error", role: "alert", children: [
|
|
16308
16371
|
/* @__PURE__ */ jsx("span", { children: crud.actionError }),
|
|
@@ -16310,6 +16373,7 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16310
16373
|
] }),
|
|
16311
16374
|
crud.loading && !row && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-item__hint", children: "Cargando\u2026" }),
|
|
16312
16375
|
!crud.loading && !row && /* @__PURE__ */ jsx("div", { className: "px-chat-crud-item__hint", children: "No se encontr\xF3 el servicio." }),
|
|
16376
|
+
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." }),
|
|
16313
16377
|
row && config.deadline && typeof row[config.deadline.field] === "string" && /* @__PURE__ */ jsx(
|
|
16314
16378
|
DeadlineCountdown,
|
|
16315
16379
|
{
|
package/dist/index.js
CHANGED
|
@@ -5760,6 +5760,9 @@ const LUMI_ICON_TYPES = {
|
|
|
5760
5760
|
// Iconos para Agenda
|
|
5761
5761
|
USER_X: "user-x",
|
|
5762
5762
|
REFRESH: "refresh",
|
|
5763
|
+
// SPEC-104 — grúas: el selector de despacho necesitaba distinguir de un vistazo qué es cada
|
|
5764
|
+
// opción, y no había ningún icono de vehículo en el set.
|
|
5765
|
+
TRUCK: "truck",
|
|
5763
5766
|
// Iconos para Chat
|
|
5764
5767
|
MESSAGE_SQUARE: "message-square",
|
|
5765
5768
|
MESSAGE_CIRCLE: "message-circle",
|
|
@@ -6470,6 +6473,27 @@ const BuildingIcon = (props) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
|
6470
6473
|
]
|
|
6471
6474
|
}
|
|
6472
6475
|
);
|
|
6476
|
+
const TruckIcon = (props) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6477
|
+
"svg",
|
|
6478
|
+
{
|
|
6479
|
+
width: "16",
|
|
6480
|
+
height: "16",
|
|
6481
|
+
viewBox: "0 0 24 24",
|
|
6482
|
+
fill: "none",
|
|
6483
|
+
stroke: "currentColor",
|
|
6484
|
+
strokeWidth: "2",
|
|
6485
|
+
strokeLinecap: "round",
|
|
6486
|
+
strokeLinejoin: "round",
|
|
6487
|
+
...props,
|
|
6488
|
+
children: [
|
|
6489
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 17V8h10v9" }),
|
|
6490
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 11h4l3 3v3" }),
|
|
6491
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "7", cy: "18", r: "2" }),
|
|
6492
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "17", cy: "18", r: "2" }),
|
|
6493
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 18h6" })
|
|
6494
|
+
]
|
|
6495
|
+
}
|
|
6496
|
+
);
|
|
6473
6497
|
const MapPinIcon = (props) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6474
6498
|
"svg",
|
|
6475
6499
|
{
|
|
@@ -6588,6 +6612,7 @@ const lumiIconMap = {
|
|
|
6588
6612
|
// Iconos para Servicios
|
|
6589
6613
|
[LUMI_ICON_TYPES.BUILDING]: BuildingIcon,
|
|
6590
6614
|
[LUMI_ICON_TYPES.MAP_PIN]: MapPinIcon,
|
|
6615
|
+
[LUMI_ICON_TYPES.TRUCK]: TruckIcon,
|
|
6591
6616
|
// Iconos para Agenda
|
|
6592
6617
|
[LUMI_ICON_TYPES.USER_X]: UserXIcon,
|
|
6593
6618
|
[LUMI_ICON_TYPES.REFRESH]: RefreshIcon,
|
|
@@ -7759,8 +7784,11 @@ const Select$1 = React.forwardRef(
|
|
|
7759
7784
|
`${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__checkbox`,
|
|
7760
7785
|
isSelected && `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__checkbox--checked`
|
|
7761
7786
|
].filter(Boolean).join(" "), children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(Icon, { type: LUMI_ICON_TYPES.CHECK, size: "small" }) }) }),
|
|
7762
|
-
option.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-icon`, children: option.icon }),
|
|
7763
|
-
/* @__PURE__ */ jsxRuntime.
|
|
7787
|
+
(option.icon || option.iconName) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-icon`, children: option.icon ?? /* @__PURE__ */ jsxRuntime.jsx(Icon, { type: option.iconName }) }),
|
|
7788
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-texts`, children: [
|
|
7789
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-label`, children: option.label }),
|
|
7790
|
+
option.sublabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-sublabel`, children: option.sublabel })
|
|
7791
|
+
] }),
|
|
7764
7792
|
mode === "single" && isSelected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${COMPONENT_PREFIX}${COMPONENT_TYPES.SELECT}__option-check`, children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { type: LUMI_ICON_TYPES.CHECK, size: "small" }) })
|
|
7765
7793
|
]
|
|
7766
7794
|
},
|
|
@@ -14104,6 +14132,7 @@ function getFormatter(key) {
|
|
|
14104
14132
|
return key ? CRUD_FORMATTERS[key] : void 0;
|
|
14105
14133
|
}
|
|
14106
14134
|
|
|
14135
|
+
const OPTIONS_LOAD_ERROR = "No se pudieron cargar las opciones. Int\xE9ntalo de nuevo.";
|
|
14107
14136
|
function unwrap$3(raw) {
|
|
14108
14137
|
if (raw && typeof raw === "object" && "response" in raw) {
|
|
14109
14138
|
return raw.response;
|
|
@@ -14132,7 +14161,9 @@ async function loadOptions(eventBus, ep, ctx) {
|
|
|
14132
14161
|
const arr = Array.isArray(data) ? data : [];
|
|
14133
14162
|
return arr.map((item) => ({
|
|
14134
14163
|
value: String(item[ep.value_field] ?? ""),
|
|
14135
|
-
label: String(item[ep.label_field] ?? "")
|
|
14164
|
+
label: String(item[ep.label_field] ?? ""),
|
|
14165
|
+
...item.sublabel ? { sublabel: String(item.sublabel) } : {},
|
|
14166
|
+
...item.icon ? { iconName: String(item.icon) } : {}
|
|
14136
14167
|
}));
|
|
14137
14168
|
}
|
|
14138
14169
|
|
|
@@ -14439,6 +14470,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14439
14470
|
const fileFields = React.useMemo(() => formFields.filter((f) => f.moleculeType === "FILE"), [formFields]);
|
|
14440
14471
|
const normalFields = React.useMemo(() => formFields.filter((f) => f.moleculeType !== "FILE"), [formFields]);
|
|
14441
14472
|
const [optionsByField, setOptionsByField] = React.useState({});
|
|
14473
|
+
const [optionsErrorByField, setOptionsErrorByField] = React.useState({});
|
|
14442
14474
|
const [loadingOptions, setLoadingOptions] = React.useState(false);
|
|
14443
14475
|
const [submitting, setSubmitting] = React.useState(false);
|
|
14444
14476
|
const [fileEntries, setFileEntries] = React.useState({});
|
|
@@ -14452,14 +14484,17 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14452
14484
|
toLoad.map(async (f) => {
|
|
14453
14485
|
try {
|
|
14454
14486
|
const opts = await loadOptions(eventBus, f.optionsEndpoint, { ...row, id: row[idField] });
|
|
14455
|
-
return [f.name, opts];
|
|
14487
|
+
return [f.name, opts, null];
|
|
14456
14488
|
} catch {
|
|
14457
|
-
return [f.name, []];
|
|
14489
|
+
return [f.name, [], OPTIONS_LOAD_ERROR];
|
|
14458
14490
|
}
|
|
14459
14491
|
})
|
|
14460
14492
|
).then((entries) => {
|
|
14461
14493
|
if (!alive) return;
|
|
14462
|
-
setOptionsByField(Object.fromEntries(entries));
|
|
14494
|
+
setOptionsByField(Object.fromEntries(entries.map(([n, o]) => [n, o])));
|
|
14495
|
+
setOptionsErrorByField(
|
|
14496
|
+
Object.fromEntries(entries.filter(([, , e]) => e).map(([n, , e]) => [n, e]))
|
|
14497
|
+
);
|
|
14463
14498
|
setLoadingOptions(false);
|
|
14464
14499
|
});
|
|
14465
14500
|
return () => {
|
|
@@ -14473,9 +14508,12 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14473
14508
|
moleculeType: CRUD_TO_FORM_MOLECULE[f.moleculeType] ?? "INPUT",
|
|
14474
14509
|
required: f.required,
|
|
14475
14510
|
placeholder: f.placeholder,
|
|
14476
|
-
options: f.options ?? optionsByField[f.name]
|
|
14511
|
+
options: f.options ?? optionsByField[f.name],
|
|
14512
|
+
// SPEC-106 T8 — `uiProps` llega tal cual a la molécula, así que el desplegable dice lo
|
|
14513
|
+
// que pasó de verdad en vez del «no hay» de siempre. Sin error, se queda el de siempre.
|
|
14514
|
+
...optionsErrorByField[f.name] ? { uiProps: { notFoundContent: optionsErrorByField[f.name] } } : {}
|
|
14477
14515
|
})),
|
|
14478
|
-
[normalFields, optionsByField]
|
|
14516
|
+
[normalFields, optionsByField, optionsErrorByField]
|
|
14479
14517
|
);
|
|
14480
14518
|
const validationSchema = React.useMemo(() => buildCrudYupSchema(normalFields), [normalFields]);
|
|
14481
14519
|
const uploadOne = async (f, file, idx) => {
|
|
@@ -15910,7 +15948,7 @@ function chatLinkFormatter(field, eventBus) {
|
|
|
15910
15948
|
return React.createElement(ChatLinkCell, { targets, eventBus });
|
|
15911
15949
|
};
|
|
15912
15950
|
}
|
|
15913
|
-
function toRenderField(f, loaded, eventBus, mapsApiKey) {
|
|
15951
|
+
function toRenderField(f, loaded, eventBus, mapsApiKey, loadError) {
|
|
15914
15952
|
const fmt = getFormatter(f.format);
|
|
15915
15953
|
const options = f.options ?? loaded[f.name];
|
|
15916
15954
|
if (f.moleculeType === "LOCATION") {
|
|
@@ -15955,13 +15993,18 @@ function toRenderField(f, loaded, eventBus, mapsApiKey) {
|
|
|
15955
15993
|
columnWidth: f.columnWidth,
|
|
15956
15994
|
fixed: f.fixed,
|
|
15957
15995
|
align: f.align,
|
|
15958
|
-
|
|
15996
|
+
// SPEC-106 T8 — con un fallo de carga, el desplegable dice que no se pudo preguntar en vez
|
|
15997
|
+
// de «no hay opciones disponibles», que es una respuesta y no la tenemos.
|
|
15998
|
+
uiProps: loadError ? { options: options ?? [], notFoundContent: loadError } : options ? { options } : void 0,
|
|
15959
15999
|
formatValue
|
|
15960
16000
|
};
|
|
15961
16001
|
}
|
|
16002
|
+
const cumpleCondicion = (c, row) => c.in.includes(String(row[c.field]));
|
|
16003
|
+
const cumplenTodas = (c, row) => (Array.isArray(c) ? c : [c]).every((x) => cumpleCondicion(x, row));
|
|
15962
16004
|
function matchesVisibleWhen(a, row) {
|
|
16005
|
+
if (a.hiddenWhen && cumplenTodas(a.hiddenWhen, row)) return false;
|
|
15963
16006
|
if (!a.visibleWhen) return true;
|
|
15964
|
-
return a.visibleWhen
|
|
16007
|
+
return cumplenTodas(a.visibleWhen, row);
|
|
15965
16008
|
}
|
|
15966
16009
|
function matchesDisabledWhen(a, row) {
|
|
15967
16010
|
if (!a.disabledWhen) return false;
|
|
@@ -16002,6 +16045,7 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
16002
16045
|
const [activeAction, setActiveAction] = React.useState(null);
|
|
16003
16046
|
const [detailRow, setDetailRow] = React.useState(null);
|
|
16004
16047
|
const [optionsByField, setOptionsByField] = React.useState({});
|
|
16048
|
+
const [optionsErrorByField, setOptionsErrorByField] = React.useState({});
|
|
16005
16049
|
React.useEffect(() => {
|
|
16006
16050
|
let alive = true;
|
|
16007
16051
|
const toLoad = config.fields.filter((f) => f.optionsEndpoint);
|
|
@@ -16009,21 +16053,25 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
16009
16053
|
void Promise.all(
|
|
16010
16054
|
toLoad.map(async (f) => {
|
|
16011
16055
|
try {
|
|
16012
|
-
return [f.name, await loadOptions(eventBus, f.optionsEndpoint)];
|
|
16056
|
+
return [f.name, await loadOptions(eventBus, f.optionsEndpoint), null];
|
|
16013
16057
|
} catch {
|
|
16014
|
-
return [f.name, []];
|
|
16058
|
+
return [f.name, [], OPTIONS_LOAD_ERROR];
|
|
16015
16059
|
}
|
|
16016
16060
|
})
|
|
16017
16061
|
).then((entries) => {
|
|
16018
|
-
if (alive)
|
|
16062
|
+
if (!alive) return;
|
|
16063
|
+
setOptionsByField(Object.fromEntries(entries.map(([n, o]) => [n, o])));
|
|
16064
|
+
setOptionsErrorByField(
|
|
16065
|
+
Object.fromEntries(entries.filter(([, , e]) => e).map(([n, , e]) => [n, e]))
|
|
16066
|
+
);
|
|
16019
16067
|
});
|
|
16020
16068
|
return () => {
|
|
16021
16069
|
alive = false;
|
|
16022
16070
|
};
|
|
16023
16071
|
}, [eventBus, config.fields]);
|
|
16024
16072
|
const renderByFields = React.useMemo(
|
|
16025
|
-
() => config.fields.map((f) => toRenderField(f, optionsByField, eventBus, googleMapsApiKey)),
|
|
16026
|
-
[config.fields, optionsByField, eventBus]
|
|
16073
|
+
() => config.fields.map((f) => toRenderField(f, optionsByField, eventBus, googleMapsApiKey, optionsErrorByField[f.name])),
|
|
16074
|
+
[config.fields, optionsByField, optionsErrorByField, eventBus]
|
|
16027
16075
|
);
|
|
16028
16076
|
const validationSchema = React.useMemo(() => buildCrudYupSchema(config.fields), [config.fields]);
|
|
16029
16077
|
const rowActions = React.useMemo(() => {
|
|
@@ -16318,12 +16366,27 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16318
16366
|
() => crud.rows.find((r) => String(r[config.idField]) === recordId),
|
|
16319
16367
|
[crud.rows, config.idField, recordId]
|
|
16320
16368
|
);
|
|
16369
|
+
const raiz = React.useRef(null);
|
|
16370
|
+
const [esUltima, setEsUltima] = React.useState(true);
|
|
16371
|
+
React.useEffect(() => {
|
|
16372
|
+
const calcular = () => {
|
|
16373
|
+
const el = raiz.current;
|
|
16374
|
+
if (!el) return;
|
|
16375
|
+
const hermanas = document.querySelectorAll(`[data-panel-record="${recordId}"]`);
|
|
16376
|
+
setEsUltima(hermanas.length === 0 || hermanas[hermanas.length - 1] === el);
|
|
16377
|
+
};
|
|
16378
|
+
calcular();
|
|
16379
|
+
const observador = new MutationObserver(calcular);
|
|
16380
|
+
observador.observe(document.body, { childList: true, subtree: true });
|
|
16381
|
+
return () => observador.disconnect();
|
|
16382
|
+
}, [recordId]);
|
|
16383
|
+
const gastada = !esUltima;
|
|
16321
16384
|
const actions = React.useMemo(() => {
|
|
16322
|
-
if (!row) return [];
|
|
16385
|
+
if (!row || gastada) return [];
|
|
16323
16386
|
return (config.rowActions ?? []).filter((a) => !allowed || allowed.includes(a.key)).filter((a) => matchesVisibleWhen(a, row));
|
|
16324
|
-
}, [config.rowActions, row, allowed]);
|
|
16387
|
+
}, [config.rowActions, row, allowed, gastada]);
|
|
16325
16388
|
const title = recordLabel ?? config.title ?? "Servicio";
|
|
16326
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-crud-item", children: [
|
|
16389
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-crud-item", ref: raiz, "data-panel-record": recordId, children: [
|
|
16327
16390
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-crud-item__header", children: title }),
|
|
16328
16391
|
crud.actionError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-crud-item__error", role: "alert", children: [
|
|
16329
16392
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: crud.actionError }),
|
|
@@ -16331,6 +16394,7 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16331
16394
|
] }),
|
|
16332
16395
|
crud.loading && !row && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-crud-item__hint", children: "Cargando\u2026" }),
|
|
16333
16396
|
!crud.loading && !row && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-crud-item__hint", children: "No se encontr\xF3 el servicio." }),
|
|
16397
|
+
gastada && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-crud-item__hint", children: "Este paso ya se gestion\xF3. Contin\xFAa en el mensaje m\xE1s reciente." }),
|
|
16334
16398
|
row && config.deadline && typeof row[config.deadline.field] === "string" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
16335
16399
|
DeadlineCountdown,
|
|
16336
16400
|
{
|