pixelon 0.3.76 → 0.3.78

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.
Files changed (26) hide show
  1. package/dist/components/atoms/lumi/FieldValue/FieldValue.d.ts +15 -0
  2. package/dist/components/atoms/lumi/FieldValue/index.d.ts +2 -0
  3. package/dist/components/atoms/lumi/FieldValue/types.d.ts +10 -0
  4. package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/ChatLinkCell.d.ts +20 -0
  5. package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/optionsLoader.d.ts +2 -1
  6. package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/types.d.ts +6 -0
  7. package/dist/components/molecules/chat/StructuredContent/helpers/errorMessage.d.ts +5 -0
  8. package/dist/components/molecules/lumi/Drawer/Drawer.d.ts +8 -0
  9. package/dist/components/molecules/lumi/DropdownMenu/types.d.ts +6 -0
  10. package/dist/components/molecules/lumi/RecordCard/RecordCard.d.ts +14 -0
  11. package/dist/components/molecules/lumi/RecordCard/index.d.ts +2 -0
  12. package/dist/components/molecules/lumi/RecordCard/types.d.ts +16 -0
  13. package/dist/components/molecules/lumi/ScrollToEnd/ScrollToEnd.d.ts +21 -0
  14. package/dist/components/molecules/lumi/ScrollToEnd/index.d.ts +2 -0
  15. package/dist/components/templates/lumi/Crud/CardList.d.ts +34 -0
  16. package/dist/components/templates/lumi/Crud/Crud.d.ts +1 -1
  17. package/dist/components/templates/lumi/Crud/ScrollToEnd.d.ts +18 -0
  18. package/dist/components/templates/lumi/Crud/helpers/fieldValue.d.ts +12 -0
  19. package/dist/components/templates/lumi/Crud/types.d.ts +27 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.esm.js +583 -279
  22. package/dist/index.js +584 -278
  23. package/dist/styles.css +1 -1
  24. package/dist/styles.css.map +1 -1
  25. package/package.json +1 -1
  26. package/src/styles/index.scss +4 -0
package/dist/index.esm.js CHANGED
@@ -3987,13 +3987,13 @@ function resolveVariable(template, formValues, fieldStates) {
3987
3987
  }
3988
3988
  return "";
3989
3989
  }
3990
- function resolveBody(obj, formValues, fieldStates) {
3990
+ function resolveBody$1(obj, formValues, fieldStates) {
3991
3991
  if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
3992
- if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
3992
+ if (Array.isArray(obj)) return obj.map((item) => resolveBody$1(item, formValues, fieldStates));
3993
3993
  if (obj !== null && typeof obj === "object") {
3994
3994
  const resolved = {};
3995
3995
  for (const [key, value] of Object.entries(obj)) {
3996
- resolved[key] = resolveBody(value, formValues, fieldStates);
3996
+ resolved[key] = resolveBody$1(value, formValues, fieldStates);
3997
3997
  }
3998
3998
  return resolved;
3999
3999
  }
@@ -4043,7 +4043,7 @@ const CostSummaryField = ({
4043
4043
  return;
4044
4044
  }
4045
4045
  fetchedRef.current = true;
4046
- const body = resolveBody(field.endpoint.body || {}, values, fieldStates);
4046
+ const body = resolveBody$1(field.endpoint.body || {}, values, fieldStates);
4047
4047
  setLoading(true);
4048
4048
  setError("");
4049
4049
  fetchViaEventBus(eventBus, field.endpoint.service, field.endpoint.url, field.endpoint.method, body).then((data) => {
@@ -4497,7 +4497,7 @@ function useReactiveEndpoints({ fields, fieldStates, setFieldState, eventBus, ti
4497
4497
  setFieldState(field.name, { loading: true, error: null });
4498
4498
  const startTime = Date.now();
4499
4499
  try {
4500
- const resolvedBody = field.endpoint.body ? resolveBody(field.endpoint.body, formValues, fieldStates) : void 0;
4500
+ const resolvedBody = field.endpoint.body ? resolveBody$1(field.endpoint.body, formValues, fieldStates) : void 0;
4501
4501
  const params = field.endpoint.params ? resolveParams(field.endpoint.params, formValues, fieldStates) : {};
4502
4502
  const requestBody = resolvedBody ?? (Object.keys(params).length > 0 ? params : void 0);
4503
4503
  if (eventBus) {
@@ -4772,7 +4772,7 @@ function useReactiveForm(props) {
4772
4772
  const handleSubmit = async () => {
4773
4773
  if (isSubmitting) return;
4774
4774
  setIsSubmitting(true);
4775
- const resolvedPayload = resolveBody(submit.payload_map, values, fieldStates);
4775
+ const resolvedPayload = resolveBody$1(submit.payload_map, values, fieldStates);
4776
4776
  if (eventBus) {
4777
4777
  try {
4778
4778
  const data = await fetchViaEventBus(
@@ -10865,6 +10865,7 @@ const Drawer = ({
10865
10865
  size = DRAWER_SIZES.RESPONSIVE,
10866
10866
  fixedSize = 400,
10867
10867
  responsivePercentage = 80,
10868
+ mobileSheet = false,
10868
10869
  title,
10869
10870
  headerExtra,
10870
10871
  showCloseButton = true,
@@ -10947,11 +10948,13 @@ const Drawer = ({
10947
10948
  `${COMPONENT_PREFIX}drawer--${size}`,
10948
10949
  isOpen && `${COMPONENT_PREFIX}drawer--open`,
10949
10950
  isMobile && `${COMPONENT_PREFIX}drawer--mobile`,
10951
+ mobileSheet && `${COMPONENT_PREFIX}drawer--sheet`,
10950
10952
  className
10951
10953
  ].filter(Boolean).join(" ");
10952
10954
  const overlayClasses = [
10953
10955
  `${COMPONENT_PREFIX}drawer-overlay`,
10954
- isOpen && showOverlay && `${COMPONENT_PREFIX}drawer-overlay--visible`
10956
+ isOpen && showOverlay && `${COMPONENT_PREFIX}drawer-overlay--visible`,
10957
+ mobileSheet && `${COMPONENT_PREFIX}drawer-overlay--sheet`
10955
10958
  ].filter(Boolean).join(" ");
10956
10959
  const getDrawerStyles = () => {
10957
10960
  if (isMobile) {
@@ -11686,11 +11689,12 @@ const DropdownMenu = ({
11686
11689
  children: options.map((option, index) => /* @__PURE__ */ jsxs(
11687
11690
  "button",
11688
11691
  {
11689
- className: `${baseClass}__option ${option.disabled ? `${baseClass}__option--disabled` : ""}`,
11692
+ className: `${baseClass}__option ${option.disabled ? `${baseClass}__option--disabled` : ""} ${option.disabled && option.disabledReason ? `${baseClass}__option--con-motivo` : ""}`,
11690
11693
  onClick: () => !option.disabled && handleOptionClick(option.onClick),
11691
11694
  disabled: option.disabled,
11692
11695
  role: "menuitem",
11693
11696
  type: "button",
11697
+ title: option.disabled && option.disabledReason ? option.disabledReason : void 0,
11694
11698
  children: [
11695
11699
  option.icon && /* @__PURE__ */ jsx(
11696
11700
  Icon,
@@ -11700,7 +11704,10 @@ const DropdownMenu = ({
11700
11704
  className: `${baseClass}__option-icon`
11701
11705
  }
11702
11706
  ),
11703
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__option-label`, children: option.label })
11707
+ option.disabled && option.disabledReason ? /* @__PURE__ */ jsxs("span", { className: `${baseClass}__option-texts`, children: [
11708
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__option-label`, children: option.label }),
11709
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__option-reason`, children: option.disabledReason })
11710
+ ] }) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__option-label`, children: option.label })
11704
11711
  ]
11705
11712
  },
11706
11713
  index
@@ -13054,36 +13061,45 @@ const Badge = ({
13054
13061
  };
13055
13062
  Badge.displayName = "LumiBadge";
13056
13063
 
13064
+ function fieldOptions(field) {
13065
+ return field.uiProps?.options;
13066
+ }
13067
+ function renderFieldValue(field, row) {
13068
+ const value = row[field.key];
13069
+ const options = fieldOptions(field);
13070
+ const opt = options?.find((o) => String(o.value) === String(value));
13071
+ if (field.moleculeType === "BADGE") {
13072
+ if (value == null || value === "") return null;
13073
+ return createElement(Badge, { color: opt?.color, children: opt?.label ?? String(value) });
13074
+ }
13075
+ if (field.moleculeType === "INPUT_CURRENCY") {
13076
+ if (value == null || value === "") return null;
13077
+ const cp = field.uiProps ?? {};
13078
+ return createElement(CurrencyDisplay, {
13079
+ value,
13080
+ showCode: cp.showCode ?? true,
13081
+ showSymbol: cp.showSymbol ?? true,
13082
+ currency: cp.currency ?? { decimalPlaces: 0 },
13083
+ align: field.align ?? "right",
13084
+ size: cp.size ?? "md"
13085
+ });
13086
+ }
13087
+ if (field.formatValue) return field.formatValue(value, row);
13088
+ if (opt) return opt.label;
13089
+ return value;
13090
+ }
13091
+ function hasFieldValue(field, row) {
13092
+ const value = row[field.key];
13093
+ return value != null && value !== "";
13094
+ }
13095
+
13057
13096
  function mapFieldsToTableColumns(fields) {
13058
13097
  return fields.filter((field) => field.showInTable !== false).map((field) => {
13059
13098
  let width = field.columnWidth;
13060
13099
  if (field.fixed && typeof field.columnWidth === "number") {
13061
13100
  width = `${field.columnWidth}px`;
13062
13101
  }
13063
- const options = field.uiProps?.options;
13064
- const build = (row) => {
13065
- const value = row[field.key];
13066
- const opt = options?.find((o) => String(o.value) === String(value));
13067
- if (field.moleculeType === "BADGE") {
13068
- if (value == null || value === "") return null;
13069
- return createElement(Badge, { color: opt?.color, children: opt?.label ?? String(value) });
13070
- }
13071
- if (field.moleculeType === "INPUT_CURRENCY") {
13072
- if (value == null || value === "") return null;
13073
- const cp = field.uiProps ?? {};
13074
- return createElement(CurrencyDisplay, {
13075
- value,
13076
- showCode: cp.showCode ?? true,
13077
- showSymbol: cp.showSymbol ?? true,
13078
- currency: cp.currency ?? { decimalPlaces: 0 },
13079
- align: field.align ?? "right",
13080
- size: cp.size ?? "md"
13081
- });
13082
- }
13083
- if (field.formatValue) return field.formatValue(value, row);
13084
- if (opt) return opt.label;
13085
- return value;
13086
- };
13102
+ const build = (row) => renderFieldValue(field, row);
13087
13103
  return {
13088
13104
  key: field.key,
13089
13105
  title: field.label,
@@ -13114,10 +13130,201 @@ function mapFieldsToFormFields(fields, mode) {
13114
13130
  }));
13115
13131
  }
13116
13132
 
13133
+ const CLASS$e = `${COMPONENT_PREFIX}field-value`;
13134
+ const FieldValue = ({
13135
+ label,
13136
+ children,
13137
+ layout = "inline",
13138
+ className
13139
+ }) => /* @__PURE__ */ jsxs("div", { className: [CLASS$e, `${CLASS$e}--${layout}`, className].filter(Boolean).join(" "), children: [
13140
+ /* @__PURE__ */ jsx("span", { className: `${CLASS$e}__label`, children: label }),
13141
+ /* @__PURE__ */ jsx("span", { className: `${CLASS$e}__value`, children })
13142
+ ] });
13143
+ FieldValue.displayName = "FieldValue";
13144
+
13145
+ const CLASS$d = `${COMPONENT_PREFIX}record-card`;
13146
+ const RecordCard = ({
13147
+ title,
13148
+ summary,
13149
+ children,
13150
+ actions,
13151
+ open = false,
13152
+ onToggle,
13153
+ ariaLabel,
13154
+ className
13155
+ }) => {
13156
+ const cuerpoId = useId();
13157
+ return /* @__PURE__ */ jsxs("div", { className: [CLASS$d, open ? `${CLASS$d}--open` : "", className].filter(Boolean).join(" "), children: [
13158
+ /* @__PURE__ */ jsxs(
13159
+ "button",
13160
+ {
13161
+ type: "button",
13162
+ className: `${CLASS$d}__header`,
13163
+ "aria-expanded": open,
13164
+ "aria-controls": cuerpoId,
13165
+ "aria-label": ariaLabel,
13166
+ onClick: onToggle,
13167
+ children: [
13168
+ /* @__PURE__ */ jsxs("span", { className: `${CLASS$d}__heading`, children: [
13169
+ /* @__PURE__ */ jsx("span", { className: `${CLASS$d}__title`, children: title }),
13170
+ summary && /* @__PURE__ */ jsx("span", { className: `${CLASS$d}__summary`, children: summary })
13171
+ ] }),
13172
+ /* @__PURE__ */ jsx(
13173
+ Icon,
13174
+ {
13175
+ type: open ? "chevron-up" : "chevron-down",
13176
+ width: 18,
13177
+ height: 18,
13178
+ className: `${CLASS$d}__chevron`,
13179
+ "aria-hidden": true
13180
+ }
13181
+ )
13182
+ ]
13183
+ }
13184
+ ),
13185
+ open && /* @__PURE__ */ jsxs("div", { className: `${CLASS$d}__body`, id: cuerpoId, children: [
13186
+ children,
13187
+ actions && /* @__PURE__ */ jsx("div", { className: `${CLASS$d}__actions`, children: actions })
13188
+ ] })
13189
+ ] });
13190
+ };
13191
+ RecordCard.displayName = "RecordCard";
13192
+
13193
+ const CLASS$c = `${COMPONENT_PREFIX}crud-cards`;
13194
+ function summaryFields(fields) {
13195
+ const visibles = fields.filter((f) => f.showInTable !== false);
13196
+ const marcados = visibles.filter((f) => f.summary);
13197
+ return marcados.length > 0 ? marcados : visibles.slice(0, 3);
13198
+ }
13199
+ function CardList({
13200
+ fields,
13201
+ rows,
13202
+ rowKey,
13203
+ buildOptions,
13204
+ loading = false,
13205
+ emptyText = "No hay registros"
13206
+ }) {
13207
+ const [abierta, setAbierta] = useState(null);
13208
+ const resumen = summaryFields(fields);
13209
+ const claveResumen = new Set(resumen.map((f) => f.key));
13210
+ const cuerpo = fields.filter((f) => f.showInTable !== false && !claveResumen.has(f.key));
13211
+ if (loading && rows.length === 0) {
13212
+ return /* @__PURE__ */ jsx("div", { className: `${CLASS$c}__estado`, children: "Cargando..." });
13213
+ }
13214
+ if (rows.length === 0) {
13215
+ return /* @__PURE__ */ jsx("div", { className: `${CLASS$c}__estado`, children: emptyText });
13216
+ }
13217
+ const [principal, ...secundarios] = resumen;
13218
+ return /* @__PURE__ */ jsx("div", { className: CLASS$c, children: rows.map((row) => {
13219
+ const key = rowKey(row);
13220
+ const opciones = buildOptions?.(row) ?? [];
13221
+ const titulo = principal ? renderFieldValue(principal, row) : key;
13222
+ return /* @__PURE__ */ jsx(
13223
+ RecordCard,
13224
+ {
13225
+ open: abierta === key,
13226
+ onToggle: () => setAbierta((actual) => actual === key ? null : key),
13227
+ ariaLabel: principal ? `${principal.label}: ${String(row[principal.key] ?? "")}` : void 0,
13228
+ title: titulo,
13229
+ summary: secundarios.length > 0 ? /* @__PURE__ */ jsx(Fragment, { children: secundarios.map((f) => /* @__PURE__ */ jsx(React.Fragment, { children: renderFieldValue(f, row) }, f.key)) }) : void 0,
13230
+ actions: opciones.length > 0 ? /* @__PURE__ */ jsx(Fragment, { children: opciones.map((o) => /* @__PURE__ */ jsxs(
13231
+ "button",
13232
+ {
13233
+ type: "button",
13234
+ className: [
13235
+ `${CLASS$c}__accion`,
13236
+ o.disabled && o.disabledReason ? "is-con-motivo" : ""
13237
+ ].filter(Boolean).join(" "),
13238
+ disabled: o.disabled,
13239
+ onClick: () => void o.onClick(),
13240
+ children: [
13241
+ /* @__PURE__ */ jsx("span", { children: o.label }),
13242
+ o.disabled && o.disabledReason && /* @__PURE__ */ jsx("span", { className: `${CLASS$c}__motivo`, children: o.disabledReason })
13243
+ ]
13244
+ },
13245
+ o.label
13246
+ )) }) : void 0,
13247
+ children: cuerpo.map(
13248
+ (f) => hasFieldValue(f, row) ? /* @__PURE__ */ jsx(FieldValue, { label: f.label, children: renderFieldValue(f, row) }, f.key) : null
13249
+ )
13250
+ },
13251
+ key
13252
+ );
13253
+ }) });
13254
+ }
13255
+ CardList.displayName = "CardList";
13256
+
13257
+ const CLASS$b = `${COMPONENT_PREFIX}crud-scroll-end`;
13258
+ const MARGEN = 24;
13259
+ const SELECTOR_CRUD = `.${COMPONENT_PREFIX}table-container, .${COMPONENT_PREFIX}crud-cards`;
13260
+ const ScrollToEnd = ({
13261
+ hostRef,
13262
+ selector = SELECTOR_CRUD,
13263
+ dep,
13264
+ label = "Ir al final de la lista"
13265
+ }) => {
13266
+ const [visible, setVisible] = useState(false);
13267
+ const scrollerRef = useRef(null);
13268
+ useEffect(() => {
13269
+ const host = hostRef.current;
13270
+ if (!host) return;
13271
+ const scroller = host.querySelector(selector) ?? host;
13272
+ scrollerRef.current = scroller;
13273
+ if (!scroller) {
13274
+ setVisible(false);
13275
+ return;
13276
+ }
13277
+ const revisar = () => {
13278
+ const s = scrollerRef.current;
13279
+ if (!s) return;
13280
+ setVisible(s.scrollHeight - s.clientHeight - s.scrollTop > MARGEN);
13281
+ };
13282
+ revisar();
13283
+ scroller.addEventListener("scroll", revisar, { passive: true });
13284
+ let observador;
13285
+ if (typeof ResizeObserver !== "undefined") {
13286
+ observador = new ResizeObserver(revisar);
13287
+ observador.observe(scroller);
13288
+ if (scroller.firstElementChild) observador.observe(scroller.firstElementChild);
13289
+ }
13290
+ return () => {
13291
+ scroller.removeEventListener("scroll", revisar);
13292
+ observador?.disconnect();
13293
+ };
13294
+ }, [hostRef, selector, dep]);
13295
+ const alFinal = useCallback(() => {
13296
+ const s = scrollerRef.current;
13297
+ if (!s) return;
13298
+ s.scrollTo({ top: s.scrollHeight, behavior: "smooth" });
13299
+ }, []);
13300
+ if (!visible) return null;
13301
+ return /* @__PURE__ */ jsx("button", { type: "button", className: CLASS$b, onClick: alFinal, "aria-label": label, title: label, children: /* @__PURE__ */ jsx(Icon, { type: "chevron-down", width: 18, height: 18, "aria-hidden": true }) });
13302
+ };
13303
+ ScrollToEnd.displayName = "ScrollToEnd";
13304
+
13117
13305
  function calculateTotalPages(totalRecords, limit) {
13118
13306
  return Math.ceil(totalRecords / limit);
13119
13307
  }
13120
13308
 
13309
+ const useAnchoPropio = () => {
13310
+ const ref = useRef(null);
13311
+ const [ancho, setAncho] = useState(null);
13312
+ useEffect(() => {
13313
+ const nodo = ref.current;
13314
+ if (!nodo) return;
13315
+ if (typeof ResizeObserver === "undefined") {
13316
+ setAncho(nodo.getBoundingClientRect().width);
13317
+ return;
13318
+ }
13319
+ const observador = new ResizeObserver((entradas) => {
13320
+ const w = entradas[0]?.contentRect.width;
13321
+ if (typeof w === "number") setAncho(w);
13322
+ });
13323
+ observador.observe(nodo);
13324
+ return () => observador.disconnect();
13325
+ }, []);
13326
+ return [ref, ancho];
13327
+ };
13121
13328
  const useIsMobile = () => {
13122
13329
  const [isMobile, setIsMobile] = useState(false);
13123
13330
  useEffect(() => {
@@ -13157,6 +13364,8 @@ function Crud({
13157
13364
  defaultFilters,
13158
13365
  tableHeight,
13159
13366
  tableScroll,
13367
+ layout = "auto",
13368
+ cardsBelow = 768,
13160
13369
  hidePagination = false,
13161
13370
  formDrawerWidth = "80%",
13162
13371
  formColumns = 2,
@@ -13261,6 +13470,69 @@ function Crud({
13261
13470
  }
13262
13471
  return columns;
13263
13472
  }, [renderByFields, isMobile, tableScroll?.x]);
13473
+ const buildRowOptions = useCallback((row) => {
13474
+ const menuOptions = [];
13475
+ if (rowActionTypes.includes("edit") && !disabledActions?.edit && onEdit) {
13476
+ menuOptions.push({
13477
+ label: labels?.editButton || "Editar",
13478
+ icon: "edit",
13479
+ onClick: () => {
13480
+ openFormDrawer("edit", row);
13481
+ }
13482
+ });
13483
+ }
13484
+ if (rowActionTypes.includes("delete") && !disabledActions?.delete && onDelete) {
13485
+ menuOptions.push({
13486
+ label: labels?.deleteButton || "Eliminar",
13487
+ icon: "trash",
13488
+ onClick: () => {
13489
+ openConfirmDrawer("delete", void 0, [row]);
13490
+ }
13491
+ });
13492
+ }
13493
+ if (rowActionTypes.includes("view") && !disabledActions?.view) {
13494
+ menuOptions.push({
13495
+ label: labels?.viewButton || "Ver",
13496
+ icon: "eye",
13497
+ onClick: async () => {
13498
+ if (onView) {
13499
+ await onView([row]);
13500
+ }
13501
+ openFormDrawer("view", row);
13502
+ }
13503
+ });
13504
+ }
13505
+ rowActions?.forEach((action) => {
13506
+ if (action.show && !action.show(row)) return;
13507
+ menuOptions.push({
13508
+ label: action.label,
13509
+ icon: action.icon,
13510
+ onClick: () => {
13511
+ if (action.drawerId) {
13512
+ openCustomDrawer(action.drawerId, row);
13513
+ }
13514
+ if (action.onClick) {
13515
+ action.onClick(row);
13516
+ }
13517
+ },
13518
+ disabled: action.disabled ? action.disabled(row) : false,
13519
+ // SPEC-090 T3 — una acción apagada sin motivo es indistinguible de una rota.
13520
+ disabledReason: action.disabledReason?.(row)
13521
+ });
13522
+ });
13523
+ return menuOptions;
13524
+ }, [
13525
+ rowActionTypes,
13526
+ disabledActions,
13527
+ onEdit,
13528
+ onDelete,
13529
+ onView,
13530
+ labels,
13531
+ openFormDrawer,
13532
+ openConfirmDrawer,
13533
+ openCustomDrawer,
13534
+ rowActions
13535
+ ]);
13264
13536
  const tableColumns = useMemo(() => {
13265
13537
  if (!showRowActions) return baseTableColumns;
13266
13538
  const actionsColumn = {
@@ -13272,53 +13544,7 @@ function Crud({
13272
13544
  cellType: "CHILDREN",
13273
13545
  dataSource: {
13274
13546
  build: (row) => {
13275
- const menuOptions = [];
13276
- if (rowActionTypes.includes("edit") && !disabledActions?.edit && onEdit) {
13277
- menuOptions.push({
13278
- label: labels?.editButton || "Editar",
13279
- icon: "edit",
13280
- onClick: () => {
13281
- openFormDrawer("edit", row);
13282
- }
13283
- });
13284
- }
13285
- if (rowActionTypes.includes("delete") && !disabledActions?.delete && onDelete) {
13286
- menuOptions.push({
13287
- label: labels?.deleteButton || "Eliminar",
13288
- icon: "trash",
13289
- onClick: () => {
13290
- openConfirmDrawer("delete", void 0, [row]);
13291
- }
13292
- });
13293
- }
13294
- if (rowActionTypes.includes("view") && !disabledActions?.view) {
13295
- menuOptions.push({
13296
- label: labels?.viewButton || "Ver",
13297
- icon: "eye",
13298
- onClick: async () => {
13299
- if (onView) {
13300
- await onView([row]);
13301
- }
13302
- openFormDrawer("view", row);
13303
- }
13304
- });
13305
- }
13306
- rowActions?.forEach((action) => {
13307
- if (action.show && !action.show(row)) return;
13308
- menuOptions.push({
13309
- label: action.label,
13310
- icon: action.icon,
13311
- onClick: () => {
13312
- if (action.drawerId) {
13313
- openCustomDrawer(action.drawerId, row);
13314
- }
13315
- if (action.onClick) {
13316
- action.onClick(row);
13317
- }
13318
- },
13319
- disabled: action.disabled ? action.disabled(row) : false
13320
- });
13321
- });
13547
+ const menuOptions = buildRowOptions(row);
13322
13548
  if (menuOptions.length === 0) return null;
13323
13549
  return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", width: "100%" }, children: /* @__PURE__ */ jsx(
13324
13550
  DropdownMenu,
@@ -13341,20 +13567,7 @@ function Crud({
13341
13567
  }
13342
13568
  };
13343
13569
  return [...baseTableColumns, actionsColumn];
13344
- }, [
13345
- baseTableColumns,
13346
- showRowActions,
13347
- rowActionTypes,
13348
- disabledActions,
13349
- labels,
13350
- onEdit,
13351
- onDelete,
13352
- onView,
13353
- rowActions,
13354
- openFormDrawer,
13355
- openConfirmDrawer,
13356
- openCustomDrawer
13357
- ]);
13570
+ }, [baseTableColumns, showRowActions, buildRowOptions]);
13358
13571
  const formFields = useMemo(() => {
13359
13572
  return mapFieldsToFormFields(renderByFields, formMode);
13360
13573
  }, [renderByFields, formMode]);
@@ -13551,6 +13764,10 @@ function Crud({
13551
13764
  return "\xBFEst\xE1 seguro que desea realizar esta acci\xF3n?";
13552
13765
  }
13553
13766
  };
13767
+ const [refContenedor, anchoPropio] = useAnchoPropio();
13768
+ const enTarjetas = layout === "cards" || // Mientras no se ha medido (`null`) se mantiene la tabla: pintar tarjetas y saltar a tabla un
13769
+ // instante después es el parpadeo que se acaba de quitar en otro sitio.
13770
+ layout === "auto" && anchoPropio !== null && anchoPropio < cardsBelow;
13554
13771
  const crudClasses = [
13555
13772
  `${COMPONENT_PREFIX}crud`,
13556
13773
  loading && `${COMPONENT_PREFIX}crud--loading`,
@@ -13567,24 +13784,43 @@ function Crud({
13567
13784
  showLabels: false
13568
13785
  }
13569
13786
  ) }),
13570
- !formOnly && /* @__PURE__ */ jsx("div", { className: `${COMPONENT_PREFIX}crud__table`, children: /* @__PURE__ */ jsx(
13571
- Table,
13787
+ !formOnly && /* @__PURE__ */ jsxs(
13788
+ "div",
13572
13789
  {
13573
- dataSource,
13574
- columns: tableColumns,
13575
- selectable: showActionBar && actionBarMode === "full",
13576
- selectedRowKeys: selectedKeys,
13577
- onSelectionChange: handleSelectionChange,
13578
- rowKey: getRowKey,
13579
- loading,
13580
- height: tableHeight,
13581
- scroll: tableScroll,
13582
- compact: size === "small",
13583
- bordered: true,
13584
- hoverable: true,
13585
- emptyText: labels?.emptyText
13790
+ className: `${COMPONENT_PREFIX}crud__data${enTarjetas ? ` ${COMPONENT_PREFIX}crud__data--cards` : ""}`,
13791
+ children: [
13792
+ /* @__PURE__ */ jsx("div", { className: `${COMPONENT_PREFIX}crud__table`, ref: refContenedor, children: enTarjetas ? /* @__PURE__ */ jsx(
13793
+ CardList,
13794
+ {
13795
+ fields: renderByFields,
13796
+ rows: dataSource,
13797
+ rowKey: getRowKey,
13798
+ buildOptions: showRowActions ? buildRowOptions : void 0,
13799
+ loading,
13800
+ emptyText: labels?.emptyText
13801
+ }
13802
+ ) : /* @__PURE__ */ jsx(
13803
+ Table,
13804
+ {
13805
+ dataSource,
13806
+ columns: tableColumns,
13807
+ selectable: showActionBar && actionBarMode === "full",
13808
+ selectedRowKeys: selectedKeys,
13809
+ onSelectionChange: handleSelectionChange,
13810
+ rowKey: getRowKey,
13811
+ loading,
13812
+ height: tableHeight,
13813
+ scroll: tableScroll,
13814
+ compact: size === "small",
13815
+ bordered: true,
13816
+ hoverable: true,
13817
+ emptyText: labels?.emptyText
13818
+ }
13819
+ ) }),
13820
+ /* @__PURE__ */ jsx(ScrollToEnd, { hostRef: refContenedor, dep: `${enTarjetas}:${dataSource.length}:${loading}` })
13821
+ ]
13586
13822
  }
13587
- ) }),
13823
+ ),
13588
13824
  !formOnly && !filters.all_data && !hidePagination && /* @__PURE__ */ jsx("div", { className: `${COMPONENT_PREFIX}crud__pagination`, children: /* @__PURE__ */ jsx(
13589
13825
  Pagination,
13590
13826
  {
@@ -13649,6 +13885,7 @@ function Crud({
13649
13885
  position: "right",
13650
13886
  size: "responsive",
13651
13887
  responsivePercentage: 30,
13888
+ mobileSheet: true,
13652
13889
  className: `${COMPONENT_PREFIX}crud__confirm-drawer`,
13653
13890
  children: /* @__PURE__ */ jsxs("div", { className: `${COMPONENT_PREFIX}crud__confirm-content`, children: [
13654
13891
  /* @__PURE__ */ jsx("p", { className: `${COMPONENT_PREFIX}crud__confirm-message`, children: getConfirmMessage() }),
@@ -13697,6 +13934,60 @@ function Crud({
13697
13934
  }
13698
13935
  Crud.displayName = "Crud";
13699
13936
 
13937
+ const CLASS$a = "px-chat-reactive-crud__chat-link";
13938
+ function Burbuja() {
13939
+ return /* @__PURE__ */ jsx(
13940
+ "svg",
13941
+ {
13942
+ width: 18,
13943
+ height: 18,
13944
+ viewBox: "0 0 24 24",
13945
+ fill: "none",
13946
+ stroke: "currentColor",
13947
+ strokeWidth: 2,
13948
+ strokeLinecap: "round",
13949
+ strokeLinejoin: "round",
13950
+ "aria-hidden": true,
13951
+ children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
13952
+ }
13953
+ );
13954
+ }
13955
+ const ChatLinkCell = ({
13956
+ targets,
13957
+ eventBus
13958
+ }) => {
13959
+ if (targets.length === 0) return null;
13960
+ const abrir = (id) => eventBus?.emit(CHAT_EVENT.OPEN_CHAT, { chatId: id, closed: false });
13961
+ if (targets.length === 1) {
13962
+ const t = targets[0];
13963
+ const texto = t.label ? `Abrir chat ${t.label}` : "Abrir chat";
13964
+ return /* @__PURE__ */ jsx("button", { type: "button", className: CLASS$a, "aria-label": texto, title: texto, onClick: () => abrir(t.id), children: /* @__PURE__ */ jsx(Burbuja, {}) });
13965
+ }
13966
+ return /* @__PURE__ */ jsx(
13967
+ DropdownMenu,
13968
+ {
13969
+ options: targets.map((t) => ({
13970
+ label: t.label ?? "Chat",
13971
+ icon: "message-square",
13972
+ onClick: () => abrir(t.id)
13973
+ })),
13974
+ trigger: /* @__PURE__ */ jsx(
13975
+ "button",
13976
+ {
13977
+ type: "button",
13978
+ className: CLASS$a,
13979
+ "aria-label": `Abrir chat (${targets.length} conversaciones)`,
13980
+ title: "Abrir chat",
13981
+ children: /* @__PURE__ */ jsx(Burbuja, {})
13982
+ }
13983
+ ),
13984
+ position: "bottom-left",
13985
+ usePortal: true
13986
+ }
13987
+ );
13988
+ };
13989
+ ChatLinkCell.displayName = "ChatLinkCell";
13990
+
13700
13991
  function buildCrudYupSchema(fields) {
13701
13992
  const shape = {};
13702
13993
  for (const f of fields) {
@@ -13777,8 +14068,21 @@ function unwrap$3(raw) {
13777
14068
  }
13778
14069
  return raw;
13779
14070
  }
13780
- async function loadOptions(eventBus, ep) {
13781
- const raw = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, ep.body ?? {});
14071
+ function resolveBody(body, ctx) {
14072
+ if (!body) return {};
14073
+ if (!ctx) return body;
14074
+ return Object.fromEntries(
14075
+ Object.entries(body).map(([k, v]) => {
14076
+ if (typeof v === "string" && v.startsWith("$")) {
14077
+ const key = v.slice(1);
14078
+ return [k, key in ctx ? ctx[key] : v];
14079
+ }
14080
+ return [k, v];
14081
+ })
14082
+ );
14083
+ }
14084
+ async function loadOptions(eventBus, ep, ctx) {
14085
+ const raw = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, resolveBody(ep.body, ctx));
13782
14086
  let data = unwrap$3(raw);
13783
14087
  if (ep.response_path && data && typeof data === "object" && !Array.isArray(data)) {
13784
14088
  data = data[ep.response_path];
@@ -13790,6 +14094,30 @@ async function loadOptions(eventBus, ep) {
13790
14094
  }));
13791
14095
  }
13792
14096
 
14097
+ const esTextoUtil = (v) => typeof v === "string" && v.trim().length > 0;
14098
+ const buscarEnCuerpo = (cuerpo) => {
14099
+ if (!cuerpo || typeof cuerpo !== "object") return null;
14100
+ const c = cuerpo;
14101
+ const detalle = c.detail;
14102
+ if (esTextoUtil(detalle)) return detalle;
14103
+ if (detalle && typeof detalle === "object") {
14104
+ const d = detalle;
14105
+ if (esTextoUtil(d.message)) return d.message;
14106
+ }
14107
+ if (esTextoUtil(c.message)) return c.message;
14108
+ return null;
14109
+ };
14110
+ const ES_JERGA_HTTP = /^Request failed with status code \d+$/i;
14111
+ const mensajeDeError = (e, respaldo) => {
14112
+ const err = e;
14113
+ const delServidor = buscarEnCuerpo(err?.response?.data);
14114
+ if (delServidor) return delServidor;
14115
+ if (esTextoUtil(err?.message) && !ES_JERGA_HTTP.test(err.message.trim())) {
14116
+ return err.message;
14117
+ }
14118
+ return respaldo;
14119
+ };
14120
+
13793
14121
  const REFRESH_INTERVAL_MS = 2e4;
13794
14122
  function unwrap$2(raw) {
13795
14123
  if (raw && typeof raw === "object" && "response" in raw) {
@@ -13830,7 +14158,7 @@ function useReactiveCrud(config, eventBus) {
13830
14158
  const loadList = useCallback(async () => {
13831
14159
  if (!eventBus) return;
13832
14160
  const myReq = ++reqIdRef.current;
13833
- setState((s) => ({ ...s, loading: true, error: null }));
14161
+ setState((s) => s.rows.length === 0 ? { ...s, loading: true, error: null } : { ...s, error: null });
13834
14162
  try {
13835
14163
  const { service, url, method } = config.data.list;
13836
14164
  const raw = await fetchViaEventBus(eventBus, service, url, method, {
@@ -13854,7 +14182,7 @@ function useReactiveCrud(config, eventBus) {
13854
14182
  if (myReq === reqIdRef.current) setState({ rows, total, loading: false, error: null });
13855
14183
  } catch (e) {
13856
14184
  if (myReq === reqIdRef.current) {
13857
- setState((s) => ({ ...s, loading: false, error: e instanceof Error ? e.message : "Error al cargar" }));
14185
+ setState((s) => ({ ...s, loading: false, error: mensajeDeError(e, "Error al cargar") }));
13858
14186
  }
13859
14187
  }
13860
14188
  }, [eventBus, config.data.list, skip, pageSize, filters]);
@@ -13898,7 +14226,7 @@ function useReactiveCrud(config, eventBus) {
13898
14226
  }
13899
14227
  await loadList();
13900
14228
  } catch (e) {
13901
- setActionError(e instanceof Error ? e.message : "La acci\xF3n no se pudo completar");
14229
+ setActionError(mensajeDeError(e, "La acci\xF3n no se pudo completar"));
13902
14230
  throw e;
13903
14231
  }
13904
14232
  },
@@ -14081,7 +14409,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
14081
14409
  void Promise.all(
14082
14410
  toLoad.map(async (f) => {
14083
14411
  try {
14084
- const opts = await loadOptions(eventBus, f.optionsEndpoint);
14412
+ const opts = await loadOptions(eventBus, f.optionsEndpoint, { ...row, id: row[idField] });
14085
14413
  return [f.name, opts];
14086
14414
  } catch {
14087
14415
  return [f.name, []];
@@ -14159,7 +14487,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
14159
14487
  await onExecute(ctx);
14160
14488
  onClose();
14161
14489
  } catch (e) {
14162
- setFormError(e instanceof Error ? e.message : "La acci\xF3n no se pudo completar");
14490
+ setFormError(mensajeDeError(e, "La acci\xF3n no se pudo completar"));
14163
14491
  } finally {
14164
14492
  setSubmitting(false);
14165
14493
  }
@@ -14770,6 +15098,7 @@ function unwrap$1(raw) {
14770
15098
  }
14771
15099
  return raw;
14772
15100
  }
15101
+ const DETALLE_OCULTOS = /* @__PURE__ */ new Set(["LOCATION", "CHAT_LINK", "FILE"]);
14773
15102
  function DetailDrawer({ row, config, eventBus, onClose, loadedOptions }) {
14774
15103
  const detail = config.detail;
14775
15104
  const [files, setFiles] = useState([]);
@@ -14868,7 +15197,7 @@ function DetailDrawer({ row, config, eventBus, onClose, loadedOptions }) {
14868
15197
  }
14869
15198
  };
14870
15199
  return /* @__PURE__ */ jsx(Drawer$1, { isOpen: true, onClose, title, placement: "right", children: /* @__PURE__ */ jsxs("div", { className: "px-chat-reactive-crud-detail", children: [
14871
- /* @__PURE__ */ jsx("dl", { className: "px-chat-reactive-crud-detail__fields", children: config.fields.map((f) => {
15200
+ /* @__PURE__ */ jsx("dl", { className: "px-chat-reactive-crud-detail__fields", children: config.fields.filter((f) => !DETALLE_OCULTOS.has(f.moleculeType)).map((f) => {
14872
15201
  const { node, copyText } = renderField(f);
14873
15202
  return /* @__PURE__ */ jsxs("div", { className: "px-chat-reactive-crud-detail__row", children: [
14874
15203
  /* @__PURE__ */ jsx("dt", { className: "px-chat-reactive-crud-detail__label", children: f.label }),
@@ -15530,35 +15859,13 @@ const LocationField = ({
15530
15859
  };
15531
15860
  LocationField.displayName = "LocationField";
15532
15861
 
15533
- function chatLinkButton(id, eventBus, label) {
15534
- return createElement(
15535
- "button",
15536
- {
15537
- key: label ?? id,
15538
- type: "button",
15539
- className: "px-chat-reactive-crud__chat-link",
15540
- "aria-label": label ? `Abrir chat ${label}` : "Abrir chat",
15541
- title: label ?? "Abrir chat",
15542
- onClick: () => eventBus?.emit(CHAT_EVENT.OPEN_CHAT, { chatId: id, closed: false })
15543
- },
15544
- createElement(
15545
- "svg",
15546
- { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
15547
- createElement("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
15548
- ),
15549
- label ? createElement("span", { className: "px-chat-reactive-crud__chat-link-label" }, label) : null
15550
- );
15551
- }
15552
15862
  function chatLinkFormatter(field, eventBus) {
15553
15863
  const sources = field.chats;
15554
15864
  return (value, row) => {
15555
15865
  const r = row;
15556
- if (sources && sources.length) {
15557
- const btns = sources.map((s) => ({ id: r ? r[s.field] : void 0, label: s.label })).filter((s) => s.id != null && s.id !== "").map((s) => chatLinkButton(String(s.id), eventBus, s.label));
15558
- return btns.length ? createElement("div", { className: "px-chat-reactive-crud__chat-links" }, btns) : null;
15559
- }
15560
- const id = value != null && value !== "" ? String(value) : "";
15561
- return id ? chatLinkButton(id, eventBus) : null;
15866
+ const targets = sources?.length ? sources.map((s) => ({ id: r ? r[s.field] : void 0, label: s.label })).filter((s) => s.id != null && s.id !== "").map((s) => ({ id: String(s.id), label: s.label })) : value != null && value !== "" ? [{ id: String(value) }] : [];
15867
+ if (!targets.length) return null;
15868
+ return createElement(ChatLinkCell, { targets, eventBus });
15562
15869
  };
15563
15870
  }
15564
15871
  function toRenderField(f, loaded, eventBus, mapsApiKey) {
@@ -15597,6 +15904,8 @@ function toRenderField(f, loaded, eventBus, mapsApiKey) {
15597
15904
  // FILE/CHAT_LINK solo son columnas → INPUT (el render lo define formatValue/build).
15598
15905
  moleculeType: f.moleculeType === "FILE" || isChatLink ? "INPUT" : f.moleculeType,
15599
15906
  showInTable: f.showInTable,
15907
+ summary: f.summary,
15908
+ // SPEC-093
15600
15909
  showInForm: f.showInForm,
15601
15910
  showInModes: f.showInModes,
15602
15911
  required: f.required,
@@ -15681,6 +15990,10 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
15681
15990
  icon: a.icon,
15682
15991
  show: (row) => matchesVisibleWhen(a, row),
15683
15992
  disabled: (row) => matchesDisabledWhen(a, row),
15993
+ // SPEC-090 T3 — el `disabledReason` de la config ya existía, pero solo lo pintaba el panel
15994
+ // del chat; en la tabla el botón se apagaba mudo. Afecta también al de la inspección
15995
+ // («Falta el comprobante de pago del cliente», SPEC-066 T7), que tampoco se veía aquí.
15996
+ disabledReason: (row) => matchesDisabledWhen(a, row) ? a.disabledReason : void 0,
15684
15997
  onClick: (row) => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] })
15685
15998
  }));
15686
15999
  if (config.detail) {
@@ -15689,6 +16002,7 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
15689
16002
  icon: "eye",
15690
16003
  show: () => true,
15691
16004
  disabled: () => false,
16005
+ disabledReason: () => void 0,
15692
16006
  onClick: (row) => setDetailRow(row)
15693
16007
  });
15694
16008
  }
@@ -15721,6 +16035,8 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
15721
16035
  loading: crud.loading,
15722
16036
  filters: { skip: crud.skip, limit: crud.pageSize, all_data: false, filters: crud.filters },
15723
16037
  tableScroll: config.tableScroll,
16038
+ layout: config.layout,
16039
+ cardsBelow: config.cardsBelow,
15724
16040
  validationSchema,
15725
16041
  getRowKey: (row) => String(row[config.idField]),
15726
16042
  onCreate: config.data.create ? async (rows) => {
@@ -15990,12 +16306,18 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
15990
16306
  "button",
15991
16307
  {
15992
16308
  type: "button",
15993
- className: `px-chat-crud-item__action${a.intent === "danger" ? " is-danger" : ""}`,
16309
+ className: [
16310
+ "px-chat-crud-item__action",
16311
+ a.intent === "danger" ? "is-danger" : "",
16312
+ disabled && a.disabledReason ? "is-con-motivo" : ""
16313
+ ].filter(Boolean).join(" "),
15994
16314
  disabled,
15995
- title: disabled ? a.disabledReason : void 0,
15996
16315
  onClick: () => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] }),
15997
16316
  children: [
15998
- /* @__PURE__ */ jsx("span", { children: a.label }),
16317
+ /* @__PURE__ */ jsxs("span", { className: "px-chat-crud-item__action-texts", children: [
16318
+ /* @__PURE__ */ jsx("span", { children: a.label }),
16319
+ disabled && a.disabledReason && /* @__PURE__ */ jsx("span", { className: "px-chat-crud-item__action-reason", children: a.disabledReason })
16320
+ ] }),
15999
16321
  done && /* @__PURE__ */ jsx(Icon$1, { type: "check-circle", width: 18, height: 18, className: "px-chat-crud-item__done", "aria-label": "Hecho" })
16000
16322
  ]
16001
16323
  },
@@ -16630,6 +16952,7 @@ function ChatNavigator({ config, eventBus }) {
16630
16952
  useEffect(() => {
16631
16953
  void load();
16632
16954
  }, [load]);
16955
+ const listaRef = useRef(null);
16633
16956
  const loadRef = useRef(load);
16634
16957
  loadRef.current = load;
16635
16958
  useEffect(() => {
@@ -16677,8 +17000,6 @@ function ChatNavigator({ config, eventBus }) {
16677
17000
  };
16678
17001
  const page = Math.floor(skip / pageSize) + 1;
16679
17002
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
16680
- const canPrev = skip > 0;
16681
- const canNext = skip + pageSize < total;
16682
17003
  return /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator", children: [
16683
17004
  config.search && /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__search", children: [
16684
17005
  /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "px-chat-chat-navigator__search-icon", children: [
@@ -16758,143 +17079,126 @@ function ChatNavigator({ config, eventBus }) {
16758
17079
  loading && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__state", children: "Cargando conversaciones\u2026" }),
16759
17080
  error && !loading && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__state px-chat-chat-navigator__state--error", children: error }),
16760
17081
  !loading && !error && items.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__state", children: labels.emptyText ?? "Sin conversaciones" }),
16761
- /* @__PURE__ */ jsx("ul", { className: "px-chat-chat-navigator__list", children: items.map((item) => {
16762
- const isRenaming = renamingId === item.id;
16763
- const displayTitle = item.title?.trim() || fallbackTitle(item);
16764
- return /* @__PURE__ */ jsxs("li", { className: "px-chat-chat-navigator__item", children: [
16765
- isRenaming ? /* @__PURE__ */ jsx(
16766
- "form",
16767
- {
16768
- className: "px-chat-chat-navigator__rename",
16769
- onSubmit: (e) => {
16770
- e.preventDefault();
16771
- void submitRename(item.id);
16772
- },
16773
- children: /* @__PURE__ */ jsx(
16774
- "input",
16775
- {
16776
- autoFocus: true,
16777
- className: "px-chat-chat-navigator__rename-input",
16778
- value: renameValue,
16779
- onChange: (e) => setRenameValue(e.target.value),
16780
- onBlur: () => void submitRename(item.id),
16781
- "aria-label": labels.renameTitle ?? "Nuevo t\xEDtulo"
16782
- }
16783
- )
16784
- }
16785
- ) : /* @__PURE__ */ jsxs("button", { type: "button", className: "px-chat-chat-navigator__open", onClick: () => open(item), children: [
16786
- /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__title", children: [
16787
- displayTitle,
16788
- item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsx(
16789
- "span",
16790
- {
16791
- className: "px-chat-chat-navigator__badge-count",
16792
- "aria-label": `${item.unread_proactive_count} mensajes nuevos sin leer`,
16793
- title: `${item.unread_proactive_count} sin leer`,
16794
- children: item.unread_proactive_count >= 100 ? "100+" : item.unread_proactive_count
16795
- }
16796
- ) : null
16797
- ] }),
16798
- /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__meta", children: [
16799
- item.agent_name ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
16800
- /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__time", children: relativeTime(item.updated_date) }),
16801
- (() => {
16802
- const opt = item.rol_code ? (config.originActorOptions ?? []).find((o) => o.value === item.rol_code) : void 0;
16803
- if (!opt) return null;
16804
- const c = opt.color;
16805
- const nameField = config.originActorNameField;
16806
- const allyName = nameField ? item[nameField] : void 0;
16807
- const label = allyName ? String(allyName) : opt.label;
16808
- return /* @__PURE__ */ jsx(
16809
- "span",
16810
- {
16811
- className: "px-chat-chat-navigator__origin-badge",
16812
- style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
16813
- title: label,
16814
- children: label
16815
- }
16816
- );
16817
- })(),
16818
- (() => {
16819
- const field = config.tagField;
16820
- if (!field) return null;
16821
- const value = item[field];
16822
- if (!value) return null;
16823
- const opt = (config.tagOptions ?? []).find((o) => o.value === String(value));
16824
- if (!opt) return null;
16825
- const c = opt.color;
16826
- return /* @__PURE__ */ jsx(
16827
- "span",
17082
+ /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__list-wrap", ref: listaRef, children: [
17083
+ /* @__PURE__ */ jsx("ul", { className: "px-chat-chat-navigator__list", children: items.map((item) => {
17084
+ const isRenaming = renamingId === item.id;
17085
+ const displayTitle = item.title?.trim() || fallbackTitle(item);
17086
+ return /* @__PURE__ */ jsxs("li", { className: "px-chat-chat-navigator__item", children: [
17087
+ isRenaming ? /* @__PURE__ */ jsx(
17088
+ "form",
17089
+ {
17090
+ className: "px-chat-chat-navigator__rename",
17091
+ onSubmit: (e) => {
17092
+ e.preventDefault();
17093
+ void submitRename(item.id);
17094
+ },
17095
+ children: /* @__PURE__ */ jsx(
17096
+ "input",
16828
17097
  {
16829
- className: "px-chat-chat-navigator__origin-badge",
16830
- style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
16831
- title: opt.label,
16832
- children: opt.label
17098
+ autoFocus: true,
17099
+ className: "px-chat-chat-navigator__rename-input",
17100
+ value: renameValue,
17101
+ onChange: (e) => setRenameValue(e.target.value),
17102
+ onBlur: () => void submitRename(item.id),
17103
+ "aria-label": labels.renameTitle ?? "Nuevo t\xEDtulo"
16833
17104
  }
16834
- );
16835
- })(),
16836
- item.service_status ? (() => {
16837
- const opt = (config.serviceStatusOptions ?? []).find(
16838
- (o) => o.value === item.service_status
16839
- );
16840
- const c = opt?.color;
16841
- return /* @__PURE__ */ jsx(
17105
+ )
17106
+ }
17107
+ ) : /* @__PURE__ */ jsxs("button", { type: "button", className: "px-chat-chat-navigator__open", onClick: () => open(item), children: [
17108
+ /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__title", children: [
17109
+ displayTitle,
17110
+ item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsx(
16842
17111
  "span",
16843
17112
  {
16844
- className: "px-chat-chat-navigator__service-badge",
16845
- style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
16846
- children: opt?.label ?? item.service_status
17113
+ className: "px-chat-chat-navigator__badge-count",
17114
+ "aria-label": `${item.unread_proactive_count} mensajes nuevos sin leer`,
17115
+ title: `${item.unread_proactive_count} sin leer`,
17116
+ children: item.unread_proactive_count >= 100 ? "100+" : item.unread_proactive_count
16847
17117
  }
16848
- );
16849
- })() : null,
16850
- item.closed ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__badge", children: labels.closedBadge ?? "Cerrado" }) : null
16851
- ] })
16852
- ] }),
16853
- config.data.rename && !isRenaming && /* @__PURE__ */ jsx(
16854
- "button",
16855
- {
16856
- type: "button",
16857
- className: "px-chat-chat-navigator__rename-btn",
16858
- "aria-label": "Renombrar",
16859
- title: "Renombrar",
16860
- onClick: () => startRename(item),
16861
- children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16862
- /* @__PURE__ */ jsx("path", { d: "M12 20h9" }),
16863
- /* @__PURE__ */ jsx("path", { d: "M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" })
17118
+ ) : null
17119
+ ] }),
17120
+ /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__meta", children: [
17121
+ item.agent_name ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
17122
+ /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__time", children: relativeTime(item.updated_date) }),
17123
+ (() => {
17124
+ const opt = item.rol_code ? (config.originActorOptions ?? []).find((o) => o.value === item.rol_code) : void 0;
17125
+ if (!opt) return null;
17126
+ const c = opt.color;
17127
+ const nameField = config.originActorNameField;
17128
+ const allyName = nameField ? item[nameField] : void 0;
17129
+ const label = allyName ? String(allyName) : opt.label;
17130
+ return /* @__PURE__ */ jsx(
17131
+ "span",
17132
+ {
17133
+ className: "px-chat-chat-navigator__origin-badge",
17134
+ style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
17135
+ title: label,
17136
+ children: label
17137
+ }
17138
+ );
17139
+ })(),
17140
+ (() => {
17141
+ const field = config.tagField;
17142
+ if (!field) return null;
17143
+ const value = item[field];
17144
+ if (!value) return null;
17145
+ const opt = (config.tagOptions ?? []).find((o) => o.value === String(value));
17146
+ if (!opt) return null;
17147
+ const c = opt.color;
17148
+ return /* @__PURE__ */ jsx(
17149
+ "span",
17150
+ {
17151
+ className: "px-chat-chat-navigator__origin-badge",
17152
+ style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
17153
+ title: opt.label,
17154
+ children: opt.label
17155
+ }
17156
+ );
17157
+ })(),
17158
+ item.service_status ? (() => {
17159
+ const opt = (config.serviceStatusOptions ?? []).find(
17160
+ (o) => o.value === item.service_status
17161
+ );
17162
+ const c = opt?.color;
17163
+ return /* @__PURE__ */ jsx(
17164
+ "span",
17165
+ {
17166
+ className: "px-chat-chat-navigator__service-badge",
17167
+ style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
17168
+ children: opt?.label ?? item.service_status
17169
+ }
17170
+ );
17171
+ })() : null,
17172
+ item.closed ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__badge", children: labels.closedBadge ?? "Cerrado" }) : null
16864
17173
  ] })
16865
- }
16866
- )
16867
- ] }, item.id);
16868
- }) }),
16869
- total > pageSize && /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__pager", children: [
16870
- /* @__PURE__ */ jsx(
16871
- "button",
16872
- {
16873
- type: "button",
16874
- className: "px-chat-chat-navigator__pager-btn",
16875
- disabled: !canPrev,
16876
- onClick: () => setSkip(Math.max(0, skip - pageSize)),
16877
- "aria-label": "P\xE1gina anterior",
16878
- children: "\u2039"
16879
- }
16880
- ),
16881
- /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__pager-info", children: [
16882
- page,
16883
- " / ",
16884
- totalPages
16885
- ] }),
16886
- /* @__PURE__ */ jsx(
16887
- "button",
16888
- {
16889
- type: "button",
16890
- className: "px-chat-chat-navigator__pager-btn",
16891
- disabled: !canNext,
16892
- onClick: () => setSkip(skip + pageSize),
16893
- "aria-label": "P\xE1gina siguiente",
16894
- children: "\u203A"
16895
- }
16896
- )
17174
+ ] }),
17175
+ config.data.rename && !isRenaming && /* @__PURE__ */ jsx(
17176
+ "button",
17177
+ {
17178
+ type: "button",
17179
+ className: "px-chat-chat-navigator__rename-btn",
17180
+ "aria-label": "Renombrar",
17181
+ title: "Renombrar",
17182
+ onClick: () => startRename(item),
17183
+ children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
17184
+ /* @__PURE__ */ jsx("path", { d: "M12 20h9" }),
17185
+ /* @__PURE__ */ jsx("path", { d: "M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" })
17186
+ ] })
17187
+ }
17188
+ )
17189
+ ] }, item.id);
17190
+ }) }),
17191
+ /* @__PURE__ */ jsx(ScrollToEnd, { hostRef: listaRef, selector: ".px-chat-chat-navigator__list", dep: `${items.length}:${loading}` })
16897
17192
  ] }),
17193
+ total > pageSize && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__pager", children: /* @__PURE__ */ jsx(
17194
+ Pagination,
17195
+ {
17196
+ current: page,
17197
+ total: totalPages,
17198
+ onChange: (p) => setSkip((p - 1) * pageSize),
17199
+ showTotal: false
17200
+ }
17201
+ ) }),
16898
17202
  config.advancedFilters?.length ? /* @__PURE__ */ jsx(Drawer$1, { isOpen: advOpen, onClose: () => setAdvOpen(false), title: "Filtros avanzados", children: /* @__PURE__ */ jsx(
16899
17203
  Filter,
16900
17204
  {
@@ -24237,4 +24541,4 @@ function buildBreadcrumbPath(items, targetKey, currentPath = []) {
24237
24541
  return [];
24238
24542
  }
24239
24543
 
24240
- export { BottomSheet, CHAT_CONFIG_DEFAULTS, CHAT_EVENT, ICON_TYPES as CHAT_ICON_TYPES, Chat, AssistantMessage as ChatAssistantMessage, AssistantThinkingIndicator as ChatAssistantThinkingIndicator, AttachmentPreview as ChatAttachmentPreview, Avatar as ChatAvatar, Button$1 as ChatButton, ChatConfigProvider, DateDivider as ChatDateDivider, FileViewer as ChatFileViewer, ChatFloatingActions, FullscreenViewer as ChatFullscreenViewer, ChatHeader as ChatHeaderComponent, Icon$1 as ChatIcon, Input$2 as ChatInput, ChatInput as ChatInputComponent, Link as ChatLink, Loader$1 as ChatLoader, Logo as ChatLogo, MessageStatus as ChatMessageStatus, Modal as ChatModal, ChatNavigator, Select$2 as ChatSelect, SelectMenu as ChatSelectMenu, StructuredContent as ChatStructuredContent, Text as ChatText, Textarea as ChatTextarea, UserAttachments as ChatUserAttachments, UserMessage as ChatUserMessage, ChatWidget, FileField, BADGE_VARIANTS as LUMI_BADGE_VARIANTS, DEFAULT_TITLES as LUMI_DEFAULT_TITLES, LUMI_ICON_TYPES, LiveIndicator, LocationField, ActionBar as LumiActionBar, Badge as LumiBadge, Base as LumiBase, BaseNavbar as LumiBaseNavbar, BaseWorkspace as LumiBaseWorkspace, Breadcrumb as LumiBreadcrumb, BreadcrumbItem as LumiBreadcrumbItem, BreadcrumbSeparator as LumiBreadcrumbSeparator, Button as LumiButton, Calendar as LumiCalendar, Card as LumiCard, Checkbox$1 as LumiCheckbox, Crud as LumiCrud, CurrencyDisplay as LumiCurrencyDisplay, Dashboard as LumiDashboard, DashboardNavbar as LumiDashboardNavbar, DashboardSidebar as LumiDashboardSidebar, DashboardWorkspace as LumiDashboardWorkspace, DateDisplay as LumiDateDisplay, DateInput$1 as LumiDateInput, Drawer as LumiDrawer, DropdownMenu as LumiDropdownMenu, DynamicForm as LumiDynamicForm, Filter as LumiFilter, FilterRow as LumiFilterRow, Checkbox as LumiFormCheckbox, DateInput as LumiFormDateInput, FormDrawer as LumiFormDrawer, Input as LumiFormInput, Select as LumiFormSelect, TimeInput as LumiFormTimeInput, Icon as LumiIcon, Input$1 as LumiInput, Loader as LumiLoader, Menu as LumiMenu, MenuItem as LumiMenuItem, MenuItemGroup as LumiMenuItemGroup, Notification as LumiNotification, Pagination as LumiPagination, Popover as LumiPopover, QRCode as LumiQRCode, Select$1 as LumiSelect, StepIndicator as LumiStepIndicator, Stepper as LumiStepper, SubMenu as LumiSubMenu, Table as LumiTable, Tabs as LumiTabs, TextArea as LumiTextArea, TimeInput$1 as LumiTimeInput, Tooltip as LumiTooltip, MapView, ReactiveCrud, ServiceShortcuts, ServicesMap, SessionExpiryPrompt, UserTools, chatEventBus, createDeepPropertySubscriber, createEventBus, createStore, createStoreMerger, deepMerge, getLastFleetSnapshot, setLastFleetSnapshot, useChatConfig };
24544
+ export { BottomSheet, CHAT_CONFIG_DEFAULTS, CHAT_EVENT, ICON_TYPES as CHAT_ICON_TYPES, Chat, AssistantMessage as ChatAssistantMessage, AssistantThinkingIndicator as ChatAssistantThinkingIndicator, AttachmentPreview as ChatAttachmentPreview, Avatar as ChatAvatar, Button$1 as ChatButton, ChatConfigProvider, DateDivider as ChatDateDivider, FileViewer as ChatFileViewer, ChatFloatingActions, FullscreenViewer as ChatFullscreenViewer, ChatHeader as ChatHeaderComponent, Icon$1 as ChatIcon, Input$2 as ChatInput, ChatInput as ChatInputComponent, Link as ChatLink, Loader$1 as ChatLoader, Logo as ChatLogo, MessageStatus as ChatMessageStatus, Modal as ChatModal, ChatNavigator, Select$2 as ChatSelect, SelectMenu as ChatSelectMenu, StructuredContent as ChatStructuredContent, Text as ChatText, Textarea as ChatTextarea, UserAttachments as ChatUserAttachments, UserMessage as ChatUserMessage, ChatWidget, FileField, BADGE_VARIANTS as LUMI_BADGE_VARIANTS, DEFAULT_TITLES as LUMI_DEFAULT_TITLES, LUMI_ICON_TYPES, LiveIndicator, LocationField, ActionBar as LumiActionBar, Badge as LumiBadge, Base as LumiBase, BaseNavbar as LumiBaseNavbar, BaseWorkspace as LumiBaseWorkspace, Breadcrumb as LumiBreadcrumb, BreadcrumbItem as LumiBreadcrumbItem, BreadcrumbSeparator as LumiBreadcrumbSeparator, Button as LumiButton, Calendar as LumiCalendar, Card as LumiCard, Checkbox$1 as LumiCheckbox, Crud as LumiCrud, CurrencyDisplay as LumiCurrencyDisplay, Dashboard as LumiDashboard, DashboardNavbar as LumiDashboardNavbar, DashboardSidebar as LumiDashboardSidebar, DashboardWorkspace as LumiDashboardWorkspace, DateDisplay as LumiDateDisplay, DateInput$1 as LumiDateInput, Drawer as LumiDrawer, DropdownMenu as LumiDropdownMenu, DynamicForm as LumiDynamicForm, FieldValue as LumiFieldValue, Filter as LumiFilter, FilterRow as LumiFilterRow, Checkbox as LumiFormCheckbox, DateInput as LumiFormDateInput, FormDrawer as LumiFormDrawer, Input as LumiFormInput, Select as LumiFormSelect, TimeInput as LumiFormTimeInput, Icon as LumiIcon, Input$1 as LumiInput, Loader as LumiLoader, Menu as LumiMenu, MenuItem as LumiMenuItem, MenuItemGroup as LumiMenuItemGroup, Notification as LumiNotification, Pagination as LumiPagination, Popover as LumiPopover, QRCode as LumiQRCode, RecordCard as LumiRecordCard, Select$1 as LumiSelect, StepIndicator as LumiStepIndicator, Stepper as LumiStepper, SubMenu as LumiSubMenu, Table as LumiTable, Tabs as LumiTabs, TextArea as LumiTextArea, TimeInput$1 as LumiTimeInput, Tooltip as LumiTooltip, MapView, ReactiveCrud, ServiceShortcuts, ServicesMap, SessionExpiryPrompt, UserTools, chatEventBus, createDeepPropertySubscriber, createEventBus, createStore, createStoreMerger, deepMerge, getLastFleetSnapshot, setLastFleetSnapshot, useChatConfig };