pixelon 0.3.81 → 0.3.83
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/optionsLoader.d.ts +1 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/types.d.ts +15 -3
- package/dist/index.esm.js +45 -17
- package/dist/index.js +45 -17
- package/package.json +1 -1
package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/ReactiveCrud.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface ReactiveCrudProps {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function matchesVisibleWhen(a: RowActionDef, row: Record<string, unknown>): boolean;
|
|
17
17
|
export declare function matchesDisabledWhen(a: RowActionDef, row: Record<string, unknown>): boolean;
|
|
18
|
+
export declare function disabledReasonFor(a: RowActionDef, row: Record<string, unknown>): string | undefined;
|
|
18
19
|
export declare function matchesDoneWhen(a: RowActionDef, row: Record<string, unknown>): boolean;
|
|
19
20
|
export declare function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, onCreateClose, googleMapsApiKey: apiKeyProp }: ReactiveCrudProps): import("react/jsx-runtime").JSX.Element;
|
|
20
21
|
export default ReactiveCrud;
|
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[]>;
|
|
@@ -84,6 +84,14 @@ export interface RowActionVisibleWhen {
|
|
|
84
84
|
field: string;
|
|
85
85
|
in: string[];
|
|
86
86
|
}
|
|
87
|
+
/** SPEC-113 — una condición que apaga la acción, con SU motivo.
|
|
88
|
+
*
|
|
89
|
+
* Un botón deshabilitado que no dice cuál de dos motivos lo apaga enseña a ignorarlo. Fundir dos
|
|
90
|
+
* situaciones opuestas —«falta el comprobante del cliente» y «todavía no has llegado»— en un
|
|
91
|
+
* «no puedes todavía» es la validación muda de SPEC-074 con otra cara. */
|
|
92
|
+
export interface RowActionDisabledWhen extends RowActionVisibleWhen {
|
|
93
|
+
reason?: string;
|
|
94
|
+
}
|
|
87
95
|
export interface RowActionDef {
|
|
88
96
|
key: string;
|
|
89
97
|
label: string;
|
|
@@ -97,9 +105,13 @@ export interface RowActionDef {
|
|
|
97
105
|
* aceptado el precio—, que con condiciones unidas por «y» no se pueden escribir. */
|
|
98
106
|
hiddenWhen?: RowActionVisibleWhen | RowActionVisibleWhen[];
|
|
99
107
|
/** SPEC-007: la acción se muestra pero queda DESHABILITADA cuando `String(row[field])` está en
|
|
100
|
-
* `in` (ej. "Iniciar" deshabilitado hasta que `has_inspection` sea true).
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
* `in` (ej. "Iniciar" deshabilitado hasta que `has_inspection` sea true).
|
|
109
|
+
*
|
|
110
|
+
* SPEC-113: admite VARIAS, cada una con su motivo. Se apaga con la primera que se cumple, y esa
|
|
111
|
+
* es la que habla. Una sola condición sigue valiendo igual que siempre. */
|
|
112
|
+
disabledWhen?: RowActionDisabledWhen | RowActionDisabledWhen[];
|
|
113
|
+
/** SPEC-007: tooltip cuando la acción está deshabilitada (ej. "Registra la inspección primero").
|
|
114
|
+
* SPEC-113: es el respaldo — una condición con `reason` propio manda sobre este. */
|
|
103
115
|
disabledReason?: string;
|
|
104
116
|
/** SPEC-007: muestra un ✓ al final cuando `String(row[field])` está en `in` (ej. inspección ya
|
|
105
117
|
* registrada). Indicador de "hecho"; no bloquea reejecutar la acción. */
|
package/dist/index.esm.js
CHANGED
|
@@ -14111,6 +14111,7 @@ function getFormatter(key) {
|
|
|
14111
14111
|
return key ? CRUD_FORMATTERS[key] : void 0;
|
|
14112
14112
|
}
|
|
14113
14113
|
|
|
14114
|
+
const OPTIONS_LOAD_ERROR = "No se pudieron cargar las opciones. Int\xE9ntalo de nuevo.";
|
|
14114
14115
|
function unwrap$3(raw) {
|
|
14115
14116
|
if (raw && typeof raw === "object" && "response" in raw) {
|
|
14116
14117
|
return raw.response;
|
|
@@ -14448,6 +14449,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14448
14449
|
const fileFields = useMemo(() => formFields.filter((f) => f.moleculeType === "FILE"), [formFields]);
|
|
14449
14450
|
const normalFields = useMemo(() => formFields.filter((f) => f.moleculeType !== "FILE"), [formFields]);
|
|
14450
14451
|
const [optionsByField, setOptionsByField] = useState({});
|
|
14452
|
+
const [optionsErrorByField, setOptionsErrorByField] = useState({});
|
|
14451
14453
|
const [loadingOptions, setLoadingOptions] = useState(false);
|
|
14452
14454
|
const [submitting, setSubmitting] = useState(false);
|
|
14453
14455
|
const [fileEntries, setFileEntries] = useState({});
|
|
@@ -14461,14 +14463,17 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14461
14463
|
toLoad.map(async (f) => {
|
|
14462
14464
|
try {
|
|
14463
14465
|
const opts = await loadOptions(eventBus, f.optionsEndpoint, { ...row, id: row[idField] });
|
|
14464
|
-
return [f.name, opts];
|
|
14466
|
+
return [f.name, opts, null];
|
|
14465
14467
|
} catch {
|
|
14466
|
-
return [f.name, []];
|
|
14468
|
+
return [f.name, [], OPTIONS_LOAD_ERROR];
|
|
14467
14469
|
}
|
|
14468
14470
|
})
|
|
14469
14471
|
).then((entries) => {
|
|
14470
14472
|
if (!alive) return;
|
|
14471
|
-
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
|
+
);
|
|
14472
14477
|
setLoadingOptions(false);
|
|
14473
14478
|
});
|
|
14474
14479
|
return () => {
|
|
@@ -14482,9 +14487,12 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14482
14487
|
moleculeType: CRUD_TO_FORM_MOLECULE[f.moleculeType] ?? "INPUT",
|
|
14483
14488
|
required: f.required,
|
|
14484
14489
|
placeholder: f.placeholder,
|
|
14485
|
-
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] } } : {}
|
|
14486
14494
|
})),
|
|
14487
|
-
[normalFields, optionsByField]
|
|
14495
|
+
[normalFields, optionsByField, optionsErrorByField]
|
|
14488
14496
|
);
|
|
14489
14497
|
const validationSchema = useMemo(() => buildCrudYupSchema(normalFields), [normalFields]);
|
|
14490
14498
|
const uploadOne = async (f, file, idx) => {
|
|
@@ -15919,7 +15927,7 @@ function chatLinkFormatter(field, eventBus) {
|
|
|
15919
15927
|
return createElement(ChatLinkCell, { targets, eventBus });
|
|
15920
15928
|
};
|
|
15921
15929
|
}
|
|
15922
|
-
function toRenderField(f, loaded, eventBus, mapsApiKey) {
|
|
15930
|
+
function toRenderField(f, loaded, eventBus, mapsApiKey, loadError) {
|
|
15923
15931
|
const fmt = getFormatter(f.format);
|
|
15924
15932
|
const options = f.options ?? loaded[f.name];
|
|
15925
15933
|
if (f.moleculeType === "LOCATION") {
|
|
@@ -15964,7 +15972,9 @@ function toRenderField(f, loaded, eventBus, mapsApiKey) {
|
|
|
15964
15972
|
columnWidth: f.columnWidth,
|
|
15965
15973
|
fixed: f.fixed,
|
|
15966
15974
|
align: f.align,
|
|
15967
|
-
|
|
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,
|
|
15968
15978
|
formatValue
|
|
15969
15979
|
};
|
|
15970
15980
|
}
|
|
@@ -15975,9 +15985,20 @@ function matchesVisibleWhen(a, row) {
|
|
|
15975
15985
|
if (!a.visibleWhen) return true;
|
|
15976
15986
|
return cumplenTodas(a.visibleWhen, row);
|
|
15977
15987
|
}
|
|
15988
|
+
function condicionesDeshabilitado(a) {
|
|
15989
|
+
if (!a.disabledWhen) return [];
|
|
15990
|
+
return Array.isArray(a.disabledWhen) ? a.disabledWhen : [a.disabledWhen];
|
|
15991
|
+
}
|
|
15992
|
+
function primeraQueApaga(a, row) {
|
|
15993
|
+
return condicionesDeshabilitado(a).find((c) => c.in.includes(String(row[c.field])));
|
|
15994
|
+
}
|
|
15978
15995
|
function matchesDisabledWhen(a, row) {
|
|
15979
|
-
|
|
15980
|
-
|
|
15996
|
+
return primeraQueApaga(a, row) !== void 0;
|
|
15997
|
+
}
|
|
15998
|
+
function disabledReasonFor(a, row) {
|
|
15999
|
+
const c = primeraQueApaga(a, row);
|
|
16000
|
+
if (!c) return void 0;
|
|
16001
|
+
return c.reason ?? a.disabledReason;
|
|
15981
16002
|
}
|
|
15982
16003
|
function matchesDoneWhen(a, row) {
|
|
15983
16004
|
if (!a.doneWhen) return false;
|
|
@@ -16014,6 +16035,7 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
16014
16035
|
const [activeAction, setActiveAction] = useState(null);
|
|
16015
16036
|
const [detailRow, setDetailRow] = useState(null);
|
|
16016
16037
|
const [optionsByField, setOptionsByField] = useState({});
|
|
16038
|
+
const [optionsErrorByField, setOptionsErrorByField] = useState({});
|
|
16017
16039
|
useEffect(() => {
|
|
16018
16040
|
let alive = true;
|
|
16019
16041
|
const toLoad = config.fields.filter((f) => f.optionsEndpoint);
|
|
@@ -16021,21 +16043,25 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
16021
16043
|
void Promise.all(
|
|
16022
16044
|
toLoad.map(async (f) => {
|
|
16023
16045
|
try {
|
|
16024
|
-
return [f.name, await loadOptions(eventBus, f.optionsEndpoint)];
|
|
16046
|
+
return [f.name, await loadOptions(eventBus, f.optionsEndpoint), null];
|
|
16025
16047
|
} catch {
|
|
16026
|
-
return [f.name, []];
|
|
16048
|
+
return [f.name, [], OPTIONS_LOAD_ERROR];
|
|
16027
16049
|
}
|
|
16028
16050
|
})
|
|
16029
16051
|
).then((entries) => {
|
|
16030
|
-
if (alive)
|
|
16052
|
+
if (!alive) return;
|
|
16053
|
+
setOptionsByField(Object.fromEntries(entries.map(([n, o]) => [n, o])));
|
|
16054
|
+
setOptionsErrorByField(
|
|
16055
|
+
Object.fromEntries(entries.filter(([, , e]) => e).map(([n, , e]) => [n, e]))
|
|
16056
|
+
);
|
|
16031
16057
|
});
|
|
16032
16058
|
return () => {
|
|
16033
16059
|
alive = false;
|
|
16034
16060
|
};
|
|
16035
16061
|
}, [eventBus, config.fields]);
|
|
16036
16062
|
const renderByFields = useMemo(
|
|
16037
|
-
() => config.fields.map((f) => toRenderField(f, optionsByField, eventBus, googleMapsApiKey)),
|
|
16038
|
-
[config.fields, optionsByField, eventBus]
|
|
16063
|
+
() => config.fields.map((f) => toRenderField(f, optionsByField, eventBus, googleMapsApiKey, optionsErrorByField[f.name])),
|
|
16064
|
+
[config.fields, optionsByField, optionsErrorByField, eventBus]
|
|
16039
16065
|
);
|
|
16040
16066
|
const validationSchema = useMemo(() => buildCrudYupSchema(config.fields), [config.fields]);
|
|
16041
16067
|
const rowActions = useMemo(() => {
|
|
@@ -16047,7 +16073,8 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
16047
16073
|
// SPEC-090 T3 — el `disabledReason` de la config ya existía, pero solo lo pintaba el panel
|
|
16048
16074
|
// del chat; en la tabla el botón se apagaba mudo. Afecta también al de la inspección
|
|
16049
16075
|
// («Falta el comprobante de pago del cliente», SPEC-066 T7), que tampoco se veía aquí.
|
|
16050
|
-
|
|
16076
|
+
// SPEC-113 — el motivo sale de la condición que apagó el botón, no de la acción entera.
|
|
16077
|
+
disabledReason: (row) => disabledReasonFor(a, row),
|
|
16051
16078
|
onClick: (row) => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] })
|
|
16052
16079
|
}));
|
|
16053
16080
|
if (config.detail) {
|
|
@@ -16371,6 +16398,7 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16371
16398
|
config.detail && /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-crud-item__action", onClick: () => setDetailRow(row), children: "Ver" }),
|
|
16372
16399
|
actions.map((a) => {
|
|
16373
16400
|
const disabled = matchesDisabledWhen(a, row);
|
|
16401
|
+
const motivo = disabledReasonFor(a, row);
|
|
16374
16402
|
const done = matchesDoneWhen(a, row);
|
|
16375
16403
|
return /* @__PURE__ */ jsxs(
|
|
16376
16404
|
"button",
|
|
@@ -16379,14 +16407,14 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16379
16407
|
className: [
|
|
16380
16408
|
"px-chat-crud-item__action",
|
|
16381
16409
|
a.intent === "danger" ? "is-danger" : "",
|
|
16382
|
-
disabled &&
|
|
16410
|
+
disabled && motivo ? "is-con-motivo" : ""
|
|
16383
16411
|
].filter(Boolean).join(" "),
|
|
16384
16412
|
disabled,
|
|
16385
16413
|
onClick: () => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] }),
|
|
16386
16414
|
children: [
|
|
16387
16415
|
/* @__PURE__ */ jsxs("span", { className: "px-chat-crud-item__action-texts", children: [
|
|
16388
16416
|
/* @__PURE__ */ jsx("span", { children: a.label }),
|
|
16389
|
-
disabled &&
|
|
16417
|
+
disabled && motivo && /* @__PURE__ */ jsx("span", { className: "px-chat-crud-item__action-reason", children: motivo })
|
|
16390
16418
|
] }),
|
|
16391
16419
|
done && /* @__PURE__ */ jsx(Icon$1, { type: "check-circle", width: 18, height: 18, className: "px-chat-crud-item__done", "aria-label": "Hecho" })
|
|
16392
16420
|
]
|
package/dist/index.js
CHANGED
|
@@ -14132,6 +14132,7 @@ function getFormatter(key) {
|
|
|
14132
14132
|
return key ? CRUD_FORMATTERS[key] : void 0;
|
|
14133
14133
|
}
|
|
14134
14134
|
|
|
14135
|
+
const OPTIONS_LOAD_ERROR = "No se pudieron cargar las opciones. Int\xE9ntalo de nuevo.";
|
|
14135
14136
|
function unwrap$3(raw) {
|
|
14136
14137
|
if (raw && typeof raw === "object" && "response" in raw) {
|
|
14137
14138
|
return raw.response;
|
|
@@ -14469,6 +14470,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14469
14470
|
const fileFields = React.useMemo(() => formFields.filter((f) => f.moleculeType === "FILE"), [formFields]);
|
|
14470
14471
|
const normalFields = React.useMemo(() => formFields.filter((f) => f.moleculeType !== "FILE"), [formFields]);
|
|
14471
14472
|
const [optionsByField, setOptionsByField] = React.useState({});
|
|
14473
|
+
const [optionsErrorByField, setOptionsErrorByField] = React.useState({});
|
|
14472
14474
|
const [loadingOptions, setLoadingOptions] = React.useState(false);
|
|
14473
14475
|
const [submitting, setSubmitting] = React.useState(false);
|
|
14474
14476
|
const [fileEntries, setFileEntries] = React.useState({});
|
|
@@ -14482,14 +14484,17 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14482
14484
|
toLoad.map(async (f) => {
|
|
14483
14485
|
try {
|
|
14484
14486
|
const opts = await loadOptions(eventBus, f.optionsEndpoint, { ...row, id: row[idField] });
|
|
14485
|
-
return [f.name, opts];
|
|
14487
|
+
return [f.name, opts, null];
|
|
14486
14488
|
} catch {
|
|
14487
|
-
return [f.name, []];
|
|
14489
|
+
return [f.name, [], OPTIONS_LOAD_ERROR];
|
|
14488
14490
|
}
|
|
14489
14491
|
})
|
|
14490
14492
|
).then((entries) => {
|
|
14491
14493
|
if (!alive) return;
|
|
14492
|
-
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
|
+
);
|
|
14493
14498
|
setLoadingOptions(false);
|
|
14494
14499
|
});
|
|
14495
14500
|
return () => {
|
|
@@ -14503,9 +14508,12 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
|
|
|
14503
14508
|
moleculeType: CRUD_TO_FORM_MOLECULE[f.moleculeType] ?? "INPUT",
|
|
14504
14509
|
required: f.required,
|
|
14505
14510
|
placeholder: f.placeholder,
|
|
14506
|
-
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] } } : {}
|
|
14507
14515
|
})),
|
|
14508
|
-
[normalFields, optionsByField]
|
|
14516
|
+
[normalFields, optionsByField, optionsErrorByField]
|
|
14509
14517
|
);
|
|
14510
14518
|
const validationSchema = React.useMemo(() => buildCrudYupSchema(normalFields), [normalFields]);
|
|
14511
14519
|
const uploadOne = async (f, file, idx) => {
|
|
@@ -15940,7 +15948,7 @@ function chatLinkFormatter(field, eventBus) {
|
|
|
15940
15948
|
return React.createElement(ChatLinkCell, { targets, eventBus });
|
|
15941
15949
|
};
|
|
15942
15950
|
}
|
|
15943
|
-
function toRenderField(f, loaded, eventBus, mapsApiKey) {
|
|
15951
|
+
function toRenderField(f, loaded, eventBus, mapsApiKey, loadError) {
|
|
15944
15952
|
const fmt = getFormatter(f.format);
|
|
15945
15953
|
const options = f.options ?? loaded[f.name];
|
|
15946
15954
|
if (f.moleculeType === "LOCATION") {
|
|
@@ -15985,7 +15993,9 @@ function toRenderField(f, loaded, eventBus, mapsApiKey) {
|
|
|
15985
15993
|
columnWidth: f.columnWidth,
|
|
15986
15994
|
fixed: f.fixed,
|
|
15987
15995
|
align: f.align,
|
|
15988
|
-
|
|
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,
|
|
15989
15999
|
formatValue
|
|
15990
16000
|
};
|
|
15991
16001
|
}
|
|
@@ -15996,9 +16006,20 @@ function matchesVisibleWhen(a, row) {
|
|
|
15996
16006
|
if (!a.visibleWhen) return true;
|
|
15997
16007
|
return cumplenTodas(a.visibleWhen, row);
|
|
15998
16008
|
}
|
|
16009
|
+
function condicionesDeshabilitado(a) {
|
|
16010
|
+
if (!a.disabledWhen) return [];
|
|
16011
|
+
return Array.isArray(a.disabledWhen) ? a.disabledWhen : [a.disabledWhen];
|
|
16012
|
+
}
|
|
16013
|
+
function primeraQueApaga(a, row) {
|
|
16014
|
+
return condicionesDeshabilitado(a).find((c) => c.in.includes(String(row[c.field])));
|
|
16015
|
+
}
|
|
15999
16016
|
function matchesDisabledWhen(a, row) {
|
|
16000
|
-
|
|
16001
|
-
|
|
16017
|
+
return primeraQueApaga(a, row) !== void 0;
|
|
16018
|
+
}
|
|
16019
|
+
function disabledReasonFor(a, row) {
|
|
16020
|
+
const c = primeraQueApaga(a, row);
|
|
16021
|
+
if (!c) return void 0;
|
|
16022
|
+
return c.reason ?? a.disabledReason;
|
|
16002
16023
|
}
|
|
16003
16024
|
function matchesDoneWhen(a, row) {
|
|
16004
16025
|
if (!a.doneWhen) return false;
|
|
@@ -16035,6 +16056,7 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
16035
16056
|
const [activeAction, setActiveAction] = React.useState(null);
|
|
16036
16057
|
const [detailRow, setDetailRow] = React.useState(null);
|
|
16037
16058
|
const [optionsByField, setOptionsByField] = React.useState({});
|
|
16059
|
+
const [optionsErrorByField, setOptionsErrorByField] = React.useState({});
|
|
16038
16060
|
React.useEffect(() => {
|
|
16039
16061
|
let alive = true;
|
|
16040
16062
|
const toLoad = config.fields.filter((f) => f.optionsEndpoint);
|
|
@@ -16042,21 +16064,25 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
16042
16064
|
void Promise.all(
|
|
16043
16065
|
toLoad.map(async (f) => {
|
|
16044
16066
|
try {
|
|
16045
|
-
return [f.name, await loadOptions(eventBus, f.optionsEndpoint)];
|
|
16067
|
+
return [f.name, await loadOptions(eventBus, f.optionsEndpoint), null];
|
|
16046
16068
|
} catch {
|
|
16047
|
-
return [f.name, []];
|
|
16069
|
+
return [f.name, [], OPTIONS_LOAD_ERROR];
|
|
16048
16070
|
}
|
|
16049
16071
|
})
|
|
16050
16072
|
).then((entries) => {
|
|
16051
|
-
if (alive)
|
|
16073
|
+
if (!alive) return;
|
|
16074
|
+
setOptionsByField(Object.fromEntries(entries.map(([n, o]) => [n, o])));
|
|
16075
|
+
setOptionsErrorByField(
|
|
16076
|
+
Object.fromEntries(entries.filter(([, , e]) => e).map(([n, , e]) => [n, e]))
|
|
16077
|
+
);
|
|
16052
16078
|
});
|
|
16053
16079
|
return () => {
|
|
16054
16080
|
alive = false;
|
|
16055
16081
|
};
|
|
16056
16082
|
}, [eventBus, config.fields]);
|
|
16057
16083
|
const renderByFields = React.useMemo(
|
|
16058
|
-
() => config.fields.map((f) => toRenderField(f, optionsByField, eventBus, googleMapsApiKey)),
|
|
16059
|
-
[config.fields, optionsByField, eventBus]
|
|
16084
|
+
() => config.fields.map((f) => toRenderField(f, optionsByField, eventBus, googleMapsApiKey, optionsErrorByField[f.name])),
|
|
16085
|
+
[config.fields, optionsByField, optionsErrorByField, eventBus]
|
|
16060
16086
|
);
|
|
16061
16087
|
const validationSchema = React.useMemo(() => buildCrudYupSchema(config.fields), [config.fields]);
|
|
16062
16088
|
const rowActions = React.useMemo(() => {
|
|
@@ -16068,7 +16094,8 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
|
|
|
16068
16094
|
// SPEC-090 T3 — el `disabledReason` de la config ya existía, pero solo lo pintaba el panel
|
|
16069
16095
|
// del chat; en la tabla el botón se apagaba mudo. Afecta también al de la inspección
|
|
16070
16096
|
// («Falta el comprobante de pago del cliente», SPEC-066 T7), que tampoco se veía aquí.
|
|
16071
|
-
|
|
16097
|
+
// SPEC-113 — el motivo sale de la condición que apagó el botón, no de la acción entera.
|
|
16098
|
+
disabledReason: (row) => disabledReasonFor(a, row),
|
|
16072
16099
|
onClick: (row) => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] })
|
|
16073
16100
|
}));
|
|
16074
16101
|
if (config.detail) {
|
|
@@ -16392,6 +16419,7 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16392
16419
|
config.detail && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-chat-crud-item__action", onClick: () => setDetailRow(row), children: "Ver" }),
|
|
16393
16420
|
actions.map((a) => {
|
|
16394
16421
|
const disabled = matchesDisabledWhen(a, row);
|
|
16422
|
+
const motivo = disabledReasonFor(a, row);
|
|
16395
16423
|
const done = matchesDoneWhen(a, row);
|
|
16396
16424
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16397
16425
|
"button",
|
|
@@ -16400,14 +16428,14 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
|
|
|
16400
16428
|
className: [
|
|
16401
16429
|
"px-chat-crud-item__action",
|
|
16402
16430
|
a.intent === "danger" ? "is-danger" : "",
|
|
16403
|
-
disabled &&
|
|
16431
|
+
disabled && motivo ? "is-con-motivo" : ""
|
|
16404
16432
|
].filter(Boolean).join(" "),
|
|
16405
16433
|
disabled,
|
|
16406
16434
|
onClick: () => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] }),
|
|
16407
16435
|
children: [
|
|
16408
16436
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-crud-item__action-texts", children: [
|
|
16409
16437
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: a.label }),
|
|
16410
|
-
disabled &&
|
|
16438
|
+
disabled && motivo && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-crud-item__action-reason", children: motivo })
|
|
16411
16439
|
] }),
|
|
16412
16440
|
done && /* @__PURE__ */ jsxRuntime.jsx(Icon$1, { type: "check-circle", width: 18, height: 18, className: "px-chat-crud-item__done", "aria-label": "Hecho" })
|
|
16413
16441
|
]
|