@asteby/metacore-runtime-react 49.3.1 → 49.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/api-context.d.ts +9 -1
  3. package/dist/api-context.d.ts.map +1 -1
  4. package/dist/api-context.js +6 -4
  5. package/dist/business/document-editor-panels.d.ts +2 -1
  6. package/dist/business/document-editor-panels.d.ts.map +1 -1
  7. package/dist/business/document-editor-panels.js +6 -6
  8. package/dist/business/document-editor.d.ts.map +1 -1
  9. package/dist/business/document-editor.js +23 -9
  10. package/dist/business/index.d.ts +1 -0
  11. package/dist/business/index.d.ts.map +1 -1
  12. package/dist/business/index.js +1 -0
  13. package/dist/business/line-items-editor.d.ts +6 -1
  14. package/dist/business/line-items-editor.d.ts.map +1 -1
  15. package/dist/business/line-items-editor.js +109 -83
  16. package/dist/business/line-product-cell.d.ts +32 -0
  17. package/dist/business/line-product-cell.d.ts.map +1 -0
  18. package/dist/business/line-product-cell.js +100 -0
  19. package/dist/dynamic-select-field.d.ts +2 -1
  20. package/dist/dynamic-select-field.d.ts.map +1 -1
  21. package/dist/dynamic-select-field.js +49 -19
  22. package/dist/dynamic-table.d.ts +31 -1
  23. package/dist/dynamic-table.d.ts.map +1 -1
  24. package/dist/dynamic-table.js +37 -4
  25. package/dist/entity-select.d.ts.map +1 -1
  26. package/dist/entity-select.js +13 -11
  27. package/dist/field-grid.d.ts +7 -1
  28. package/dist/field-grid.d.ts.map +1 -1
  29. package/dist/field-grid.js +4 -2
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -0
  33. package/dist/query-batch.d.ts.map +1 -1
  34. package/dist/query-batch.js +17 -1
  35. package/dist/record-picker-actions.d.ts +27 -0
  36. package/dist/record-picker-actions.d.ts.map +1 -0
  37. package/dist/record-picker-actions.js +32 -0
  38. package/package.json +1 -1
  39. package/src/__tests__/api-batch-opt-out.test.tsx +83 -0
  40. package/src/__tests__/document-editor-inline-selects.test.tsx +202 -0
  41. package/src/__tests__/dynamic-table-bulk-actions.test.tsx +140 -0
  42. package/src/__tests__/dynamic-table-load-error.test.tsx +97 -0
  43. package/src/api-context.tsx +19 -4
  44. package/src/business/document-editor-panels.tsx +13 -10
  45. package/src/business/document-editor.tsx +72 -20
  46. package/src/business/index.ts +1 -0
  47. package/src/business/line-items-editor.tsx +229 -174
  48. package/src/business/line-product-cell.tsx +219 -0
  49. package/src/dynamic-select-field.tsx +51 -22
  50. package/src/dynamic-table.tsx +91 -4
  51. package/src/entity-select.tsx +17 -39
  52. package/src/field-grid.tsx +15 -1
  53. package/src/index.ts +2 -0
  54. package/src/query-batch.ts +18 -1
  55. package/src/record-picker-actions.tsx +81 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 49.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2fca21f: `ApiProvider` acepta `batch={false}` para no agrupar lecturas en `POST /q`: un host cuyo backend no implementa `/q` ya no paga un POST fallido por cada lectura. Además, con el batch activo, si `/q` responde 404/405 el cliente deja de intentarlo tras el primer fallo y la lectura se resuelve por el GET normal. El comportamiento por defecto no cambia para hosts que sí tienen `/q`.
8
+ - 2575411: DynamicTable: nueva primitiva de acciones masivas. `extraBulkActions` (nodo o función `({ selectedRows, selectedIds, clearSelection, refresh }) => nodo`) pinta las acciones del host en la barra flotante de selección, junto al "Eliminar" integrado; `hideBulkDelete` oculta ese "Eliminar". La selección ahora se indexa por `id` del registro (antes por posición en la página) y, con paginación clásica, se vacía al cambiar de página, tamaño, orden, búsqueda o filtros de columna, de modo que una acción masiva nunca actúa sobre filas que el usuario ya no ve.
9
+ - e030a86: DynamicTable: un fallo del endpoint de lista que no es 403 (500, red o `success: false`) ya no se ve como "No se encontraron resultados": muestra "No se pudieron cargar los datos" con un botón "Reintentar" que vuelve a pedir la lista (claves `dynamic.load_error_title`, `dynamic.load_error_hint`, `dynamic.retry`). Nueva prop opcional `emptyState` para mostrar contenido de primer uso cuando la lista carga bien, está vacía y no hay búsqueda ni filtros activos; nunca tapa un error ni un 403.
10
+
11
+ ## 49.4.0
12
+
13
+ ### Minor Changes
14
+
15
+ - e95a6a4: DocumentEditor con selects «pro»: el buscador de producto vive en la celda «Descripción» de cada renglón (con un renglón vacío siempre al final, Enter elige y pasa a «Cant.»), los resultados salen en un portal collision-aware que nunca recorta la tabla ni el modal, y los dynamic_select con `ref` traen crear/editar unidos al trigger (`RecordPickerAction`, compartido con `EntitySelect`; el lápiz despacha `metacore:edit-record`). Pulido visual del editor: cabecera agrupada en tres columnas, etiquetas en sentence case, «Cargar desde» segmentado con estado vacío, tabla densa con totales alineados a «Importe», panel de totales con estado vacío y atajo ⌘/Ctrl+Enter para guardar.
16
+
3
17
  ## 49.3.1
4
18
 
5
19
  ### Patch Changes
@@ -20,9 +20,17 @@ export interface ApiClient {
20
20
  }
21
21
  export interface ApiProviderProps {
22
22
  client: ApiClient;
23
+ /**
24
+ * Groups list, metadata and options reads into one `POST /q`. Defaults to
25
+ * `true`. Pass `false` when the host backend does not implement `/q`: every
26
+ * read then goes straight to its own GET and no POST is attempted. When
27
+ * left on and `/q` answers 404/405, the batch turns itself off for that
28
+ * client after the first failed attempt and the read falls back to GET.
29
+ */
30
+ batch?: boolean;
23
31
  children: React.ReactNode;
24
32
  }
25
- export declare function ApiProvider({ client, children }: ApiProviderProps): React.JSX.Element;
33
+ export declare function ApiProvider({ client, batch, children }: ApiProviderProps): React.JSX.Element;
26
34
  /** Returns the host-injected api client. Throws if no <ApiProvider> is mounted. */
27
35
  export declare function useApi(): ApiClient;
28
36
  /** Optional branch context — hosts that support tenant branches can supply
@@ -1 +1 @@
1
- {"version":3,"file":"api-context.d.ts","sourceRoot":"","sources":["../src/api-context.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAwD,MAAM,OAAO,CAAA;AAI5E,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACtB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACzE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACtF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACrF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;CAC/E;AAID,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,SAAS,CAAA;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,gBAAgB,qBAuBjE;AAmCD,mFAAmF;AACnF,wBAAgB,MAAM,IAAI,SAAS,CAkBlC;AAED;;wEAEwE;AACxE,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CACzC;AAID,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,mBAAmB,qBAEvE;AAED,wBAAgB,gBAAgB,IAAI,WAAW,CAE9C"}
1
+ {"version":3,"file":"api-context.d.ts","sourceRoot":"","sources":["../src/api-context.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAwD,MAAM,OAAO,CAAA;AAI5E,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACtB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACzE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACtF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACrF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;CAC/E;AAKD,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,SAAS,CAAA;IACjB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,KAAY,EAAE,QAAQ,EAAE,EAAE,gBAAgB,qBA2B/E;AAmCD,mFAAmF;AACnF,wBAAgB,MAAM,IAAI,SAAS,CAoBlC;AAED;;wEAEwE;AACxE,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CACzC;AAID,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,mBAAmB,qBAEvE;AAED,wBAAgB,gBAAgB,IAAI,WAAW,CAE9C"}
@@ -7,7 +7,8 @@ import { createContext, useContext, useEffect, useMemo } from 'react';
7
7
  import { batchGet, invalidateQueryBatchData } from './query-batch';
8
8
  import { invalidateOptionsCache } from './use-options-resolver';
9
9
  const ApiContext = createContext(null);
10
- export function ApiProvider({ client, children }) {
10
+ const BatchContext = createContext(true);
11
+ export function ApiProvider({ client, batch = true, children }) {
11
12
  // Hosts also write through their own client (native dialogs, settings
12
13
  // screens) without going through useApi(). When the client is axios,
13
14
  // every non-GET response drops what the runtime remembered as well.
@@ -28,7 +29,7 @@ export function ApiProvider({ client, children }) {
28
29
  });
29
30
  return () => interceptors.eject?.(id);
30
31
  }, [client]);
31
- return _jsx(ApiContext.Provider, { value: client, children: children });
32
+ return (_jsx(ApiContext.Provider, { value: client, children: _jsx(BatchContext.Provider, { value: batch, children: children }) }));
32
33
  }
33
34
  // POSTs that do not change any record a list or picker shows: the read batch
34
35
  // (/q) and the bell inbox the host writes on every control toast
@@ -54,6 +55,7 @@ function mutating(request) {
54
55
  /** Returns the host-injected api client. Throws if no <ApiProvider> is mounted. */
55
56
  export function useApi() {
56
57
  const ctx = useContext(ApiContext);
58
+ const batch = useContext(BatchContext);
57
59
  if (!ctx) {
58
60
  throw new Error('useApi() requires an <ApiProvider> ancestor. Hosts must inject an axios-like client via runtime-react ApiProvider.');
59
61
  }
@@ -64,11 +66,11 @@ export function useApi() {
64
66
  return useMemo(() => ({
65
67
  // Each call reaches the host client with the arguments it was given —
66
68
  // never a trailing `undefined` it didn't ask for.
67
- get: (...args) => batchGet(ctx, ...args),
69
+ get: (...args) => batch ? batchGet(ctx, ...args) : ctx.get(...args),
68
70
  post: (...args) => isWrite('post', args[0]) ? mutating(ctx.post(...args)) : ctx.post(...args),
69
71
  put: (...args) => mutating(ctx.put(...args)),
70
72
  delete: (...args) => mutating(ctx.delete(...args)),
71
- }), [ctx]);
73
+ }), [ctx, batch]);
72
74
  }
73
75
  const BranchContext = createContext({ id: undefined });
74
76
  export function BranchProvider({ branch, children }) {
@@ -44,8 +44,9 @@ export interface TotalsRow {
44
44
  emphasis?: boolean;
45
45
  }
46
46
  /** Totales alineados a la derecha; la última fila con `emphasis` es el total. */
47
- export declare function TotalsPanel({ rows }: {
47
+ export declare function TotalsPanel({ rows, muted }: {
48
48
  rows: TotalsRow[];
49
+ muted?: boolean;
49
50
  }): import("react").JSX.Element;
50
51
  /** Lista de revisión: errores (rojo) y avisos (ámbar). Vacía → no pinta nada. */
51
52
  export declare function ValidationChecklist({ issues, title }: {
@@ -1 +1 @@
1
- {"version":3,"file":"document-editor-panels.d.ts","sourceRoot":"","sources":["../../src/business/document-editor-panels.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGvD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEzD,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,+BAYzI;AAED,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,SAAS,CAAA;IAChB,qEAAqE;IACrE,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAmB,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,uBAAuB,+BA4BtI;AAED,MAAM,WAAW,cAAc;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC1D,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAA;IAC5B,GAAG,EAAE,SAAS,CAAA;IACd,QAAQ,CAAC,EAAE,SAAS,CAAA;CACvB;AAED,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,cAAc,+BA4B9E;AAED,MAAM,WAAW,SAAS;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,SAAS,EAAE,CAAA;CAAE,+BAW1D;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,KAAiC,EAAE,EAAE;IAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,sCAe3H;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;CAAE,sCAO3E;AAED,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gEAAgE;IAChE,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC/C,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,GAAG,EAAE,SAAS,CAAA;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,iBAAiB,+BAgC3F"}
1
+ {"version":3,"file":"document-editor-panels.d.ts","sourceRoot":"","sources":["../../src/business/document-editor-panels.tsx"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ import { AlertTriangle, ChevronDown, XCircle } from 'lucide-react';
9
9
  import { computeLine } from './line-items';
10
10
  /** Bloque con título discreto y espaciado generoso (sin bordes pesados). */
11
11
  export function EditorSection({ title, hint, children, slot }) {
12
- return (_jsxs("section", { className: "space-y-3", "data-slot": slot, children: [(title || hint) && (_jsxs("header", { className: "flex items-baseline justify-between gap-3", children: [title && _jsx("h3", { className: "text-sm font-semibold", children: title }), hint && _jsx("span", { className: "text-xs text-muted-foreground", children: hint })] })), children] }));
12
+ return (_jsxs("section", { className: "space-y-3", "data-slot": slot, children: [(title || hint) && (_jsxs("header", { className: "flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1", children: [title && _jsx("h3", { className: "whitespace-nowrap text-sm font-semibold", children: title }), hint && _jsx("span", { className: "text-xs text-muted-foreground", children: hint })] })), children] }));
13
13
  }
14
14
  /**
15
15
  * Sección plegable. El contenido queda MONTADO aunque esté cerrada (`hidden`):
@@ -24,25 +24,25 @@ export function CollapsibleSection({ title, summary, open, defaultOpen = false,
24
24
  setInner(!isOpen);
25
25
  onOpenChange?.(!isOpen);
26
26
  };
27
- return (_jsxs("section", { className: "rounded-lg border border-dashed", "data-slot": slot, "data-state": isOpen ? 'open' : 'closed', children: [_jsxs("button", { type: "button", "aria-expanded": isOpen, "aria-controls": id, onClick: toggle, className: "flex w-full items-center justify-between gap-3 px-4 py-3 text-left text-sm", children: [_jsx("span", { className: "font-medium", children: title }), _jsxs("span", { className: "flex min-w-0 items-center gap-2 text-xs text-muted-foreground", children: [!isOpen && summary && _jsx("span", { className: "truncate", children: summary }), _jsx(ChevronDown, { className: `size-4 shrink-0 transition-transform ${isOpen ? 'rotate-180' : ''}`, "aria-hidden": true })] })] }), _jsx("div", { id: id, hidden: !isOpen, className: "space-y-4 px-4 pb-4", children: children })] }));
27
+ return (_jsxs("section", { className: "rounded-xl border border-dashed", "data-slot": slot, "data-state": isOpen ? 'open' : 'closed', children: [_jsxs("button", { type: "button", "aria-expanded": isOpen, "aria-controls": id, onClick: toggle, className: "flex w-full items-center justify-between gap-3 rounded-xl px-4 py-3 text-left text-sm transition-colors hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50", children: [_jsx("span", { className: "font-medium", children: title }), _jsxs("span", { className: "flex min-w-0 items-center gap-2 text-xs text-muted-foreground", children: [!isOpen && summary && _jsx("span", { className: "truncate", children: summary }), _jsx(ChevronDown, { className: `size-4 shrink-0 transition-transform ${isOpen ? 'rotate-180' : ''}`, "aria-hidden": true })] })] }), _jsx("div", { id: id, hidden: !isOpen, className: "space-y-4 px-4 pb-4", children: children })] }));
28
28
  }
29
29
  /** Tarjeta compacta de la contraparte: nombre, 3-6 datos clave y estado de crédito. */
30
30
  export function PartyCard({ name, rows, credit, fmt, children }) {
31
- return (_jsxs("div", { className: "rounded-md bg-muted/40 px-4 py-3 text-sm", "data-slot": "party-card", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [name && _jsx("span", { className: "font-medium", children: name }), credit && (_jsx(Badge, { variant: credit.level === 'block' ? 'destructive' : credit.level === 'warn' ? 'secondary' : 'outline', "data-slot": "party-credit", children: credit.holdReason
31
+ return (_jsxs("div", { className: "rounded-lg bg-muted/40 px-4 py-3 text-sm", "data-slot": "party-card", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [name && _jsx("span", { className: "font-medium", children: name }), credit && (_jsx(Badge, { variant: credit.level === 'block' ? 'destructive' : credit.level === 'warn' ? 'secondary' : 'outline', "data-slot": "party-credit", children: credit.holdReason
32
32
  ? credit.holdReason
33
33
  : credit.available != null
34
34
  ? `Crédito disponible ${fmt.money(credit.available)}`
35
35
  : `Saldo ${fmt.money(credit.balance ?? 0)}` }))] }), rows.length > 0 && (_jsx("dl", { className: "mt-2 grid grid-cols-2 gap-x-6 gap-y-1 sm:grid-cols-3", children: rows.map((r) => (_jsxs("div", { className: "min-w-0", children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: r.label }), _jsx("dd", { className: "truncate", children: r.value })] }, r.key))) })), children] }));
36
36
  }
37
37
  /** Totales alineados a la derecha; la última fila con `emphasis` es el total. */
38
- export function TotalsPanel({ rows }) {
39
- return (_jsx("dl", { className: "ml-auto w-full max-w-xs space-y-1 text-sm tabular-nums", "data-slot": "totals", children: rows.map((r) => (_jsxs("div", { className: `flex justify-between gap-6 ${r.emphasis ? 'border-t pt-2 text-base font-semibold' : ''}`, children: [_jsx("dt", { className: r.emphasis ? '' : 'text-muted-foreground', children: r.label }), _jsx("dd", { children: r.value })] }, r.label))) }));
38
+ export function TotalsPanel({ rows, muted = false }) {
39
+ return (_jsx("dl", { className: `ml-auto w-full max-w-xs space-y-1.5 text-sm tabular-nums ${muted ? 'text-muted-foreground' : ''}`, "data-slot": "totals", children: rows.map((r, i) => (_jsxs("div", { className: `flex items-baseline justify-between gap-6 ${r.emphasis ? `${i > 0 ? 'border-t pt-2.5' : ''} text-lg font-semibold` : ''}`, children: [_jsx("dt", { className: r.emphasis ? '' : 'text-muted-foreground', children: r.label }), _jsx("dd", { children: r.value })] }, r.label))) }));
40
40
  }
41
41
  /** Lista de revisión: errores (rojo) y avisos (ámbar). Vacía → no pinta nada. */
42
42
  export function ValidationChecklist({ issues, title = 'Revisa antes de guardar' }) {
43
43
  if (issues.length === 0)
44
44
  return null;
45
- return (_jsxs("div", { className: "rounded-md bg-muted/30 px-4 py-3", "data-slot": "validation-checklist", role: "status", children: [_jsx("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: title }), _jsx("ul", { className: "space-y-1 text-sm", children: issues.map((i, k) => (_jsxs("li", { className: `flex items-start gap-2 ${i.severity === 'error' ? 'text-destructive' : 'text-amber-700'}`, children: [i.severity === 'error' ? _jsx(XCircle, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": true }) : _jsx(AlertTriangle, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": true }), i.message] }, `${i.field ?? ''}:${k}`))) })] }));
45
+ return (_jsxs("div", { className: "rounded-md bg-muted/30 px-4 py-3", "data-slot": "validation-checklist", role: "status", children: [_jsx("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: title }), _jsx("ul", { className: "space-y-1 text-sm", children: issues.map((i, k) => (_jsxs("li", { className: `flex items-start gap-2 ${i.severity === 'error' ? 'text-destructive' : 'text-amber-700 dark:text-amber-400'}`, children: [i.severity === 'error' ? _jsx(XCircle, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": true }) : _jsx(AlertTriangle, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": true }), i.message] }, `${i.field ?? ''}:${k}`))) })] }));
46
46
  }
47
47
  /** Contenido de la vista previa (PDF, HTML o XML, en ese orden de preferencia). */
48
48
  export function PreviewPanel({ preview }) {
@@ -1 +1 @@
1
- {"version":3,"file":"document-editor.d.ts","sourceRoot":"","sources":["../../src/business/document-editor.tsx"],"names":[],"mappings":"AA4BA,OAAO,EAAqB,KAAK,sBAAsB,EAAwB,MAAM,qBAAqB,CAAA;AAkD1G,OAAO,KAAK,EAAsC,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAE3G,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,qBAAqB,CAAA;IAC5B,IAAI,EAAE,gBAAgB,CAAA;IACtB,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,oEAAoE;IACpE,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3C,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACpC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,cAAc,CAAC,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAA;IACjD,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACvB;AAID,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,MAAM,EACN,aAAa,EACb,OAAO,EACP,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,KAAK,EACL,UAAkB,GACrB,EAAE,mBAAmB,+BAwuBrB"}
1
+ {"version":3,"file":"document-editor.d.ts","sourceRoot":"","sources":["../../src/business/document-editor.tsx"],"names":[],"mappings":"AA4BA,OAAO,EAAqB,KAAK,sBAAsB,EAAwB,MAAM,qBAAqB,CAAA;AAkD1G,OAAO,KAAK,EAAsC,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAE3G,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,qBAAqB,CAAA;IAC5B,IAAI,EAAE,gBAAgB,CAAA;IACtB,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,oEAAoE;IACpE,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3C,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACpC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,cAAc,CAAC,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAA;IACjD,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACvB;AAID,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,MAAM,EACN,aAAa,EACb,OAAO,EACP,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,KAAK,EACL,UAAkB,GACrB,EAAE,mBAAmB,+BA4xBrB"}
@@ -21,7 +21,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
21
21
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
22
  import { useTranslation } from 'react-i18next';
23
23
  import { Button } from '@asteby/metacore-ui/primitives';
24
- import { Loader2 } from 'lucide-react';
24
+ import { Loader2, ReceiptText } from 'lucide-react';
25
25
  import { toast } from 'sonner';
26
26
  import { useApi } from '../api-context';
27
27
  import { useOrgTaxRate, useTimeZone } from '../org-runtime-context';
@@ -482,7 +482,7 @@ export function DocumentEditor({ model, endpoint, forms, type, record, initialSo
482
482
  setFieldErrors((prev) => clearFieldErrorTree(prev, key));
483
483
  };
484
484
  const fieldRecord = useMemo(() => seedRecord(seeds, record), [seeds, record]);
485
- const fieldCell = (field, fullWidth = false) => visible(field) ? (_jsxs(FieldCell, { fullWidth: fullWidth, children: [_jsx(FieldLabel, { htmlFor: field.key, required: field.required, children: tl(field.label) }), renderField(withFriendlyOptions(gatedOptions(field, header)), header[field.key], (v) => updateField(field.key, v), header, fieldRecord, fieldErrors), fieldErrors[field.key] && _jsx("p", { className: "mt-1 text-xs text-destructive", children: fieldErrors[field.key] })] }, field.key)) : null;
485
+ const fieldCell = (field, fullWidth = false, className) => visible(field) ? (_jsxs(FieldCell, { fullWidth: fullWidth, className: className, children: [_jsx(FieldLabel, { htmlFor: field.key, required: field.required, tone: "sentence", children: tl(field.label) }), renderField(withFriendlyOptions(gatedOptions(field, header)), header[field.key], (v) => updateField(field.key, v), header, fieldRecord, fieldErrors), fieldErrors[field.key] && _jsx("p", { className: "mt-1 text-xs text-destructive", children: fieldErrors[field.key] })] }, field.key)) : null;
486
486
  const summaryRows = partySummaryRows(party, type.party?.summary, {
487
487
  ...Object.fromEntries(Object.entries(partyLabels).map(([k, v]) => [k, tl(v)])),
488
488
  ...Object.fromEntries(allFields.map((f) => [f.key, tl(f.label)])),
@@ -525,21 +525,28 @@ export function DocumentEditor({ model, endpoint, forms, type, record, initialSo
525
525
  // Sin acción de vista previa del servidor, un documento con renglones ofrece
526
526
  // la suya local (encabezado, renglones y totales): sin borrador ni red.
527
527
  const localPreview = !previewEnabled && !type.preview && !!lineCfg && !isAllocation && kind !== 'credit';
528
- const essentials = (_jsxs(EditorSection, { slot: "editor-essentials", children: [_jsxs(FieldGrid, { children: [groups.party && fieldCell(groups.party), groups.essential.map((f) => fieldCell(f))] }), party && (_jsx(PartyCard, { name: party.name ?? party.legal_name, rows: summaryRows, credit: credit, fmt: fmt, children: partyContribs.map(({ id, component: C }) => (_jsx(C, { ...contribProps }, id))) }))] }));
529
- const loadFrom = sources.length > 0 && !isAllocation && (_jsx(EditorSection, { slot: "load-from-source", title: t('documentEditor.load_from', { defaultValue: 'Cargar desde…' }), hint: busy === 'source' ? t('documentEditor.loading_source', { defaultValue: 'Cargando…' }) : sourceNote, children: _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [sources.length > 1 && (_jsx("div", { role: "radiogroup", "aria-label": t('documentEditor.source_kind', { defaultValue: 'Documento de origen' }), className: "inline-flex flex-wrap gap-1 rounded-md border p-1", children: sources.map((s) => (_jsx("button", { type: "button", role: "radio", "aria-checked": s.key === sourceKey, "data-source-kind": s.key, onClick: () => {
528
+ const essentials = (_jsxs(EditorSection, { slot: "editor-essentials", children: [_jsx("div", { className: "@container rounded-xl border bg-card/40 p-4", children: _jsxs(FieldGrid, { className: "gap-x-4 @2xl:grid-cols-3", children: [groups.party && fieldCell(groups.party, false, '@2xl:col-span-2'), groups.essential.map((f) => fieldCell(f))] }) }), party && (_jsx(PartyCard, { name: party.name ?? party.legal_name, rows: summaryRows, credit: credit, fmt: fmt, children: partyContribs.map(({ id, component: C }) => (_jsx(C, { ...contribProps }, id))) }))] }));
529
+ const loadFrom = sources.length > 0 && !isAllocation && (_jsx(EditorSection, { slot: "load-from-source", title: t('documentEditor.load_from', { defaultValue: 'Cargar desde…' }), hint: busy === 'source'
530
+ ? t('documentEditor.loading_source', { defaultValue: 'Cargando…' })
531
+ : sourceNote ?? (sourceId ? undefined : t('documentEditor.load_from_hint', { defaultValue: 'Opcional · precarga contraparte y renglones' })), children: _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [sources.length > 1 && (_jsx("div", { role: "radiogroup", "aria-label": t('documentEditor.source_kind', { defaultValue: 'Documento de origen' }), className: "inline-flex h-9 shrink-0 items-center gap-0.5 rounded-md border bg-muted/40 p-0.5", children: sources.map((s) => (_jsx("button", { type: "button", role: "radio", "aria-checked": s.key === sourceKey, "data-source-kind": s.key, onClick: () => {
530
532
  setSourceKey(s.key);
531
533
  setSourceId('');
532
- }, className: `rounded px-3 py-1.5 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${s.key === sourceKey ? 'bg-primary/10 font-medium text-primary' : 'text-muted-foreground hover:bg-accent'}`, children: tl(s.label) }, s.key))) })), activeSource && (_jsx(SourcePicker, { source: activeSource, value: sourceId, seed: sourceSeed, header: header, label: tl(activeSource.label), onPick: (id) => {
534
+ }, className: `h-full rounded-sm px-3 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 ${s.key === sourceKey
535
+ ? 'bg-background font-medium text-foreground shadow-xs dark:bg-input/60'
536
+ : 'text-muted-foreground hover:text-foreground'}`, children: tl(s.label) }, s.key))) })), !activeSource && (_jsx("div", { className: "flex h-9 min-w-60 flex-1 items-center rounded-md border border-dashed px-3 text-sm text-muted-foreground", "data-slot": "source-picker-empty", children: t('documentEditor.pick_source_kind', { defaultValue: 'Elige el tipo de documento para buscarlo' }) })), activeSource && (_jsx(SourcePicker, { source: activeSource, value: sourceId, seed: sourceSeed, header: header, label: tl(activeSource.label), onPick: (id) => {
533
537
  setSourceKey(activeSource.key);
534
538
  setSourceId(id);
535
539
  if (id)
536
540
  void loadFromSource(activeSource, id);
537
541
  } }, activeSource.key))] }) }));
538
- const totalsPanel = _jsx(TotalsPanel, { rows: totalsRows });
542
+ // Sin renglones todavía: un estado vacío amable en vez de $0.00 en cada fila.
543
+ const noLines = !isAllocation && !!lineCfg && lines.length === 0;
544
+ const totalsPanel = noLines ? (_jsx(TotalsPanel, { rows: totalsRows.filter((r) => r.emphasis), muted: true })) : (_jsx(TotalsPanel, { rows: totalsRows }));
545
+ const totalsEmpty = noLines && (_jsxs("div", { className: "flex items-start gap-3 text-sm text-muted-foreground", "data-slot": "totals-empty", children: [_jsx(ReceiptText, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": true }), _jsx("p", { children: t('documentEditor.totals_empty', { defaultValue: 'Aún no hay renglones. Busca un producto en la tabla para ver el desglose.' }) })] }));
539
546
  const linesSection = lineCfg && (_jsxs(EditorSection, { slot: isAllocation ? 'editor-allocation' : 'editor-lines', title: linesTitle, hint: isAllocation && openBalance > 0 && amountKey ? (_jsx("button", { type: "button", className: "underline-offset-2 hover:underline", onClick: () => updateField(amountKey, openBalance), children: t('documentEditor.pay_all', { defaultValue: 'Cobrar todo ({{amount}})', amount: fmt.money(openBalance) }) })) : undefined, children: [isAllocation ? (allocPartyId ? (_jsx(PaymentAllocator, { documents: openDocs, result: allocation, value: alloc, onChange: setAlloc, currency: currency, loading: docsLoading, today: today })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: t('documentEditor.pick_party_first', { defaultValue: 'Elige el cliente para ver sus documentos con saldo.' }) }))) : (_jsx(DocumentLinesGrid, { value: lines, onChange: (next) => {
540
547
  setLines(next);
541
548
  setServerIssues([]);
542
- }, columns: lineCfg.columns ?? ['discount', 'tax'], priceSource: lineCfg.price_source ?? (kind === 'purchase' ? 'cost' : 'sale'), discountMode: lineCfg.discount_mode, mode: linesFromSourceDoc ? 'from_source' : 'free', policy: kind === 'credit' ? { allowZeroQuantity: linesFromSourceDoc } : undefined, search: search, currency: currency })), !fullscreen && totalsPanel] }));
549
+ }, columns: lineCfg.columns ?? ['discount', 'tax'], priceSource: lineCfg.price_source ?? (kind === 'purchase' ? 'cost' : 'sale'), discountMode: lineCfg.discount_mode, mode: linesFromSourceDoc ? 'from_source' : 'free', policy: kind === 'credit' ? { allowZeroQuantity: linesFromSourceDoc } : undefined, search: search, currency: currency, showTotals: false })), !fullscreen && totalsPanel] }));
543
550
  const advanced = (groups.advanced.length > 0 || headerContribs.length > 0) && (_jsxs(CollapsibleSection, { slot: "advanced-options", title: t('documentEditor.advanced', { defaultValue: 'Opciones fiscales' }), summary: advancedSummary || t('documentEditor.advanced_defaults', { defaultValue: 'Valores sugeridos' }), open: advancedOpen, onOpenChange: setAdvancedOpen, children: [groups.advanced.length > 0 && _jsx(FieldGrid, { children: groups.advanced.map((f) => fieldCell(f)) }), headerContribs.map(({ id, component: C }) => (_jsx(C, { ...contribProps }, id)))] }));
544
551
  const contributedSections = [...bodyContribs, ...sideContribs].map(({ id, component: C }) => (_jsx("section", { "data-contribution": id, children: _jsx(C, { ...contribProps }) }, id)));
545
552
  const notes = groups.notes.length > 0 && _jsx(FieldGrid, { children: groups.notes.map((f) => fieldCell(f, true)) });
@@ -552,8 +559,15 @@ export function DocumentEditor({ model, endpoint, forms, type, record, initialSo
552
559
  return { label: tl(f.label), value: v == null || v === '' ? '' : opt ? friendlyOptionLabel(tl(String(opt.label ?? v))) : seeded ?? String(v) };
553
560
  })
554
561
  .filter((r) => r.value), lines: lines, totals: totalsRows, fmt: fmt }) })) : null;
562
+ const isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent || '');
555
563
  const checklist = _jsx(ValidationChecklist, { issues: shownIssues, title: t('documentEditor.review', { defaultValue: 'Revisa antes de guardar' }) });
556
- return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", "data-slot": "document-editor", "data-kind": kind, "data-layout": fullscreen ? 'fullscreen' : 'dialog', children: [fullscreen ? (_jsxs("div", { className: "-mx-1 min-h-0 flex-1 overflow-y-auto px-1 py-2 lg:grid lg:grid-cols-[minmax(0,1fr)_340px] lg:gap-8", children: [_jsxs("div", { className: "min-w-0 space-y-8", "data-slot": "editor-main", children: [_jsx(FormErrorBanner, { message: formError }), essentials, loadFrom, linesSection, advanced, contributedSections, notes] }), _jsxs("aside", { className: "mt-8 space-y-6 lg:sticky lg:top-0 lg:mt-0 lg:self-start", "data-slot": "editor-aside", children: [_jsx("div", { className: "rounded-lg border p-4", children: totalsPanel }), checklist, previewSection] })] })) : (_jsxs("div", { className: "-mx-1 min-h-0 flex-1 space-y-8 overflow-y-auto px-1 py-2", children: [_jsx(FormErrorBanner, { message: formError }), essentials, loadFrom, linesSection, advanced, contributedSections, notes, previewSection, checklist] })), _jsxs("footer", { className: "flex shrink-0 flex-wrap items-center justify-end gap-2 border-t pt-4", "data-slot": "editor-actions", children: [footerContribs.map(({ id, component: C }) => (_jsx(C, { ...contribProps }, id))), _jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, disabled: busy === 'save', children: t('common.cancel', { defaultValue: 'Cancelar' }) }), _jsxs(Button, { type: "button", "data-primary": "true", onClick: () => void submit(), disabled: busy !== null, children: [busy === 'save' && _jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": true }), type.submit_label ? tl(type.submit_label) : t('common.save', { defaultValue: 'Guardar' })] })] })] }));
564
+ return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", "data-slot": "document-editor", "data-kind": kind, "data-layout": fullscreen ? 'fullscreen' : 'dialog', onKeyDown: (e) => {
565
+ // ⌘/Ctrl+Enter guarda desde cualquier campo del editor.
566
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && busy === null) {
567
+ e.preventDefault();
568
+ void submit();
569
+ }
570
+ }, children: [fullscreen ? (_jsxs("div", { className: "-mx-1 min-h-0 flex-1 overflow-y-auto px-1 py-2 lg:grid lg:grid-cols-[minmax(0,1fr)_320px] lg:gap-6", children: [_jsxs("div", { className: "min-w-0 space-y-6", "data-slot": "editor-main", children: [_jsx(FormErrorBanner, { message: formError }), essentials, loadFrom, linesSection, advanced, contributedSections, notes] }), _jsxs("aside", { className: "mt-6 space-y-4 lg:sticky lg:top-0 lg:mt-0 lg:self-start", "data-slot": "editor-aside", children: [_jsxs("div", { className: "space-y-3 rounded-xl border bg-card/40 p-4", children: [totalsEmpty, totalsPanel] }), checklist, previewSection] })] })) : (_jsxs("div", { className: "-mx-1 min-h-0 flex-1 space-y-6 overflow-y-auto px-1 py-2", children: [_jsx(FormErrorBanner, { message: formError }), essentials, loadFrom, linesSection, advanced, contributedSections, notes, previewSection, checklist] })), _jsxs("footer", { className: "flex shrink-0 flex-wrap items-center justify-end gap-2 border-t pt-4", "data-slot": "editor-actions", children: [_jsxs("span", { className: "mr-auto hidden items-center gap-1 text-xs text-muted-foreground sm:inline-flex", "data-slot": "editor-shortcut", children: [_jsx("kbd", { className: "rounded border bg-muted px-1.5 py-0.5 font-mono text-[0.7rem]", children: isMac ? '⌘' : 'Ctrl' }), _jsx("kbd", { className: "rounded border bg-muted px-1.5 py-0.5 font-mono text-[0.7rem]", children: "Enter" }), _jsx("span", { children: t('documentEditor.shortcut_save', { defaultValue: 'para guardar' }) })] }), footerContribs.map(({ id, component: C }) => (_jsx(C, { ...contribProps }, id))), _jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, disabled: busy === 'save', children: t('common.cancel', { defaultValue: 'Cancelar' }) }), _jsxs(Button, { type: "button", "data-primary": "true", onClick: () => void submit(), disabled: busy !== null, children: [busy === 'save' && _jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": true }), type.submit_label ? tl(type.submit_label) : t('common.save', { defaultValue: 'Guardar' })] })] })] }));
557
571
  }
558
572
  /** Selector del documento origen (mismo widget de relación que el resto de formularios). */
559
573
  function SourcePicker({ source, value, seed, header, label, onPick, }) {
@@ -564,5 +578,5 @@ function SourcePicker({ source, value, seed, header, label, onPick, }) {
564
578
  ref: source.model,
565
579
  option_filter: source.option_filter,
566
580
  }), [source, label]);
567
- return (_jsx("div", { className: "min-w-[240px] flex-1", "data-source": source.key, children: renderField(field, value, (v) => onPick(v == null || v === '' ? '' : String(v)), header, seed ? { id: seed.value, label: seed.label } : undefined) }));
581
+ return (_jsx("div", { className: "min-w-60 flex-1", "data-source": source.key, children: renderField(field, value, (v) => onPick(v == null || v === '' ? '' : String(v)), header, seed ? { id: seed.value, label: seed.label } : undefined) }));
568
582
  }
@@ -4,6 +4,7 @@ export * from './feedback';
4
4
  export * from './line-items';
5
5
  export * from './document-lines';
6
6
  export * from './line-items-editor';
7
+ export * from './line-product-cell';
7
8
  export * from './payment';
8
9
  export * from './payment-capture';
9
10
  export * from './refund';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/business/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/business/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA"}
@@ -4,6 +4,7 @@ export * from './feedback';
4
4
  export * from './line-items';
5
5
  export * from './document-lines';
6
6
  export * from './line-items-editor';
7
+ export * from './line-product-cell';
7
8
  export * from './payment';
8
9
  export * from './payment-capture';
9
10
  export * from './refund';
@@ -40,10 +40,15 @@ export interface DocumentLinesGridProps {
40
40
  editPermission?: string;
41
41
  /** Moneda ISO (default: la de la org). */
42
42
  currency?: string;
43
+ /**
44
+ * Pinta el bloque de totales bajo la tabla. Default true; un contenedor que
45
+ * ya muestra sus propios totales (DocumentEditor) lo apaga.
46
+ */
47
+ showTotals?: boolean;
43
48
  }
44
49
  /** @deprecated Usa `DocumentLinesGridProps`. Es el mismo contrato. */
45
50
  export type LineItemsEditorProps = DocumentLinesGridProps;
46
- export declare function DocumentLinesGrid({ value, onChange, columns, policy, serverErrors, onValidate, onRequestProduct, search, priceSource, mode, discountMode, quantityStepper, mergeSameProduct, warehouseId, allowSections, readOnly, editPermission, currency, }: DocumentLinesGridProps): import("react").JSX.Element;
51
+ export declare function DocumentLinesGrid({ value, onChange, columns, policy, serverErrors, onValidate, onRequestProduct, search, priceSource, mode, discountMode, quantityStepper, mergeSameProduct, warehouseId, allowSections, readOnly, editPermission, currency, showTotals, }: DocumentLinesGridProps): import("react").JSX.Element;
47
52
  /** Mismo componente. El nombre anterior sigue exportado. */
48
53
  export declare const LineItemsEditor: typeof DocumentLinesGrid;
49
54
  //# sourceMappingURL=line-items-editor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"line-items-editor.d.ts","sourceRoot":"","sources":["../../src/business/line-items-editor.tsx"],"names":[],"mappings":"AASA,OAAO,EAGH,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACnB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAKH,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAC3B,MAAM,cAAc,CAAA;AAErB,OAAO,EAEH,KAAK,YAAY,EACjB,KAAK,aAAa,EAErB,MAAM,kBAAkB,CAAA;AAEzB,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,KAAK,GAAG,YAAY,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;AAExF,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,gEAAgE;IAChE,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;IACrC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,eAAe,EAAE,CAAA;IAC3B,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAA;IAC7C,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IAC3G,4FAA4F;IAC5F,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,mFAAmF;IACnF,IAAI,CAAC,EAAE,iBAAiB,CAAA;IACxB,oFAAoF;IACpF,YAAY,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAA;IAC5C,6DAA6D;IAC7D,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AAIzD,wBAAgB,iBAAiB,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,OAAsB,EACtB,MAAM,EACN,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,WAAoB,EACpB,IAAa,EACb,YAAwB,EACxB,eAAe,EACf,gBAAuB,EACvB,WAAW,EACX,aAAoB,EACpB,QAAgB,EAChB,cAAc,EACd,QAAQ,GACX,EAAE,sBAAsB,+BAwWxB;AAED,4DAA4D;AAC5D,eAAO,MAAM,eAAe,0BAAoB,CAAA"}
1
+ {"version":3,"file":"line-items-editor.d.ts","sourceRoot":"","sources":["../../src/business/line-items-editor.tsx"],"names":[],"mappings":"AASA,OAAO,EAIH,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACnB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAKH,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAC3B,MAAM,cAAc,CAAA;AAErB,OAAO,EACH,KAAK,YAAY,EACjB,KAAK,aAAa,EAErB,MAAM,kBAAkB,CAAA;AAEzB,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,KAAK,GAAG,YAAY,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;AAExF,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,gEAAgE;IAChE,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;IACrC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,eAAe,EAAE,CAAA;IAC3B,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAA;IAC7C,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IAC3G,4FAA4F;IAC5F,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,mFAAmF;IACnF,IAAI,CAAC,EAAE,iBAAiB,CAAA;IACxB,oFAAoF;IACpF,YAAY,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAA;IAC5C,6DAA6D;IAC7D,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AAIzD,wBAAgB,iBAAiB,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,OAAsB,EACtB,MAAM,EACN,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,WAAoB,EACpB,IAAa,EACb,YAAwB,EACxB,eAAe,EACf,gBAAuB,EACvB,WAAW,EACX,aAAoB,EACpB,QAAgB,EAChB,cAAc,EACd,QAAQ,EACR,UAAiB,GACpB,EAAE,sBAAsB,+BAwbxB;AAED,4DAA4D;AAC5D,eAAO,MAAM,eAAe,0BAAoB,CAAA"}