@asteby/metacore-runtime-react 28.5.0 → 28.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 28.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1c8c4fe: Unifica el flujo de importación alrededor de la `ImportSpec` que sirve el kernel. `starter-core` traía una copia byte a byte del `ImportDialog` de `runtime-react` (414 líneas) que nadie importaba y cuyos endpoints ya habían divergido (`/data/` vs `/dynamic/`): se elimina y el de `runtime-react` queda canónico. El diálogo acepta `.xlsx` además de CSV/JSON, y normaliza los errores por fila del backend nuevo (`{row, column, message}` en validate, `{row, column, error}` en import) sin romper el formato anterior (`{row, field, message}`), que antes renderizaba celdas vacías. `TableMetadata.import` queda tipado (`ImportSpecMeta`/`ImportColumnMeta`): cuando el kernel sirve la spec, la acción de importar aparece sin que el host encienda ninguna bandera, porque el modelo declaró sus columnas o el kernel las derivó de su formulario. Se agregan `hideImport` y `hideExport` por vista, para las tablas con alcance por rol que no quieren el punto de entrada masivo aunque el modelo lo soporte.
8
+
9
+ ## 28.6.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 21c5268: Soporta `visible_when` a nivel de SECCIÓN en `form_layout` (kernel v0.84.0). Cuando una sección declara un predicado `visible_when` que evalúa falso contra los valores actuales del formulario, la sección se oculta por completo junto con sus campos; en modo wizard (steps) ese paso desaparece de la secuencia de navegación sin romper Anterior/Siguiente ni el submit del último paso. Reutiliza el mismo evaluador (`getVisibleWhen`/`evaluateVisibleWhen`) que ya usan los campos, tolerando snake_case y camelCase. El `visible_when` de cada campo se sigue respetando dentro de una sección visible, y sin `visible_when` de sección el comportamiento es idéntico al actual.
14
+
3
15
  ## 28.5.0
4
16
 
5
17
  ### Minor Changes
@@ -598,7 +598,7 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
598
598
  // their section. Empty sections drop out for free. Steps mode only drives a
599
599
  // wizard in editable modes — view mode always stacks the sections.
600
600
  const formLayout = modalMeta?.form_layout ?? modalMeta?.formLayout;
601
- const groups = groupFieldsBySection(visibleFields, formLayout);
601
+ const groups = groupFieldsBySection(visibleFields, formLayout, formValues);
602
602
  const isSteps = isEditable && formLayout?.mode === 'steps' && groups.length > 1;
603
603
  const clampedStep = Math.min(stepIndex, Math.max(groups.length - 1, 0));
604
604
  const isLastStep = clampedStep === groups.length - 1;
@@ -1 +1 @@
1
- {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/dialogs/import.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAG7C,UAAU,iBAAiB;IACvB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,aAAa,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAoBD,wBAAgB,YAAY,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,UAAU,GACb,EAAE,iBAAiB,+BAwVnB"}
1
+ {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/dialogs/import.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAG7C,UAAU,iBAAiB;IACvB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,aAAa,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAqCD,wBAAgB,YAAY,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,UAAU,GACb,EAAE,iBAAiB,+BAkWnB"}
@@ -1,12 +1,21 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- // ImportDialog — three-step CSV/JSON import flow (upload → validate → import
3
- // with per-row error report). Axios-like client is provided by <ApiProvider>.
2
+ // ImportDialog — three-step XLSX/CSV/JSON import flow (upload → validate →
3
+ // import with per-row error report). The template, the accepted columns and
4
+ // the validation rules all come from the model's ImportSpec served by the
5
+ // kernel, so this dialog carries no per-model knowledge. Axios-like client is
6
+ // provided by <ApiProvider>.
4
7
  import { useState, useEffect, useRef } from 'react';
5
8
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button, Input, Label, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@asteby/metacore-ui/primitives';
6
9
  import { Progress } from './_primitives';
7
10
  import { toast } from 'sonner';
8
11
  import { FileDown, Loader2, Check, AlertCircle } from 'lucide-react';
9
12
  import { useApi } from '../api-context';
13
+ function errorColumn(e) {
14
+ return e.column ?? e.field ?? '';
15
+ }
16
+ function errorMessage(e) {
17
+ return e.message ?? e.error ?? '';
18
+ }
10
19
  export function ImportDialog({ open, onOpenChange, model, metadata, onImported, }) {
11
20
  const api = useApi();
12
21
  const [step, setStep] = useState('upload');
@@ -42,7 +51,7 @@ export function ImportDialog({ open, onOpenChange, model, metadata, onImported,
42
51
  const url = window.URL.createObjectURL(response.data);
43
52
  const link = document.createElement('a');
44
53
  link.href = url;
45
- link.download = `${model}-plantilla.csv`;
54
+ link.download = `${model}-plantilla.xlsx`;
46
55
  document.body.appendChild(link);
47
56
  link.click();
48
57
  document.body.removeChild(link);
@@ -66,7 +75,8 @@ export function ImportDialog({ open, onOpenChange, model, metadata, onImported,
66
75
  });
67
76
  const data = res.data?.data ?? res.data;
68
77
  setValidationResult({
69
- valid: data.valid ?? 0,
78
+ valid: data.rowCount ?? data.valid ?? 0,
79
+ skipped: data.skipped ?? 0,
70
80
  errors: data.errors ?? [],
71
81
  });
72
82
  setStep('validation');
@@ -98,7 +108,8 @@ export function ImportDialog({ open, onOpenChange, model, metadata, onImported,
98
108
  const data = res.data?.data ?? res.data;
99
109
  setImportResult({
100
110
  created: data.created ?? 0,
101
- errors: data.errors ?? [],
111
+ skipped: data.skipped ?? 0,
112
+ errors: data.failures ?? data.errors ?? [],
102
113
  });
103
114
  setStep('results');
104
115
  if ((data.created ?? 0) > 0) {
@@ -122,6 +133,6 @@ export function ImportDialog({ open, onOpenChange, model, metadata, onImported,
122
133
  validation: 'Validacion',
123
134
  results: 'Resultados',
124
135
  };
125
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden", children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsxs(DialogTitle, { children: ["Importar ", metadata.title] }), _jsx(DialogDescription, { children: stepTitle[step] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto p-6", children: [step === 'upload' && (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsxs(Button, { variant: "link", className: "px-0 h-auto text-sm", onClick: handleDownloadTemplate, children: [_jsx(FileDown, { className: "h-4 w-4 mr-1" }), "Descargar plantilla CSV"] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Descarga la plantilla para asegurar el formato correcto." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "import-file", className: "text-sm font-medium", children: "Archivo" }), _jsx(Input, { ref: fileInputRef, id: "import-file", type: "file", accept: ".csv,.json", onChange: handleFileChange, className: "cursor-pointer" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Formatos aceptados: CSV, JSON" })] })] })), step === 'validation' && validationResult && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { children: [_jsx("strong", { children: validationResult.valid }), " valido(s)"] })] }), validationResult.errors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" }), _jsxs("span", { children: [_jsx("strong", { children: validationResult.errors.length }), " error(es)"] })] }))] }), validationResult.errors.length > 0 && (_jsx("div", { className: "border rounded-md max-h-60 overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-16", children: "Fila" }), _jsx(TableHead, { children: "Campo" }), _jsx(TableHead, { children: "Error" })] }) }), _jsx(TableBody, { children: validationResult.errors.map((error, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: error.row }), _jsx(TableCell, { className: "text-sm", children: error.field }), _jsx(TableCell, { className: "text-sm text-destructive", children: error.message })] }, idx))) })] }) })), importing && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { value: progress }), _jsxs("p", { className: "text-xs text-muted-foreground text-center", children: ["Importando... ", progress > 0 ? `${progress}%` : ''] })] }))] })), step === 'results' && importResult && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [importResult.created > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { children: [_jsx("strong", { children: importResult.created }), " creado(s)"] })] })), importResult.errors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" }), _jsxs("span", { children: [_jsx("strong", { children: importResult.errors.length }), " error(es)"] })] }))] }), importResult.created > 0 && importResult.errors.length === 0 && (_jsxs("div", { className: "flex items-center gap-2 rounded-md bg-green-50 dark:bg-green-950/20 p-3 text-sm text-green-700 dark:text-green-400", children: [_jsx(Check, { className: "h-4 w-4 shrink-0" }), "Todos los registros fueron importados correctamente."] })), importResult.errors.length > 0 && (_jsx("div", { className: "border rounded-md max-h-60 overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-16", children: "Fila" }), _jsx(TableHead, { children: "Campo" }), _jsx(TableHead, { children: "Error" })] }) }), _jsx(TableBody, { children: importResult.errors.map((error, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: error.row }), _jsx(TableCell, { className: "text-sm", children: error.field }), _jsx(TableCell, { className: "text-sm text-destructive", children: error.message })] }, idx))) })] }) }))] }))] }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0", children: [step === 'upload' && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: handleClose, children: "Cancelar" }), _jsxs(Button, { onClick: handleValidate, disabled: !file || validating, children: [validating && (_jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" })), validating ? 'Validando...' : 'Validar'] })] })), step === 'validation' && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => setStep('upload'), disabled: importing, children: "Atras" }), _jsxs(Button, { onClick: handleImport, disabled: importing ||
136
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden", children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsxs(DialogTitle, { children: ["Importar ", metadata.title] }), _jsx(DialogDescription, { children: stepTitle[step] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto p-6", children: [step === 'upload' && (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsxs(Button, { variant: "link", className: "px-0 h-auto text-sm", onClick: handleDownloadTemplate, children: [_jsx(FileDown, { className: "h-4 w-4 mr-1" }), "Descargar plantilla"] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "La plantilla trae las columnas, un ejemplo y las instrucciones. Borra la fila de ejemplo antes de subir." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "import-file", className: "text-sm font-medium", children: "Archivo" }), _jsx(Input, { ref: fileInputRef, id: "import-file", type: "file", accept: ".xlsx,.xls,.csv,.json", onChange: handleFileChange, className: "cursor-pointer" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Formatos aceptados: Excel, CSV, JSON" })] })] })), step === 'validation' && validationResult && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { children: [_jsx("strong", { children: validationResult.valid }), " valido(s)"] })] }), validationResult.errors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" }), _jsxs("span", { children: [_jsx("strong", { children: validationResult.errors.length }), " error(es)"] })] }))] }), validationResult.skipped > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Se ignoraron ", validationResult.skipped, " fila(s) de ejemplo de la plantilla."] })), validationResult.errors.length > 0 && (_jsx("div", { className: "border rounded-md max-h-60 overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-16", children: "Fila" }), _jsx(TableHead, { children: "Campo" }), _jsx(TableHead, { children: "Error" })] }) }), _jsx(TableBody, { children: validationResult.errors.map((error, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: error.row }), _jsx(TableCell, { className: "text-sm", children: errorColumn(error) }), _jsx(TableCell, { className: "text-sm text-destructive", children: errorMessage(error) })] }, idx))) })] }) })), importing && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { value: progress }), _jsxs("p", { className: "text-xs text-muted-foreground text-center", children: ["Importando... ", progress > 0 ? `${progress}%` : ''] })] }))] })), step === 'results' && importResult && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [importResult.created > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { children: [_jsx("strong", { children: importResult.created }), " creado(s)"] })] })), importResult.errors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" }), _jsxs("span", { children: [_jsx("strong", { children: importResult.errors.length }), " error(es)"] })] }))] }), importResult.created > 0 && importResult.errors.length === 0 && (_jsxs("div", { className: "flex items-center gap-2 rounded-md bg-green-50 dark:bg-green-950/20 p-3 text-sm text-green-700 dark:text-green-400", children: [_jsx(Check, { className: "h-4 w-4 shrink-0" }), "Todos los registros fueron importados correctamente."] })), importResult.errors.length > 0 && (_jsx("div", { className: "border rounded-md max-h-60 overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-16", children: "Fila" }), _jsx(TableHead, { children: "Campo" }), _jsx(TableHead, { children: "Error" })] }) }), _jsx(TableBody, { children: importResult.errors.map((error, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: error.row }), _jsx(TableCell, { className: "text-sm", children: errorColumn(error) }), _jsx(TableCell, { className: "text-sm text-destructive", children: errorMessage(error) })] }, idx))) })] }) }))] }))] }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0", children: [step === 'upload' && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: handleClose, children: "Cancelar" }), _jsxs(Button, { onClick: handleValidate, disabled: !file || validating, children: [validating && (_jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" })), validating ? 'Validando...' : 'Validar'] })] })), step === 'validation' && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => setStep('upload'), disabled: importing, children: "Atras" }), _jsxs(Button, { onClick: handleImport, disabled: importing ||
126
137
  (validationResult !== null && validationResult.valid === 0), children: [importing && (_jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" })), importing ? 'Importando...' : 'Importar'] })] })), step === 'results' && (_jsx(Button, { onClick: handleClose, children: "Cerrar" }))] })] }) }));
127
138
  }
@@ -53,7 +53,7 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
53
53
  // because visibleFields is already visibility-filtered). Without a layout
54
54
  // this is a single default group → the render below collapses to the legacy
55
55
  // flat grid, byte-for-byte.
56
- const groups = useMemo(() => groupFieldsBySection(visibleFields, formLayout), [visibleFields, formLayout]);
56
+ const groups = useMemo(() => groupFieldsBySection(visibleFields, formLayout, values), [visibleFields, formLayout, values]);
57
57
  const isSteps = formLayout?.mode === 'steps' && groups.length > 1;
58
58
  // Wizard step cursor (steps mode only). Clamped whenever the group count
59
59
  // shrinks (a visible_when flip can empty a trailing step's section).
@@ -10,6 +10,15 @@ export interface DynamicTableProps {
10
10
  */
11
11
  mutationEndpoint?: string;
12
12
  enableUrlSync?: boolean;
13
+ /**
14
+ * Hide the import action on THIS view even when the model supports it.
15
+ * A role-scoped view (e.g. a rep seeing only their own records) usually
16
+ * wants the table without a bulk-import entry point, while the same model
17
+ * keeps it on the admin view. Absent → the model's metadata decides.
18
+ */
19
+ hideImport?: boolean;
20
+ /** Hide the export action on this view. See `hideImport`. */
21
+ hideExport?: boolean;
13
22
  hiddenColumns?: string[];
14
23
  onAction?: (action: string, row: any) => void;
15
24
  /**
@@ -64,5 +73,5 @@ export interface DynamicTableProps {
64
73
  */
65
74
  infiniteScroll?: boolean;
66
75
  }
67
- export declare function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync, hiddenColumns, onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns, getDynamicColumns, timeZone, currency, pagination: paginationMode, infiniteScroll: infiniteScrollProp, }: DynamicTableProps): import("react").JSX.Element;
76
+ export declare function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync, hideImport, hideExport, hiddenColumns, onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns, getDynamicColumns, timeZone, currency, pagination: paginationMode, infiniteScroll: infiniteScrollProp, }: DynamicTableProps): import("react").JSX.Element;
68
77
  //# sourceMappingURL=dynamic-table.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AA2FnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,GAC7C,EAAE,iBAAiB,+BAuuCnB"}
1
+ {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AA2FnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,GAC7C,EAAE,iBAAiB,+BAgvCnB"}
@@ -103,7 +103,7 @@ function writeStoredPageSize(model, size) {
103
103
  // quota / private mode — persistence is a nicety, never fatal
104
104
  }
105
105
  }
106
- export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync = true, hiddenColumns = [], onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns = [], getDynamicColumns = defaultGetDynamicColumns, timeZone, currency, pagination: paginationMode, infiniteScroll: infiniteScrollProp = false, }) {
106
+ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync = true, hideImport, hideExport, hiddenColumns = [], onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns = [], getDynamicColumns = defaultGetDynamicColumns, timeZone, currency, pagination: paginationMode, infiniteScroll: infiniteScrollProp = false, }) {
107
107
  // The explicit `pagination` prop wins; the legacy `infiniteScroll` boolean
108
108
  // still selects the mode when `pagination` is absent (back-compat).
109
109
  const infiniteScroll = paginationMode ? paginationMode === 'infinite' : infiniteScrollProp;
@@ -519,6 +519,13 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
519
519
  return metadata;
520
520
  return gateTableMetadata(metadata, model, can, (key, fallback) => t(key, { defaultValue: fallback }));
521
521
  }, [metadata, permissionsActive, can, model, t]);
522
+ // Importing is offered when the host opts in explicitly (`canImport`) or,
523
+ // when it says nothing, whenever the kernel served an import spec for the
524
+ // model — which it does for every model with at least one importable form
525
+ // field. That makes the flow work out of the box instead of requiring each
526
+ // host to flip a flag per view.
527
+ const importEnabled = !hideImport && (viewMetadata?.canImport ?? Boolean(viewMetadata?.import?.columns?.length));
528
+ const exportEnabled = !hideExport && Boolean(viewMetadata?.canExport);
522
529
  const buildFilterParams = useCallback(() => {
523
530
  const params = {};
524
531
  if (sorting.length > 0) {
@@ -1028,7 +1035,7 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
1028
1035
  if (!metadata) {
1029
1036
  return _jsx("div", { className: "text-center text-muted-foreground py-8", children: "Error al cargar la configuraci\u00F3n de la tabla." });
1030
1037
  }
1031
- return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [viewMetadata?.canExport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), viewMetadata?.canImport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsxs("div", { ref: infDesktopRoot, className: 'hidden sm:block flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: [_jsxs(Table, { noWrapper: true, className: cn('min-w-max w-full', aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && 'h-full'), children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
1038
+ return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [exportEnabled && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), importEnabled && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsxs("div", { ref: infDesktopRoot, className: 'hidden sm:block flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: [_jsxs(Table, { noWrapper: true, className: cn('min-w-max w-full', aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && 'h-full'), children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
1032
1039
  const isActionsColumn = header.id === 'actions';
1033
1040
  return (_jsx(TableHead, { colSpan: header.colSpan, style: header.column.columnDef.size ? { width: header.column.columnDef.size } : undefined, className: cn('bg-card border-b h-10', isActionsColumn && 'sticky right-0 z-20 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
1034
1041
  }) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : table.getRowModel().rows?.length ? (_jsxs(_Fragment, { children: [table.getRowModel().rows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', className: cn(onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: row.getVisibleCells().map((cell) => {
@@ -1065,5 +1072,5 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
1065
1072
  defaultValue: '{{count}} de {{total}}',
1066
1073
  count: data.length,
1067
1074
  total: rowCount,
1068
- }) }))) : (_jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions })) })] }), rowActionDialogs, _jsx(AlertDialog, { open: showBulkDeleteConfirm, onOpenChange: (open) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: isBulkDeleting ? 'Eliminando registros...' : '¿Eliminar múltiples registros?' }), _jsx(AlertDialogDescription, { children: isBulkDeleting ? (_jsxs("div", { className: "space-y-4 mt-4", children: [_jsx(Progress, { value: (bulkDeleteProgress / bulkDeleteTotal) * 100 }), _jsxs("p", { className: "text-center text-sm", children: ["Procesando ", bulkDeleteProgress, " de ", bulkDeleteTotal, " registros..."] })] })) : (_jsxs(_Fragment, { children: ["Esta acci\u00F3n no se puede deshacer. Se eliminar\u00E1n permanentemente ", _jsx("strong", { children: Object.keys(rowSelection).length }), " registro(s) de nuestros servidores."] })) })] }), !isBulkDeleting && (_jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmBulkDelete(); }, className: "bg-red-600 hover:bg-red-700", children: "Eliminar todos" })] }))] }) }), viewMetadata?.canExport && (_jsx(ExportDialog, { open: exportOpen, onOpenChange: setExportOpen, model: model, metadata: metadata, currentFilters: buildFilterParams(), hasActiveFilters: hasActiveFilters })), viewMetadata?.canImport && (_jsx(ImportDialog, { open: importOpen, onOpenChange: setImportOpen, model: model, metadata: metadata, onImported: handleRefresh })), _jsx(DataTableBulkActions, { table: table, entityName: "registro", children: _jsxs(Button, { variant: "destructive", size: "sm", className: "h-8", onClick: () => setShowBulkDeleteConfirm(true), children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5" }), " Eliminar"] }) })] }));
1075
+ }) }))) : (_jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions })) })] }), rowActionDialogs, _jsx(AlertDialog, { open: showBulkDeleteConfirm, onOpenChange: (open) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: isBulkDeleting ? 'Eliminando registros...' : '¿Eliminar múltiples registros?' }), _jsx(AlertDialogDescription, { children: isBulkDeleting ? (_jsxs("div", { className: "space-y-4 mt-4", children: [_jsx(Progress, { value: (bulkDeleteProgress / bulkDeleteTotal) * 100 }), _jsxs("p", { className: "text-center text-sm", children: ["Procesando ", bulkDeleteProgress, " de ", bulkDeleteTotal, " registros..."] })] })) : (_jsxs(_Fragment, { children: ["Esta acci\u00F3n no se puede deshacer. Se eliminar\u00E1n permanentemente ", _jsx("strong", { children: Object.keys(rowSelection).length }), " registro(s) de nuestros servidores."] })) })] }), !isBulkDeleting && (_jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmBulkDelete(); }, className: "bg-red-600 hover:bg-red-700", children: "Eliminar todos" })] }))] }) }), exportEnabled && (_jsx(ExportDialog, { open: exportOpen, onOpenChange: setExportOpen, model: model, metadata: metadata, currentFilters: buildFilterParams(), hasActiveFilters: hasActiveFilters })), importEnabled && (_jsx(ImportDialog, { open: importOpen, onOpenChange: setImportOpen, model: model, metadata: metadata, onImported: handleRefresh })), _jsx(DataTableBulkActions, { table: table, entityName: "registro", children: _jsxs(Button, { variant: "destructive", size: "sm", className: "h-8", onClick: () => setShowBulkDeleteConfirm(true), children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5" }), " Eliminar"] }) })] }));
1069
1076
  }
@@ -1,3 +1,4 @@
1
+ import type { VisibleWhen } from './types';
1
2
  /** One declared section of a model form. `title`/`description` arrive already
2
3
  * localized from the kernel and are rendered verbatim. */
3
4
  export interface FormSection {
@@ -6,6 +7,12 @@ export interface FormSection {
6
7
  description?: string;
7
8
  /** Sections mode only: render this section collapsed on first paint. */
8
9
  collapsed?: boolean;
10
+ /** Section-level visibility gate (kernel v0.84.0). When present and its
11
+ * predicate evaluates false against the live form values, the whole section
12
+ * is omitted (and in steps mode its wizard step drops from the sequence).
13
+ * Tolerates the camelCase alias, same as fields. */
14
+ visible_when?: VisibleWhen;
15
+ visibleWhen?: VisibleWhen;
9
16
  }
10
17
  /** Model-level layout directive. `mode` defaults to `"sections"`. */
11
18
  export interface FormLayout {
@@ -47,8 +54,15 @@ export declare function getFieldSection(field: {
47
54
  * section whose only members are hidden by `visible_when` never renders an
48
55
  * empty shell (and never yields an empty wizard step). Because the caller
49
56
  * passes the already visibility-filtered list, this falls out for free.
57
+ * - A section declaring its OWN `visible_when` is dropped whole (before its
58
+ * fields are even collected) whenever the predicate evaluates false against
59
+ * `values`, reusing the SAME evaluator the fields use. In steps mode the
60
+ * caller derives the wizard sequence from these groups, so a hidden section
61
+ * simply never becomes a step. Section-less / unknown-section fields are
62
+ * never gated by any section predicate. Without a section `visible_when`
63
+ * (or without `values`), behaviour is byte-for-byte the legacy path.
50
64
  */
51
65
  export declare function groupFieldsBySection<F extends {
52
66
  section?: string;
53
- }>(fields: F[], layout: FormLayout | undefined): FieldGroup<F>[];
67
+ }>(fields: F[], layout: FormLayout | undefined, values?: Record<string, any> | null): FieldGroup<F>[];
54
68
  //# sourceMappingURL=form-layout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-layout.d.ts","sourceRoot":"","sources":["../src/form-layout.ts"],"names":[],"mappings":"AAgBA;0DAC0D;AAC1D,MAAM,WAAW,WAAW;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,SAAS,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,qEAAqE;AACrE,MAAM,WAAW,UAAU;IACvB,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAA;IAC3B,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;CAC3B;AAED;oDACoD;AACpD,MAAM,WAAW,UAAU,CAAC,CAAC;IACzB,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,kFAAkF;IAClF,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,CAAC,EAAE,CAAA;CACd;AAED,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,gBAAgB,CAAA;AAEhD,yEAAyE;AACzE,wBAAgB,eAAe,CAC3B,KAAK,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GACjE,MAAM,GAAG,SAAS,CAIpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EAC/D,MAAM,EAAE,CAAC,EAAE,EACX,MAAM,EAAE,UAAU,GAAG,SAAS,GAC/B,UAAU,CAAC,CAAC,CAAC,EAAE,CAsCjB"}
1
+ {"version":3,"file":"form-layout.d.ts","sourceRoot":"","sources":["../src/form-layout.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAG1C;0DAC0D;AAC1D,MAAM,WAAW,WAAW;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;wDAGoD;IACpD,YAAY,CAAC,EAAE,WAAW,CAAA;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED,qEAAqE;AACrE,MAAM,WAAW,UAAU;IACvB,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAA;IAC3B,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;CAC3B;AAED;oDACoD;AACpD,MAAM,WAAW,UAAU,CAAC,CAAC;IACzB,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,kFAAkF;IAClF,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,CAAC,EAAE,CAAA;CACd;AAED,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,gBAAgB,CAAA;AAEhD,yEAAyE;AACzE,wBAAgB,eAAe,CAC3B,KAAK,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GACjE,MAAM,GAAG,SAAS,CAIpB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EAC/D,MAAM,EAAE,CAAC,EAAE,EACX,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GACpC,UAAU,CAAC,CAAC,CAAC,EAAE,CAoDjB"}
@@ -13,6 +13,7 @@
13
13
  // (`dynamic-form.tsx`, `dialogs/dynamic-record.tsx`) share one tested
14
14
  // implementation and the modal-render machinery never has to be booted under
15
15
  // happy-dom just to assert the grouping (same approach as PR #673).
16
+ import { evaluateVisibleWhen, getVisibleWhen } from './dynamic-form-schema';
16
17
  /** The synthetic key of the orphan group (fields with no / unknown `section`). */
17
18
  export const DEFAULT_SECTION_KEY = '__default__';
18
19
  /** Reads a field's `section` reference, tolerating a camelCase alias. */
@@ -38,12 +39,27 @@ export function getFieldSection(field) {
38
39
  * section whose only members are hidden by `visible_when` never renders an
39
40
  * empty shell (and never yields an empty wizard step). Because the caller
40
41
  * passes the already visibility-filtered list, this falls out for free.
42
+ * - A section declaring its OWN `visible_when` is dropped whole (before its
43
+ * fields are even collected) whenever the predicate evaluates false against
44
+ * `values`, reusing the SAME evaluator the fields use. In steps mode the
45
+ * caller derives the wizard sequence from these groups, so a hidden section
46
+ * simply never becomes a step. Section-less / unknown-section fields are
47
+ * never gated by any section predicate. Without a section `visible_when`
48
+ * (or without `values`), behaviour is byte-for-byte the legacy path.
41
49
  */
42
- export function groupFieldsBySection(fields, layout) {
50
+ export function groupFieldsBySection(fields, layout, values) {
43
51
  if (!layout?.sections?.length) {
44
52
  return [{ key: DEFAULT_SECTION_KEY, isDefault: true, fields }];
45
53
  }
46
- const known = new Set(layout.sections.map((s) => s.key));
54
+ // Sections whose own `visible_when` predicate evaluates false are hidden
55
+ // WHOLESALE: the section never emits a group/step AND its member fields are
56
+ // dropped (they must not leak into the default group). Reuses the same
57
+ // field-level evaluator — no duplicated logic. A `declared` set keeps a
58
+ // field targeting a hidden section from being treated as "unknown section"
59
+ // and orphaned.
60
+ const declared = new Set(layout.sections.map((s) => s.key));
61
+ const visibleSections = layout.sections.filter((s) => evaluateVisibleWhen(getVisibleWhen(s), values));
62
+ const known = new Set(visibleSections.map((s) => s.key));
47
63
  const bySection = new Map();
48
64
  const orphans = [];
49
65
  for (const f of fields) {
@@ -55,6 +71,10 @@ export function groupFieldsBySection(fields, layout) {
55
71
  else
56
72
  bySection.set(sec, [f]);
57
73
  }
74
+ else if (sec && declared.has(sec)) {
75
+ // Belongs to a declared-but-hidden section → drop the field.
76
+ continue;
77
+ }
58
78
  else {
59
79
  orphans.push(f);
60
80
  }
@@ -64,7 +84,7 @@ export function groupFieldsBySection(fields, layout) {
64
84
  if (orphans.length) {
65
85
  groups.push({ key: DEFAULT_SECTION_KEY, isDefault: true, fields: orphans });
66
86
  }
67
- for (const s of layout.sections) {
87
+ for (const s of visibleSections) {
68
88
  const secFields = bySection.get(s.key);
69
89
  if (!secFields || secFields.length === 0)
70
90
  continue; // hide empty section
package/dist/types.d.ts CHANGED
@@ -1,3 +1,22 @@
1
+ /** One spreadsheet column of a model's import template. Mirrors the kernel's
2
+ * `modelbase.ImportColumn`. */
3
+ export interface ImportColumnMeta {
4
+ key: string;
5
+ header: string;
6
+ aliases?: string[];
7
+ required?: boolean;
8
+ type?: string;
9
+ example?: string;
10
+ hint?: string;
11
+ generator?: string;
12
+ }
13
+ /** A model's spreadsheet-import declaration. Mirrors `modelbase.ImportSpec`. */
14
+ export interface ImportSpecMeta {
15
+ columns: ImportColumnMeta[];
16
+ maxRows?: number;
17
+ sheetName?: string;
18
+ instructions?: string[];
19
+ }
1
20
  export interface TableMetadata {
2
21
  title: string;
3
22
  endpoint: string;
@@ -12,6 +31,16 @@ export interface TableMetadata {
12
31
  canExport?: boolean;
13
32
  canImport?: boolean;
14
33
  canCreate?: boolean;
34
+ /**
35
+ * The model's spreadsheet-import declaration, served by the kernel: the
36
+ * columns of the generated template and the headers accepted when parsing
37
+ * a filled file back in. The kernel derives it from the model's form
38
+ * fields when the model declares nothing, and omits the field entirely
39
+ * when the model has no importable column — so its presence is the signal
40
+ * that importing is meaningful for this model. Purely additive; older
41
+ * kernels omit it.
42
+ */
43
+ import?: ImportSpecMeta;
15
44
  /**
16
45
  * Child relations of this model, served by the kernel (>= v0.41.0). A
17
46
  * generic detail page renders one `DynamicRelation` panel per entry via
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "28.5.0",
3
+ "version": "28.7.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -111,3 +111,91 @@ describe('groupFieldsBySection — steps', () => {
111
111
  expect(groups.map(g => g.key)).toEqual(['who', 'pay'])
112
112
  })
113
113
  })
114
+
115
+ describe('groupFieldsBySection — section-level visible_when (kernel v0.84.0)', () => {
116
+ // A discriminator field `kind` gates the `company` section: only visible for
117
+ // business customers. Tolerate both snake_case and camelCase authoring.
118
+ const gatedLayout: FormLayout = {
119
+ mode: 'sections',
120
+ sections: [
121
+ { key: 'general', title: 'General' },
122
+ {
123
+ key: 'company',
124
+ title: 'Empresa',
125
+ visible_when: { field: 'kind', equals: 'business' },
126
+ },
127
+ ],
128
+ }
129
+
130
+ const fields: F[] = [
131
+ { key: 'name', section: 'general' },
132
+ { key: 'tax_id', section: 'company' },
133
+ ]
134
+
135
+ it('hides the section AND its fields when the predicate is false', () => {
136
+ const groups = groupFieldsBySection(fields, gatedLayout, { kind: 'person' })
137
+ // `company` never appears, and `tax_id` is not leaked into any group.
138
+ expect(groups.map(g => g.key)).toEqual(['general'])
139
+ const allFields = groups.flatMap(g => g.fields.map(f => f.key))
140
+ expect(allFields).toEqual(['name'])
141
+ })
142
+
143
+ it('shows the section when the predicate is satisfied', () => {
144
+ const groups = groupFieldsBySection(fields, gatedLayout, { kind: 'business' })
145
+ expect(groups.map(g => g.key)).toEqual(['general', 'company'])
146
+ expect(groups[1].fields.map(f => f.key)).toEqual(['tax_id'])
147
+ })
148
+
149
+ it('tolerates the camelCase visibleWhen alias', () => {
150
+ const camelLayout: FormLayout = {
151
+ mode: 'sections',
152
+ sections: [
153
+ { key: 'general', title: 'General' },
154
+ { key: 'company', visibleWhen: { field: 'kind', in: ['business'] } },
155
+ ],
156
+ }
157
+ expect(groupFieldsBySection(fields, camelLayout, { kind: 'person' }).map(g => g.key)).toEqual([
158
+ 'general',
159
+ ])
160
+ expect(groupFieldsBySection(fields, camelLayout, { kind: 'business' }).map(g => g.key)).toEqual([
161
+ 'general',
162
+ 'company',
163
+ ])
164
+ })
165
+
166
+ it('without values (or without a section predicate) behaves exactly like before', () => {
167
+ // No values passed → a predicated section reads its gate field as '' and
168
+ // stays hidden (equals miss), while an ungated layout is untouched.
169
+ const plain = groupFieldsBySection(fields, {
170
+ mode: 'sections',
171
+ sections: [{ key: 'general' }, { key: 'company' }],
172
+ })
173
+ expect(plain.map(g => g.key)).toEqual(['general', 'company'])
174
+ })
175
+
176
+ it('drops a wizard step whose section is gated off, preserving navigable order', () => {
177
+ const stepsGated: FormLayout = {
178
+ mode: 'steps',
179
+ sections: [
180
+ { key: 'who', title: 'Cliente' },
181
+ {
182
+ key: 'company',
183
+ title: 'Empresa',
184
+ visible_when: { field: 'kind', equals: 'business' },
185
+ },
186
+ { key: 'pay', title: 'Pago' },
187
+ ],
188
+ }
189
+ const stepFields: F[] = [
190
+ { key: 'customer_id', section: 'who' },
191
+ { key: 'tax_id', section: 'company' },
192
+ { key: 'amount', section: 'pay' },
193
+ ]
194
+ // person → company step removed: wizard is who → pay, submit still on last.
195
+ const hidden = groupFieldsBySection(stepFields, stepsGated, { kind: 'person' })
196
+ expect(hidden.map(g => g.key)).toEqual(['who', 'pay'])
197
+ // business → all three steps in order.
198
+ const shown = groupFieldsBySection(stepFields, stepsGated, { kind: 'business' })
199
+ expect(shown.map(g => g.key)).toEqual(['who', 'company', 'pay'])
200
+ })
201
+ })
@@ -884,7 +884,7 @@ export function DynamicRecordDialog({
884
884
  // their section. Empty sections drop out for free. Steps mode only drives a
885
885
  // wizard in editable modes — view mode always stacks the sections.
886
886
  const formLayout = modalMeta?.form_layout ?? modalMeta?.formLayout
887
- const groups = groupFieldsBySection(visibleFields, formLayout)
887
+ const groups = groupFieldsBySection(visibleFields, formLayout, formValues)
888
888
  const isSteps = isEditable && formLayout?.mode === 'steps' && groups.length > 1
889
889
  const clampedStep = Math.min(stepIndex, Math.max(groups.length - 1, 0))
890
890
  const isLastStep = clampedStep === groups.length - 1
@@ -1,5 +1,8 @@
1
- // ImportDialog — three-step CSV/JSON import flow (upload → validate → import
2
- // with per-row error report). Axios-like client is provided by <ApiProvider>.
1
+ // ImportDialog — three-step XLSX/CSV/JSON import flow (upload → validate →
2
+ // import with per-row error report). The template, the accepted columns and
3
+ // the validation rules all come from the model's ImportSpec served by the
4
+ // kernel, so this dialog carries no per-model knowledge. Axios-like client is
5
+ // provided by <ApiProvider>.
3
6
  import { useState, useEffect, useRef } from 'react'
4
7
  import {
5
8
  Dialog,
@@ -32,20 +35,37 @@ interface ImportDialogProps {
32
35
  onImported?: () => void
33
36
  }
34
37
 
35
- interface ValidationError {
38
+ // RowError is one problem the backend reports against one spreadsheet row.
39
+ // The kernel returns `{row, column, message}` on validate and
40
+ // `{row, column, error}` on import; older backends returned `{row, field,
41
+ // message}`. Normalising here keeps the dialog working against every one of
42
+ // them instead of rendering blank cells.
43
+ interface RowError {
36
44
  row: number
37
- field: string
38
- message: string
45
+ column?: string
46
+ field?: string
47
+ message?: string
48
+ error?: string
49
+ }
50
+
51
+ function errorColumn(e: RowError): string {
52
+ return e.column ?? e.field ?? ''
53
+ }
54
+
55
+ function errorMessage(e: RowError): string {
56
+ return e.message ?? e.error ?? ''
39
57
  }
40
58
 
41
59
  interface ValidationResult {
42
60
  valid: number
43
- errors: ValidationError[]
61
+ skipped: number
62
+ errors: RowError[]
44
63
  }
45
64
 
46
65
  interface ImportResult {
47
66
  created: number
48
- errors: ValidationError[]
67
+ skipped: number
68
+ errors: RowError[]
49
69
  }
50
70
 
51
71
  type Step = 'upload' | 'validation' | 'results'
@@ -93,7 +113,7 @@ export function ImportDialog({
93
113
  const url = window.URL.createObjectURL(response.data)
94
114
  const link = document.createElement('a')
95
115
  link.href = url
96
- link.download = `${model}-plantilla.csv`
116
+ link.download = `${model}-plantilla.xlsx`
97
117
  document.body.appendChild(link)
98
118
  link.click()
99
119
  document.body.removeChild(link)
@@ -120,7 +140,8 @@ export function ImportDialog({
120
140
 
121
141
  const data = res.data?.data ?? res.data
122
142
  setValidationResult({
123
- valid: data.valid ?? 0,
143
+ valid: data.rowCount ?? data.valid ?? 0,
144
+ skipped: data.skipped ?? 0,
124
145
  errors: data.errors ?? [],
125
146
  })
126
147
  setStep('validation')
@@ -157,7 +178,8 @@ export function ImportDialog({
157
178
  const data = res.data?.data ?? res.data
158
179
  setImportResult({
159
180
  created: data.created ?? 0,
160
- errors: data.errors ?? [],
181
+ skipped: data.skipped ?? 0,
182
+ errors: data.failures ?? data.errors ?? [],
161
183
  })
162
184
  setStep('results')
163
185
 
@@ -206,10 +228,11 @@ export function ImportDialog({
206
228
  onClick={handleDownloadTemplate}
207
229
  >
208
230
  <FileDown className="h-4 w-4 mr-1" />
209
- Descargar plantilla CSV
231
+ Descargar plantilla
210
232
  </Button>
211
233
  <p className="text-xs text-muted-foreground mt-1">
212
- Descarga la plantilla para asegurar el formato correcto.
234
+ La plantilla trae las columnas, un ejemplo y las
235
+ instrucciones. Borra la fila de ejemplo antes de subir.
213
236
  </p>
214
237
  </div>
215
238
 
@@ -221,12 +244,12 @@ export function ImportDialog({
221
244
  ref={fileInputRef}
222
245
  id="import-file"
223
246
  type="file"
224
- accept=".csv,.json"
247
+ accept=".xlsx,.xls,.csv,.json"
225
248
  onChange={handleFileChange}
226
249
  className="cursor-pointer"
227
250
  />
228
251
  <p className="text-xs text-muted-foreground">
229
- Formatos aceptados: CSV, JSON
252
+ Formatos aceptados: Excel, CSV, JSON
230
253
  </p>
231
254
  </div>
232
255
  </div>
@@ -251,6 +274,13 @@ export function ImportDialog({
251
274
  )}
252
275
  </div>
253
276
 
277
+ {validationResult.skipped > 0 && (
278
+ <p className="text-xs text-muted-foreground">
279
+ Se ignoraron {validationResult.skipped} fila(s) de ejemplo
280
+ de la plantilla.
281
+ </p>
282
+ )}
283
+
254
284
  {validationResult.errors.length > 0 && (
255
285
  <div className="border rounded-md max-h-60 overflow-auto">
256
286
  <Table>
@@ -268,10 +298,10 @@ export function ImportDialog({
268
298
  {error.row}
269
299
  </TableCell>
270
300
  <TableCell className="text-sm">
271
- {error.field}
301
+ {errorColumn(error)}
272
302
  </TableCell>
273
303
  <TableCell className="text-sm text-destructive">
274
- {error.message}
304
+ {errorMessage(error)}
275
305
  </TableCell>
276
306
  </TableRow>
277
307
  ))}
@@ -336,10 +366,10 @@ export function ImportDialog({
336
366
  {error.row}
337
367
  </TableCell>
338
368
  <TableCell className="text-sm">
339
- {error.field}
369
+ {errorColumn(error)}
340
370
  </TableCell>
341
371
  <TableCell className="text-sm text-destructive">
342
- {error.message}
372
+ {errorMessage(error)}
343
373
  </TableCell>
344
374
  </TableRow>
345
375
  ))}
@@ -113,8 +113,8 @@ export function DynamicForm({
113
113
  // this is a single default group → the render below collapses to the legacy
114
114
  // flat grid, byte-for-byte.
115
115
  const groups = useMemo(
116
- () => groupFieldsBySection(visibleFields, formLayout),
117
- [visibleFields, formLayout],
116
+ () => groupFieldsBySection(visibleFields, formLayout, values),
117
+ [visibleFields, formLayout, values],
118
118
  )
119
119
  const isSteps = formLayout?.mode === 'steps' && groups.length > 1
120
120
 
@@ -165,6 +165,15 @@ export interface DynamicTableProps {
165
165
  */
166
166
  mutationEndpoint?: string
167
167
  enableUrlSync?: boolean
168
+ /**
169
+ * Hide the import action on THIS view even when the model supports it.
170
+ * A role-scoped view (e.g. a rep seeing only their own records) usually
171
+ * wants the table without a bulk-import entry point, while the same model
172
+ * keeps it on the admin view. Absent → the model's metadata decides.
173
+ */
174
+ hideImport?: boolean
175
+ /** Hide the export action on this view. See `hideImport`. */
176
+ hideExport?: boolean
168
177
  hiddenColumns?: string[]
169
178
  onAction?: (action: string, row: any) => void
170
179
  /**
@@ -225,6 +234,8 @@ export function DynamicTable({
225
234
  endpoint,
226
235
  mutationEndpoint,
227
236
  enableUrlSync = true,
237
+ hideImport,
238
+ hideExport,
228
239
  hiddenColumns = [],
229
240
  onAction,
230
241
  onRowClick,
@@ -644,6 +655,15 @@ export function DynamicTable({
644
655
  return gateTableMetadata(metadata, model, can, (key, fallback) => t(key, { defaultValue: fallback }))
645
656
  }, [metadata, permissionsActive, can, model, t])
646
657
 
658
+ // Importing is offered when the host opts in explicitly (`canImport`) or,
659
+ // when it says nothing, whenever the kernel served an import spec for the
660
+ // model — which it does for every model with at least one importable form
661
+ // field. That makes the flow work out of the box instead of requiring each
662
+ // host to flip a flag per view.
663
+ const importEnabled =
664
+ !hideImport && (viewMetadata?.canImport ?? Boolean(viewMetadata?.import?.columns?.length))
665
+ const exportEnabled = !hideExport && Boolean(viewMetadata?.canExport)
666
+
647
667
  const buildFilterParams = useCallback(() => {
648
668
  const params: Record<string, any> = {}
649
669
  if (sorting.length > 0) {
@@ -1202,12 +1222,12 @@ export function DynamicTable({
1202
1222
  onBulkDelete={() => setShowBulkDeleteConfirm(true)}
1203
1223
  extraActions={
1204
1224
  <>
1205
- {viewMetadata?.canExport && (
1225
+ {exportEnabled && (
1206
1226
  <Button variant="outline" size="sm" className="h-8" onClick={() => setExportOpen(true)}>
1207
1227
  <Download className="h-4 w-4 mr-1" /> Exportar
1208
1228
  </Button>
1209
1229
  )}
1210
- {viewMetadata?.canImport && (
1230
+ {importEnabled && (
1211
1231
  <Button variant="outline" size="sm" className="h-8" onClick={() => setImportOpen(true)}>
1212
1232
  <Upload className="h-4 w-4 mr-1" /> Importar
1213
1233
  </Button>
@@ -1478,10 +1498,10 @@ export function DynamicTable({
1478
1498
  </AlertDialogContent>
1479
1499
  </AlertDialog>
1480
1500
 
1481
- {viewMetadata?.canExport && (
1501
+ {exportEnabled && (
1482
1502
  <ExportDialog open={exportOpen} onOpenChange={setExportOpen} model={model} metadata={metadata} currentFilters={buildFilterParams()} hasActiveFilters={hasActiveFilters} />
1483
1503
  )}
1484
- {viewMetadata?.canImport && (
1504
+ {importEnabled && (
1485
1505
  <ImportDialog open={importOpen} onOpenChange={setImportOpen} model={model} metadata={metadata} onImported={handleRefresh} />
1486
1506
  )}
1487
1507
  <DataTableBulkActions table={table} entityName="registro">
@@ -14,6 +14,9 @@
14
14
  // implementation and the modal-render machinery never has to be booted under
15
15
  // happy-dom just to assert the grouping (same approach as PR #673).
16
16
 
17
+ import type { VisibleWhen } from './types'
18
+ import { evaluateVisibleWhen, getVisibleWhen } from './dynamic-form-schema'
19
+
17
20
  /** One declared section of a model form. `title`/`description` arrive already
18
21
  * localized from the kernel and are rendered verbatim. */
19
22
  export interface FormSection {
@@ -22,6 +25,12 @@ export interface FormSection {
22
25
  description?: string
23
26
  /** Sections mode only: render this section collapsed on first paint. */
24
27
  collapsed?: boolean
28
+ /** Section-level visibility gate (kernel v0.84.0). When present and its
29
+ * predicate evaluates false against the live form values, the whole section
30
+ * is omitted (and in steps mode its wizard step drops from the sequence).
31
+ * Tolerates the camelCase alias, same as fields. */
32
+ visible_when?: VisibleWhen
33
+ visibleWhen?: VisibleWhen
25
34
  }
26
35
 
27
36
  /** Model-level layout directive. `mode` defaults to `"sections"`. */
@@ -71,16 +80,35 @@ export function getFieldSection(
71
80
  * section whose only members are hidden by `visible_when` never renders an
72
81
  * empty shell (and never yields an empty wizard step). Because the caller
73
82
  * passes the already visibility-filtered list, this falls out for free.
83
+ * - A section declaring its OWN `visible_when` is dropped whole (before its
84
+ * fields are even collected) whenever the predicate evaluates false against
85
+ * `values`, reusing the SAME evaluator the fields use. In steps mode the
86
+ * caller derives the wizard sequence from these groups, so a hidden section
87
+ * simply never becomes a step. Section-less / unknown-section fields are
88
+ * never gated by any section predicate. Without a section `visible_when`
89
+ * (or without `values`), behaviour is byte-for-byte the legacy path.
74
90
  */
75
91
  export function groupFieldsBySection<F extends { section?: string }>(
76
92
  fields: F[],
77
93
  layout: FormLayout | undefined,
94
+ values?: Record<string, any> | null,
78
95
  ): FieldGroup<F>[] {
79
96
  if (!layout?.sections?.length) {
80
97
  return [{ key: DEFAULT_SECTION_KEY, isDefault: true, fields }]
81
98
  }
82
99
 
83
- const known = new Set(layout.sections.map((s) => s.key))
100
+ // Sections whose own `visible_when` predicate evaluates false are hidden
101
+ // WHOLESALE: the section never emits a group/step AND its member fields are
102
+ // dropped (they must not leak into the default group). Reuses the same
103
+ // field-level evaluator — no duplicated logic. A `declared` set keeps a
104
+ // field targeting a hidden section from being treated as "unknown section"
105
+ // and orphaned.
106
+ const declared = new Set(layout.sections.map((s) => s.key))
107
+ const visibleSections = layout.sections.filter((s) =>
108
+ evaluateVisibleWhen(getVisibleWhen(s), values),
109
+ )
110
+
111
+ const known = new Set(visibleSections.map((s) => s.key))
84
112
  const bySection = new Map<string, F[]>()
85
113
  const orphans: F[] = []
86
114
 
@@ -90,6 +118,9 @@ export function groupFieldsBySection<F extends { section?: string }>(
90
118
  const arr = bySection.get(sec)
91
119
  if (arr) arr.push(f)
92
120
  else bySection.set(sec, [f])
121
+ } else if (sec && declared.has(sec)) {
122
+ // Belongs to a declared-but-hidden section → drop the field.
123
+ continue
93
124
  } else {
94
125
  orphans.push(f)
95
126
  }
@@ -100,7 +131,7 @@ export function groupFieldsBySection<F extends { section?: string }>(
100
131
  if (orphans.length) {
101
132
  groups.push({ key: DEFAULT_SECTION_KEY, isDefault: true, fields: orphans })
102
133
  }
103
- for (const s of layout.sections) {
134
+ for (const s of visibleSections) {
104
135
  const secFields = bySection.get(s.key)
105
136
  if (!secFields || secFields.length === 0) continue // hide empty section
106
137
  groups.push({
package/src/types.ts CHANGED
@@ -1,6 +1,27 @@
1
1
  // Shared metadata shape consumed by every host. Some hosts add a `link`
2
2
  // action type with a `linkUrl` template — represented here as part of the
3
3
  // `type` union so the SDK can render it uniformly.
4
+ /** One spreadsheet column of a model's import template. Mirrors the kernel's
5
+ * `modelbase.ImportColumn`. */
6
+ export interface ImportColumnMeta {
7
+ key: string
8
+ header: string
9
+ aliases?: string[]
10
+ required?: boolean
11
+ type?: string
12
+ example?: string
13
+ hint?: string
14
+ generator?: string
15
+ }
16
+
17
+ /** A model's spreadsheet-import declaration. Mirrors `modelbase.ImportSpec`. */
18
+ export interface ImportSpecMeta {
19
+ columns: ImportColumnMeta[]
20
+ maxRows?: number
21
+ sheetName?: string
22
+ instructions?: string[]
23
+ }
24
+
4
25
  export interface TableMetadata {
5
26
  title: string
6
27
  endpoint: string
@@ -15,6 +36,16 @@ export interface TableMetadata {
15
36
  canExport?: boolean
16
37
  canImport?: boolean
17
38
  canCreate?: boolean
39
+ /**
40
+ * The model's spreadsheet-import declaration, served by the kernel: the
41
+ * columns of the generated template and the headers accepted when parsing
42
+ * a filled file back in. The kernel derives it from the model's form
43
+ * fields when the model declares nothing, and omits the field entirely
44
+ * when the model has no importable column — so its presence is the signal
45
+ * that importing is meaningful for this model. Purely additive; older
46
+ * kernels omit it.
47
+ */
48
+ import?: ImportSpecMeta
18
49
  /**
19
50
  * Child relations of this model, served by the kernel (>= v0.41.0). A
20
51
  * generic detail page renders one `DynamicRelation` panel per entry via