@asteby/metacore-runtime-react 20.1.6 → 21.0.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.
@@ -12,7 +12,6 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
12
12
  // `getDynamicColumns` prop (hosts retain ownership because the rendered
13
13
  // column cells are tightly coupled to their design system).
14
14
  import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
15
- import { useNavigate } from '@tanstack/react-router';
16
15
  import { useTranslation } from 'react-i18next';
17
16
  import { format } from 'date-fns';
18
17
  import { flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table';
@@ -26,17 +25,15 @@ import { useMetadataCache } from './metadata-cache';
26
25
  import { useApi, useCurrentBranch } from './api-context';
27
26
  import { defaultGetDynamicColumns, DATE_CELL_TYPES, aggregateOf, formatAggregateTotal } from './dynamic-columns';
28
27
  import { OptionsContext } from './options-context';
29
- import { ActionModalDispatcher } from './action-modal-dispatcher';
30
28
  import { getSearchableColumnKeys } from './column-visibility';
31
29
  import { useCan, usePermissionsActive, gateTableMetadata } from './permissions-context';
32
- import { DynamicRecordDialog } from './dialogs/dynamic-record';
30
+ import { useDynamicRowActions } from './dynamic-row-actions';
33
31
  import { ExportDialog } from './dialogs/export';
34
32
  import { ImportDialog } from './dialogs/import';
35
33
  export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColumns = [], onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns = [], getDynamicColumns = defaultGetDynamicColumns, timeZone, currency, }) {
36
34
  const { t, i18n } = useTranslation();
37
35
  const api = useApi();
38
36
  const currentBranch = useCurrentBranch();
39
- const navigate = useNavigate();
40
37
  const prevBranchId = useRef(currentBranch?.id);
41
38
  const { getMetadata, setMetadata: cacheMetadata } = useMetadataCache();
42
39
  const cachedMeta = getMetadata(model);
@@ -48,12 +45,8 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
48
45
  const [loading, setLoading] = useState(!cachedMeta);
49
46
  const [loadingData, setLoadingData] = useState(true);
50
47
  const [optionsMap, setOptionsMap] = useState(new Map());
51
- const [recordDialog, setRecordDialog] = useState({ open: false, mode: 'view', recordId: null });
52
- const [rowToDelete, setRowToDelete] = useState(null);
53
- const [isDeleting, setIsDeleting] = useState(false);
54
48
  const [exportOpen, setExportOpen] = useState(false);
55
49
  const [importOpen, setImportOpen] = useState(false);
56
- const [actionModal, setActionModal] = useState({ open: false, action: null, record: null });
57
50
  const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false);
58
51
  const [isBulkDeleting, setIsBulkDeleting] = useState(false);
59
52
  const [bulkDeleteProgress, setBulkDeleteProgress] = useState(0);
@@ -414,73 +407,15 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
414
407
  return () => clearTimeout(timeoutId);
415
408
  }, [fetchData, fetchAggregates, metadata]);
416
409
  const handleRefresh = useCallback(() => { fetchData(); fetchAggregates(); }, [fetchData, fetchAggregates]);
417
- const handleInternalAction = useCallback(async (action, row) => {
418
- if (action === 'delete') {
419
- setRowToDelete(row);
420
- return;
421
- }
422
- if (action === 'view' || action === 'edit') {
423
- if (onAction)
424
- await Promise.resolve(onAction(action, row));
425
- else
426
- setRecordDialog({ open: true, mode: action, recordId: row.id });
427
- return;
428
- }
429
- const linkDef = metadata?.actions?.find((a) => a.key === action && a.type === 'link');
430
- if (linkDef?.linkUrl) {
431
- const url = linkDef.linkUrl.replace(/\{(\w+)\}/g, (_, field) => String(row[field] ?? ''));
432
- navigate({ to: url });
433
- return;
434
- }
435
- const actionDef = metadata?.actions?.find((a) => a.key === action);
436
- if (actionDef && (actionDef.fields?.length || actionDef.confirm || actionDef.executable)) {
437
- setActionModal({
438
- open: true,
439
- action: {
440
- key: actionDef.key,
441
- label: actionDef.label,
442
- icon: actionDef.icon || 'Zap',
443
- color: actionDef.color,
444
- confirm: actionDef.confirm,
445
- confirmMessage: actionDef.confirmMessage,
446
- fields: actionDef.fields,
447
- requiresState: actionDef.requiresState,
448
- executable: actionDef.executable,
449
- },
450
- record: row,
451
- });
452
- return;
453
- }
454
- if (onAction) {
455
- await Promise.resolve(onAction(action, row));
456
- handleRefresh();
457
- }
458
- else
459
- handleRefresh();
460
- }, [onAction, handleRefresh, metadata, navigate]);
461
- const confirmDelete = async () => {
462
- if (!rowToDelete)
463
- return;
464
- setIsDeleting(true);
465
- try {
466
- const deleteEndpoint = endpoint ? `${endpoint}/${rowToDelete.id}` : `/data/${model}/${rowToDelete.id}`;
467
- const res = await api.delete(deleteEndpoint);
468
- if (res.data.success) {
469
- toast.success(res.data.message || 'Eliminado correctamente');
470
- handleRefresh();
471
- }
472
- else
473
- toast.error(res.data.message || 'Error al eliminar');
474
- }
475
- catch (error) {
476
- console.error('Error al eliminar', error);
477
- toast.error('Error al eliminar el registro');
478
- }
479
- finally {
480
- setIsDeleting(false);
481
- setRowToDelete(null);
482
- }
483
- };
410
+ // Per-row action dispatch (view/edit/delete/link/custom) + its dialogs live
411
+ // in the shared hook so DynamicKanban's card menu behaves identically.
412
+ const { handleInternalAction, dialogs: rowActionDialogs } = useDynamicRowActions({
413
+ model,
414
+ endpoint,
415
+ metadata,
416
+ onAction,
417
+ onRefresh: handleRefresh,
418
+ });
484
419
  const confirmBulkDelete = async () => {
485
420
  const selectedRows = table.getFilteredSelectedRowModel().rows;
486
421
  if (selectedRows.length === 0)
@@ -673,5 +608,5 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
673
608
  const label = typeof header === 'string' ? header : cell.column.id;
674
609
  return (_jsxs("div", { className: 'flex items-start justify-between gap-3 text-sm', children: [_jsx("span", { className: 'shrink-0 text-muted-foreground', children: label }), _jsx("span", { className: 'min-w-0 break-words text-right font-medium', children: flexRender(cell.column.columnDef.cell, cell.getContext()) })] }, cell.id));
675
610
  }), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', onClick: onRowClick ? (e) => e.stopPropagation() : undefined, children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
676
- })) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), _jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] })) }), _jsx("div", { className: 'shrink-0 pt-4', children: _jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions }) })] }), _jsx(AlertDialog, { open: !!rowToDelete, onOpenChange: (open) => !open && setRowToDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "\u00BFEst\u00E1 absolutamente seguro?" }), _jsx(AlertDialogDescription, { children: "Esta acci\u00F3n no se puede deshacer. Esto eliminar\u00E1 permanentemente el registro seleccionado de nuestros servidores." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isDeleting, children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmDelete(); }, className: "bg-red-600 hover:bg-red-700", disabled: isDeleting, children: isDeleting ? 'Eliminando...' : 'Eliminar' })] })] }) }), _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" })] }))] }) }), _jsx(DynamicRecordDialog, { open: recordDialog.open, onOpenChange: (open) => setRecordDialog((prev) => ({ ...prev, open })), mode: recordDialog.mode, model: model, recordId: recordDialog.recordId, endpoint: endpoint, onSaved: handleRefresh }), 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 })), actionModal.action && (_jsx(ActionModalDispatcher, { open: actionModal.open, onOpenChange: (open) => setActionModal((prev) => ({ ...prev, open })), action: actionModal.action, model: model, record: actionModal.record, endpoint: endpoint, onSuccess: 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"] }) })] }));
611
+ })) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), _jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] })) }), _jsx("div", { className: 'shrink-0 pt-4', children: _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"] }) })] }));
677
612
  }
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from './options-context';
3
3
  export * from './dynamic-table';
4
4
  export { DynamicKanban, type DynamicKanbanProps, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
5
5
  export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, type DynamicViewProps, } from './dynamic-view';
6
+ export { useDynamicFilters, type UseDynamicFiltersOptions, type UseDynamicFiltersResult, } from './use-dynamic-filters';
6
7
  export * from './dynamic-form';
7
8
  export { ActionModalDispatcher, type ActionModalProps, } from './action-modal-dispatcher';
8
9
  export { ModelActionToolbar, useModelActions, type ModelActionToolbarProps, type ActionPlacement, } from './model-action-toolbar';
@@ -10,7 +11,8 @@ export * from './addon-loader';
10
11
  export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, type AddonLayout, type AddonLayoutProviderProps, } from './addon-layout-context';
11
12
  export * from './slot';
12
13
  export * from './capability-gate';
13
- export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, type CanFn, type PermissionsProviderProps, } from './permissions-context';
14
+ export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, resolveRowActions, type CanFn, type PermissionsProviderProps, } from './permissions-context';
15
+ export { useDynamicRowActions, type UseDynamicRowActionsParams, type DynamicRowActions, } from './dynamic-row-actions';
14
16
  export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, normalizeCatalogGroups, flattenGroups, filterModuleGroups, type PermissionsManagerProps, type PermissionsCatalog, type GroupedPermissionsCatalog, type FlatPermissionsCatalog, type ModuleGroup, type PermissionModuleDef, type PermissionActionDef, type GeneralPermissionDef, type RoleDef, type RoleInput, } from './permissions-manager';
15
17
  export * from './org-runtime-context';
16
18
  export * from './org-runtime-provider';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export * from './options-context';
8
8
  export * from './dynamic-table';
9
9
  export { DynamicKanban, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
10
10
  export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, } from './dynamic-view';
11
+ export { useDynamicFilters, } from './use-dynamic-filters';
11
12
  export * from './dynamic-form';
12
13
  export { ActionModalDispatcher, } from './action-modal-dispatcher';
13
14
  export { ModelActionToolbar, useModelActions, } from './model-action-toolbar';
@@ -15,7 +16,8 @@ export * from './addon-loader';
15
16
  export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, } from './addon-layout-context';
16
17
  export * from './slot';
17
18
  export * from './capability-gate';
18
- export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, } from './permissions-context';
19
+ export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, resolveRowActions, } from './permissions-context';
20
+ export { useDynamicRowActions, } from './dynamic-row-actions';
19
21
  export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, normalizeCatalogGroups, flattenGroups, filterModuleGroups, } from './permissions-manager';
20
22
  export * from './org-runtime-context';
21
23
  export * from './org-runtime-provider';
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { TableMetadata } from './types';
2
+ import type { TableMetadata, ActionDefinition } from './types';
3
3
  /** Predicate answering "can the current user use this capability?". */
4
4
  export type CanFn = (capability: string) => boolean;
5
5
  export interface PermissionsProviderProps {
@@ -45,4 +45,18 @@ export declare function modelCapability(model: string, actionKey: string): strin
45
45
  * (`usePermissionsActive()`), otherwise pass the metadata through untouched.
46
46
  */
47
47
  export declare function gateTableMetadata(metadata: TableMetadata, model: string, can: CanFn, tx?: (i18nKey: string, fallback: string) => string): TableMetadata;
48
+ /**
49
+ * Resolves the per-row (`placement: 'row'`) action list for a model exactly the
50
+ * way DynamicTable's action column does, so the kanban card menu shows the SAME
51
+ * actions:
52
+ * - when a <PermissionsProvider> is active, the metadata is run through
53
+ * `gateTableMetadata` first (filters by capability + materializes the CRUD
54
+ * trio), otherwise the raw metadata is used.
55
+ * - explicit actions win; absent them, the implicit View/Edit/Delete trio is
56
+ * materialized when `enableCRUDActions` is on.
57
+ * - table/create-placement actions are stripped (they belong to the toolbar).
58
+ *
59
+ * Pure. `tx` resolves the trio's i18n labels (defaults to the Spanish fallbacks).
60
+ */
61
+ export declare function resolveRowActions(metadata: TableMetadata, model: string, can: CanFn, permissionsActive: boolean, tx?: (i18nKey: string, fallback: string) => string): ActionDefinition[];
48
62
  //# sourceMappingURL=permissions-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions-context.d.ts","sourceRoot":"","sources":["../src/permissions-context.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAA6C,MAAM,OAAO,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAoB,MAAM,SAAS,CAAA;AAM9D,uEAAuE;AACvE,MAAM,MAAM,KAAK,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAA;AAEnD,MAAM,WAAW,wBAAwB;IACrC,6EAA6E;IAC7E,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAMD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,KAAK,CAKtE;AAMD,wBAAgB,mBAAmB,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,wBAAwB,qBAG/F;AAED;;;;GAIG;AACH,wBAAgB,MAAM,IAAI,KAAK,CAE9B;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAOD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,KAAK,EACV,EAAE,GAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAmC,GAC/E,aAAa,CAkCf"}
1
+ {"version":3,"file":"permissions-context.d.ts","sourceRoot":"","sources":["../src/permissions-context.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAA6C,MAAM,OAAO,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAM9D,uEAAuE;AACvE,MAAM,MAAM,KAAK,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAA;AAEnD,MAAM,WAAW,wBAAwB;IACrC,6EAA6E;IAC7E,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAMD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,KAAK,CAKtE;AAMD,wBAAgB,mBAAmB,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,wBAAwB,qBAG/F;AAED;;;;GAIG;AACH,wBAAgB,MAAM,IAAI,KAAK,CAE9B;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAOD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,KAAK,EACV,EAAE,GAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAmC,GAC/E,aAAa,CAkCf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,KAAK,EACV,iBAAiB,EAAE,OAAO,EAC1B,EAAE,GAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAmC,GAC/E,gBAAgB,EAAE,CAkBpB"}
@@ -122,3 +122,32 @@ export function gateTableMetadata(metadata, model, can, tx = (_k, fallback) => f
122
122
  canCreate: metadata.canCreate === undefined ? undefined : metadata.canCreate && allowed('create'),
123
123
  };
124
124
  }
125
+ /**
126
+ * Resolves the per-row (`placement: 'row'`) action list for a model exactly the
127
+ * way DynamicTable's action column does, so the kanban card menu shows the SAME
128
+ * actions:
129
+ * - when a <PermissionsProvider> is active, the metadata is run through
130
+ * `gateTableMetadata` first (filters by capability + materializes the CRUD
131
+ * trio), otherwise the raw metadata is used.
132
+ * - explicit actions win; absent them, the implicit View/Edit/Delete trio is
133
+ * materialized when `enableCRUDActions` is on.
134
+ * - table/create-placement actions are stripped (they belong to the toolbar).
135
+ *
136
+ * Pure. `tx` resolves the trio's i18n labels (defaults to the Spanish fallbacks).
137
+ */
138
+ export function resolveRowActions(metadata, model, can, permissionsActive, tx = (_k, fallback) => fallback) {
139
+ const gated = permissionsActive ? gateTableMetadata(metadata, model, can, tx) : metadata;
140
+ const explicit = gated.actions ?? [];
141
+ const hasExplicit = (gated.hasActions ?? explicit.length > 0) && explicit.length > 0;
142
+ const base = hasExplicit
143
+ ? explicit
144
+ : gated.enableCRUDActions
145
+ ? DEFAULT_TRIO.map((a) => ({
146
+ key: a.key,
147
+ name: a.key,
148
+ label: tx(a.i18nKey, a.fallback),
149
+ icon: a.icon,
150
+ }))
151
+ : [];
152
+ return base.filter((a) => (a.placement ?? 'row') === 'row');
153
+ }
@@ -0,0 +1,38 @@
1
+ import type { ColumnFilterConfig } from './dynamic-columns-shim';
2
+ import type { TableMetadata } from './types';
3
+ export interface UseDynamicFiltersOptions {
4
+ /**
5
+ * Static equality filters always applied (never shown as removable chips).
6
+ * Mirrors DynamicTable's `defaultFilters` — e.g. scoping a board to one owner.
7
+ */
8
+ defaultFilters?: Record<string, any>;
9
+ }
10
+ export interface UseDynamicFiltersResult {
11
+ /** Per-field selected values, `filterKey → values[]` (empty = inactive). */
12
+ dynamicFilters: Record<string, string[]>;
13
+ /** Free-text search term (the toolbar search box). */
14
+ globalFilter: string;
15
+ setGlobalFilter: (v: string) => void;
16
+ /** Config for each filterable field, keyed by the metadata filter/column key. */
17
+ columnFilterConfigs: Map<string, ColumnFilterConfig>;
18
+ /**
19
+ * The serialized query params (`f_<key>`, `search`, `search_columns`) ready to
20
+ * spread into a `/data/:model` request. Stable object identity while inputs
21
+ * are unchanged so a consumer can safely depend on it in a fetch effect.
22
+ */
23
+ filterParams: Record<string, any>;
24
+ /** Apply a field's selection (replaces its values). */
25
+ handleDynamicFilterChange: (filterKey: string, values: string[]) => void;
26
+ /** Count of fields with an active selection + the search term. */
27
+ activeFilterCount: number;
28
+ /** Clear every field filter and the search term. */
29
+ clearAll: () => void;
30
+ }
31
+ /**
32
+ * Reads the model's filter metadata and returns the live filter state + the
33
+ * serialized request params. Pure of any view concern — DynamicTable adds its
34
+ * own sort/pagination/`columnFilters`; DynamicKanban spreads `filterParams`
35
+ * straight onto its single-page fetch.
36
+ */
37
+ export declare function useDynamicFilters(metadata: TableMetadata | null, opts?: UseDynamicFiltersOptions): UseDynamicFiltersResult;
38
+ //# sourceMappingURL=use-dynamic-filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-dynamic-filters.d.ts","sourceRoot":"","sources":["../src/use-dynamic-filters.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,kBAAkB,EAAgB,MAAM,wBAAwB,CAAA;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,sDAAsD;IACtD,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,iFAAiF;IACjF,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IACpD;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACjC,uDAAuD;IACvD,yBAAyB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACxE,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,CAAA;IACzB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,GAAG,IAAI,EAC9B,IAAI,GAAE,wBAA6B,GAClC,uBAAuB,CAqNzB"}
@@ -0,0 +1,226 @@
1
+ // useDynamicFilters — the metadata-driven filter engine shared by DynamicTable
2
+ // and DynamicKanban. It owns nothing view-specific: given a model's
3
+ // `TableMetadata`, it derives the per-field filter configs (from
4
+ // `metadata.filters[]` + every `metadata.columns[].filterable` column),
5
+ // prefetches the server-side option lists for relation/select filters, tracks
6
+ // the selected values + global search, and serializes them into the exact
7
+ // `f_<key>=<op>:<value>` / `search` query params the `/data/:model` endpoint
8
+ // already understands.
9
+ //
10
+ // DynamicTable historically inlined this; DynamicKanban now needs the SAME
11
+ // filtering (RFC: a board is just another view of the model), so the logic is
12
+ // factored here to guarantee both surfaces filter identically.
13
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
+ import { useApi } from './api-context';
15
+ import { DATE_CELL_TYPES } from './dynamic-columns';
16
+ import { getSearchableColumnKeys } from './column-visibility';
17
+ /**
18
+ * Reads the model's filter metadata and returns the live filter state + the
19
+ * serialized request params. Pure of any view concern — DynamicTable adds its
20
+ * own sort/pagination/`columnFilters`; DynamicKanban spreads `filterParams`
21
+ * straight onto its single-page fetch.
22
+ */
23
+ export function useDynamicFilters(metadata, opts = {}) {
24
+ const { defaultFilters } = opts;
25
+ const api = useApi();
26
+ const [dynamicFilters, setDynamicFilters] = useState({});
27
+ const [globalFilter, setGlobalFilter] = useState('');
28
+ const [filterOptionsMap, setFilterOptionsMap] = useState(new Map());
29
+ // Prefetch the option lists for relation/select filters (once per model). The
30
+ // combobox needs these before the user opens it; mirrors DynamicTable's
31
+ // metadata-init option prefetch, narrowed to the FILTER endpoints only (the
32
+ // kanban renders card cells through ActivityValueRenderer, not the cell
33
+ // options map, so we skip that half).
34
+ const prefetchedSig = useRef(null);
35
+ useEffect(() => {
36
+ if (!metadata)
37
+ return;
38
+ const filterEndpoints = (metadata.filters || [])
39
+ .filter((f) => f.searchEndpoint &&
40
+ (f.type === 'select' ||
41
+ f.type === 'dynamic_select' ||
42
+ f.type === 'boolean'))
43
+ .map((f) => f.searchEndpoint);
44
+ const columnFilterEndpoints = (metadata.columns || [])
45
+ .filter((c) => c.filterable && c.searchEndpoint)
46
+ .map((c) => c.searchEndpoint);
47
+ const endpoints = Array.from(new Set([...filterEndpoints, ...columnFilterEndpoints]));
48
+ if (endpoints.length === 0)
49
+ return;
50
+ // Dedup on the endpoint set, not model identity (TableMetadata carries no
51
+ // name) — the same board re-rendering shouldn't refetch its options.
52
+ const sig = endpoints.join('|');
53
+ if (prefetchedSig.current === sig)
54
+ return;
55
+ prefetchedSig.current = sig;
56
+ let cancelled = false;
57
+ Promise.all(endpoints.map(async (ep) => {
58
+ try {
59
+ const res = await api.get(ep);
60
+ return {
61
+ endpoint: ep,
62
+ data: res.data?.success ? res.data.data : [],
63
+ };
64
+ }
65
+ catch (e) {
66
+ console.error(`Failed to fetch filter options for ${ep}`, e);
67
+ return { endpoint: ep, data: [] };
68
+ }
69
+ })).then((results) => {
70
+ if (cancelled)
71
+ return;
72
+ const fMap = new Map();
73
+ for (const r of results) {
74
+ fMap.set(r.endpoint, (r.data || []).map((item) => ({
75
+ label: item.label || item.name || '',
76
+ value: String(item.value ?? item.id ?? ''),
77
+ icon: item.icon,
78
+ color: item.color || item.class,
79
+ })));
80
+ }
81
+ setFilterOptionsMap(fMap);
82
+ });
83
+ return () => {
84
+ cancelled = true;
85
+ };
86
+ // eslint-disable-next-line react-hooks/exhaustive-deps
87
+ }, [metadata]);
88
+ const handleDynamicFilterChange = useCallback((filterKey, values) => {
89
+ setDynamicFilters((prev) => ({ ...prev, [filterKey]: values }));
90
+ }, []);
91
+ const clearAll = useCallback(() => {
92
+ setDynamicFilters({});
93
+ setGlobalFilter('');
94
+ }, []);
95
+ // Derive one ColumnFilterConfig per filterable field. Explicit
96
+ // `metadata.filters[]` wins; otherwise every `filterable` column yields a
97
+ // config with its UI inferred from the column shape. Copied 1:1 from
98
+ // DynamicTable so table and board build identical configs.
99
+ const columnFilterConfigs = useMemo(() => {
100
+ const map = new Map();
101
+ if (!metadata)
102
+ return map;
103
+ for (const f of metadata.filters ?? []) {
104
+ const fType = f.type;
105
+ let options = [];
106
+ if (f.options && f.options.length > 0) {
107
+ options = f.options.map((o) => ({
108
+ label: o.label,
109
+ value: String(o.value),
110
+ icon: o.icon,
111
+ color: o.color,
112
+ }));
113
+ }
114
+ if (f.searchEndpoint && filterOptionsMap.has(f.searchEndpoint)) {
115
+ options = filterOptionsMap.get(f.searchEndpoint) || [];
116
+ }
117
+ if (fType === 'select' && options.length === 0 && !f.searchEndpoint)
118
+ continue;
119
+ map.set(f.key, {
120
+ filterType: fType,
121
+ filterKey: f.column || f.key,
122
+ options,
123
+ selectedValues: dynamicFilters[f.column || f.key] || [],
124
+ onFilterChange: handleDynamicFilterChange,
125
+ loading: f.searchEndpoint ? !filterOptionsMap.has(f.searchEndpoint) : false,
126
+ searchEndpoint: f.searchEndpoint,
127
+ });
128
+ }
129
+ for (const c of metadata.columns ?? []) {
130
+ if (!c.filterable || map.has(c.key))
131
+ continue;
132
+ const hasStaticOptions = (c.options?.length ?? 0) > 0;
133
+ const hasEndpoint = !!c.searchEndpoint;
134
+ const isRelation = !!c.ref || c.filterType === 'dynamic_select';
135
+ let filterType;
136
+ if (c.filterType)
137
+ filterType = c.filterType;
138
+ else if (isRelation && hasEndpoint)
139
+ filterType = 'dynamic_select';
140
+ else if (hasStaticOptions || hasEndpoint)
141
+ filterType = 'select';
142
+ else if (c.type === 'boolean')
143
+ filterType = 'boolean';
144
+ else if (c.type === 'number')
145
+ filterType = 'number_range';
146
+ else if (DATE_CELL_TYPES.includes(c.type))
147
+ filterType = 'date_range';
148
+ else
149
+ filterType = 'text';
150
+ const options = hasStaticOptions
151
+ ? c.options.map((o) => ({
152
+ label: o.label,
153
+ value: String(o.value),
154
+ icon: o.icon,
155
+ color: o.color,
156
+ }))
157
+ : hasEndpoint && filterOptionsMap.has(c.searchEndpoint)
158
+ ? filterOptionsMap.get(c.searchEndpoint) || []
159
+ : [];
160
+ map.set(c.key, {
161
+ filterType,
162
+ filterKey: c.key,
163
+ options,
164
+ selectedValues: dynamicFilters[c.key] || [],
165
+ onFilterChange: handleDynamicFilterChange,
166
+ loading: hasEndpoint && !filterOptionsMap.has(c.searchEndpoint),
167
+ searchEndpoint: c.searchEndpoint,
168
+ });
169
+ }
170
+ return map;
171
+ }, [metadata, filterOptionsMap, dynamicFilters, handleDynamicFilterChange]);
172
+ const searchableKeys = useMemo(() => (metadata ? getSearchableColumnKeys(metadata) : null), [metadata]);
173
+ // Serialize to `/data/:model` query params — identical operator encoding to
174
+ // DynamicTable.buildFilterParams (IN:/RANGE:/GTE:/LTE:/ILIKE:/plain).
175
+ const filterParams = useMemo(() => {
176
+ const params = {};
177
+ if (globalFilter) {
178
+ if (searchableKeys === null) {
179
+ params.search = globalFilter;
180
+ }
181
+ else if (searchableKeys.length > 0) {
182
+ params.search = globalFilter;
183
+ params.search_columns = searchableKeys.join(',');
184
+ }
185
+ // searchableKeys === [] → no searchable column, skip the search param.
186
+ }
187
+ if (defaultFilters) {
188
+ Object.entries(defaultFilters).forEach(([key, value]) => {
189
+ params[`f_${key}`] = value;
190
+ });
191
+ }
192
+ Object.entries(dynamicFilters).forEach(([key, values]) => {
193
+ if (values.length === 0)
194
+ return;
195
+ const gteVal = values.find((v) => v.startsWith('GTE:'));
196
+ const lteVal = values.find((v) => v.startsWith('LTE:'));
197
+ if (gteVal || lteVal) {
198
+ const min = gteVal ? gteVal.replace('GTE:', '') : '';
199
+ const max = lteVal ? lteVal.replace('LTE:', '') : '';
200
+ params[`f_${key}`] = `RANGE:${min},${max}`;
201
+ return;
202
+ }
203
+ if (values.length === 1)
204
+ params[`f_${key}`] = values[0];
205
+ else
206
+ params[`f_${key}`] = `IN:${values.join(',')}`;
207
+ });
208
+ return params;
209
+ }, [globalFilter, searchableKeys, defaultFilters, dynamicFilters]);
210
+ const activeFilterCount = useMemo(() => {
211
+ let n = Object.values(dynamicFilters).filter((v) => v.length > 0).length;
212
+ if (globalFilter)
213
+ n += 1;
214
+ return n;
215
+ }, [dynamicFilters, globalFilter]);
216
+ return {
217
+ dynamicFilters,
218
+ globalFilter,
219
+ setGlobalFilter,
220
+ columnFilterConfigs,
221
+ filterParams,
222
+ handleDynamicFilterChange,
223
+ activeFilterCount,
224
+ clearAll,
225
+ };
226
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "20.1.6",
3
+ "version": "21.0.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,7 +38,7 @@
38
38
  "sonner": ">=1.7",
39
39
  "zustand": ">=5",
40
40
  "@asteby/metacore-sdk": "^3.2.0",
41
- "@asteby/metacore-ui": "^2.6.2"
41
+ "@asteby/metacore-ui": "^2.7.0"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "@tanstack/react-router": {
@@ -68,7 +68,7 @@
68
68
  "vitest": "^4.0.0",
69
69
  "zustand": "^5.0.0",
70
70
  "@asteby/metacore-sdk": "3.2.0",
71
- "@asteby/metacore-ui": "2.6.2"
71
+ "@asteby/metacore-ui": "2.7.0"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsc -p tsconfig.json",