@asteby/metacore-runtime-react 20.1.6 → 20.1.7

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,23 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 20.1.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 649de1b: fix(kanban): card "…" actions (Ver / Editar / Eliminar / custom) now work and are permission-gated
8
+
9
+ The per-card kebab menu on the kanban board forwarded the raw action object to a
10
+ no-op, so clicking Ver/Editar/Eliminar did nothing, and the menu was not filtered
11
+ by permission. The dispatch logic was extracted from `DynamicTable` into a shared
12
+ `useDynamicRowActions` hook (delete-confirm dialog, view/edit → host `onAction`
13
+ string contract or the built-in record dialog, link → navigate, custom →
14
+ ActionModal) that both renderers now use, so a kanban card behaves identically to a
15
+ table row. The card actions are resolved through the new `resolveRowActions` helper
16
+ — the same capability gate (`useCan`/`gateTableMetadata`) and implicit View/Edit/
17
+ Delete trio materialization the table's action column uses — so an action the user
18
+ lacks permission for no longer appears. `DynamicKanban` gains an optional
19
+ `onAction(action: string, row)` prop mirroring `DynamicTable`.
20
+
3
21
  ## 20.1.6
4
22
 
5
23
  ### Patch Changes
@@ -50,6 +50,18 @@ export declare const formatAggregateTotal: (col: ColumnDefinition, value: unknow
50
50
  * - row with no `status` field → all actions shown.
51
51
  */
52
52
  export declare const isActionAllowedForRowState: (action: any, row: any) => boolean;
53
+ /**
54
+ * Declarative `condition` gate for a per-row action: shows the action only when
55
+ * the row's `field` satisfies the `eq | neq | in | not_in` operator. No
56
+ * condition declared → always shown. Unknown operator → permissive.
57
+ */
58
+ export declare const isActionConditionMet: (action: any, row: any) => boolean;
59
+ /**
60
+ * Whether a per-row action should appear for `row`: both the state-machine gate
61
+ * (`requiresState`) AND the declarative `condition` must pass. Shared by the
62
+ * table's action column and the kanban card menu so they hide/show identically.
63
+ */
64
+ export declare const isRowActionVisible: (action: any, row: any) => boolean;
53
65
  /**
54
66
  * Resolves the relation sibling object a backend serves alongside an FK column.
55
67
  * For a column keyed `category_id` the data row also carries
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAiC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AA8DD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAqKD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2DAA4D,CAAA;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6C5C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOtE,CAAA;AAsID;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CA+nBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
1
+ {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAiC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AA8DD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAiB5D,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OACqB,CAAA;AAqKhF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2DAA4D,CAAA;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6C5C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOtE,CAAA;AAsID;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CA6mBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
@@ -141,6 +141,36 @@ export const isActionAllowedForRowState = (action, row) => {
141
141
  return true;
142
142
  return requires.map(String).includes(String(status));
143
143
  };
144
+ /**
145
+ * Declarative `condition` gate for a per-row action: shows the action only when
146
+ * the row's `field` satisfies the `eq | neq | in | not_in` operator. No
147
+ * condition declared → always shown. Unknown operator → permissive.
148
+ */
149
+ export const isActionConditionMet = (action, row) => {
150
+ if (!action?.condition)
151
+ return true;
152
+ const { field, operator, value } = action.condition;
153
+ const rowValue = String(row?.[field] ?? '');
154
+ const values = Array.isArray(value) ? value : [value];
155
+ switch (operator) {
156
+ case 'eq':
157
+ return rowValue === values[0];
158
+ case 'neq':
159
+ return rowValue !== values[0];
160
+ case 'in':
161
+ return values.includes(rowValue);
162
+ case 'not_in':
163
+ return !values.includes(rowValue);
164
+ default:
165
+ return true;
166
+ }
167
+ };
168
+ /**
169
+ * Whether a per-row action should appear for `row`: both the state-machine gate
170
+ * (`requiresState`) AND the declarative `condition` must pass. Shared by the
171
+ * table's action column and the kanban card menu so they hide/show identically.
172
+ */
173
+ export const isRowActionVisible = (action, row) => isActionAllowedForRowState(action, row) && isActionConditionMet(action, row);
144
174
  const lowerFirst = (value) => {
145
175
  if (!value)
146
176
  return value;
@@ -793,26 +823,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
793
823
  maxSize: 80,
794
824
  meta: {},
795
825
  cell: ({ row }) => (_jsx("div", { className: "flex items-center justify-end", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", className: "h-8 w-8 p-0", children: [_jsx("span", { className: "sr-only", children: "Abrir men\u00FA" }), _jsx(MoreHorizontal, { className: "h-4 w-4" })] }) }), _jsx(DropdownMenuContent, { align: "end", children: resolvedActions
796
- .filter((action) => isActionAllowedForRowState(action, row.original))
797
- .filter((action) => {
798
- if (!action.condition)
799
- return true;
800
- const { field, operator, value } = action.condition;
801
- const rowValue = String(row.original[field] ?? '');
802
- const values = Array.isArray(value) ? value : [value];
803
- switch (operator) {
804
- case 'eq':
805
- return rowValue === values[0];
806
- case 'neq':
807
- return rowValue !== values[0];
808
- case 'in':
809
- return values.includes(rowValue);
810
- case 'not_in':
811
- return !values.includes(rowValue);
812
- default:
813
- return true;
814
- }
815
- })
826
+ .filter((action) => isRowActionVisible(action, row.original))
816
827
  .map((action) => (_jsxs(DropdownMenuItem, { onClick: () => onAction && onAction(action.key, row.original), children: [_jsx(DynamicIcon, { name: action.icon, className: "mr-2 h-4 w-4" }), action.label] }, action.key))) })] }) })),
817
828
  });
818
829
  }
@@ -52,6 +52,13 @@ export interface DynamicKanbanProps {
52
52
  refreshTrigger?: any;
53
53
  /** Called when a card is clicked (outside its action menu). */
54
54
  onCardClick?: (row: any) => void;
55
+ /**
56
+ * Host hook for `view`/`edit` card actions (STRING contract — same as
57
+ * DynamicTable's `onAction`). When provided, `view`/`edit` route to the host
58
+ * (e.g. its seeded record modal); when omitted they open the SDK's built-in
59
+ * record dialog. `delete`/link/custom actions are always handled in-SDK.
60
+ */
61
+ onAction?: (action: string, row: any) => void;
55
62
  /**
56
63
  * Max cards fetched per lane render. Kanban shows all cards at once (no
57
64
  * pagination UI), so it requests a single large page. Defaults to 200.
@@ -62,5 +69,5 @@ export interface DynamicKanbanProps {
62
69
  /** ISO 4217 currency for money card fields (org config). */
63
70
  currency?: string;
64
71
  }
65
- export declare function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pageSize, timeZone, currency, }: DynamicKanbanProps): React.JSX.Element;
72
+ export declare function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, onAction, pageSize, timeZone, currency, }: DynamicKanbanProps): React.JSX.Element;
66
73
  //# sourceMappingURL=dynamic-kanban.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAoC9B,OAAO,KAAK,EACR,aAAa,EACb,gBAAgB,EAGhB,SAAS,EACT,eAAe,EAClB,MAAM,SAAS,CAAA;AAMhB;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,EAAE,CAejE;AAQD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,mBAAmB,CAAA;AAE/C,wBAAgB,YAAY,CACxB,OAAO,EAAE,GAAG,EAAE,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,SAAS,EAAE,GACpB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAepB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,WAAW,EAAE,eAAe,EAAE,GAAG,SAAS,EAC1C,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACX,OAAO,CAOT;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAC3B,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACnB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAYpB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,aAAa,EACvB,SAAS,SAAI,GACd;IAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAkBhE;AA8BD,MAAM,WAAW,kBAAkB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kFAAkF;IAClF,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,QAAc,EACd,QAAQ,EACR,QAAQ,GACX,EAAE,kBAAkB,qBAmSpB"}
1
+ {"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAoC9B,OAAO,KAAK,EACR,aAAa,EACb,gBAAgB,EAGhB,SAAS,EACT,eAAe,EAClB,MAAM,SAAS,CAAA;AAMhB;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,EAAE,CAejE;AAQD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,mBAAmB,CAAA;AAE/C,wBAAgB,YAAY,CACxB,OAAO,EAAE,GAAG,EAAE,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,SAAS,EAAE,GACpB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAepB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,WAAW,EAAE,eAAe,EAAE,GAAG,SAAS,EAC1C,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACX,OAAO,CAOT;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAC3B,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACnB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAYpB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,aAAa,EACvB,SAAS,SAAI,GACd;IAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAkBhE;AA8BD,MAAM,WAAW,kBAAkB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kFAAkF;IAClF,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,QAAQ,EACR,QAAc,EACd,QAAQ,EACR,QAAQ,GACX,EAAE,kBAAkB,qBAqSpB"}
@@ -9,11 +9,11 @@ import { generateBadgeStyles, optionColor } from '@asteby/metacore-ui/lib';
9
9
  import { useApi } from './api-context';
10
10
  import { useMetadataCache } from './metadata-cache';
11
11
  import { ActivityValueRenderer } from './activity-value-renderer';
12
- import { ActionModalDispatcher } from './action-modal-dispatcher';
13
- import { useModelActions } from './model-action-toolbar';
14
12
  import { DynamicIcon } from './dynamic-icon';
15
13
  import { isColumnVisibleInTable } from './column-visibility';
16
- import { isActionAllowedForRowState } from './dynamic-columns';
14
+ import { isRowActionVisible } from './dynamic-columns';
15
+ import { useCan, usePermissionsActive, resolveRowActions } from './permissions-context';
16
+ import { useDynamicRowActions } from './dynamic-row-actions';
17
17
  // ---------------------------------------------------------------------------
18
18
  // Pure helpers (exported for unit tests — no React, no transport)
19
19
  // ---------------------------------------------------------------------------
@@ -146,7 +146,7 @@ function useIsDarkTheme() {
146
146
  }, []);
147
147
  return isDark;
148
148
  }
149
- export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pageSize = 200, timeZone, currency, }) {
149
+ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, onAction, pageSize = 200, timeZone, currency, }) {
150
150
  const { t, i18n } = useTranslation();
151
151
  const api = useApi();
152
152
  const isDark = useIsDarkTheme();
@@ -158,7 +158,6 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
158
158
  const [loadingData, setLoadingData] = useState(true);
159
159
  // Active drag card id (for the DragOverlay + drop-zone highlighting).
160
160
  const [activeId, setActiveId] = useState(null);
161
- const [actionModal, setActionModal] = useState({ action: null, record: null });
162
161
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
163
162
  // ---- metadata fetch (same path as DynamicTable) ----
164
163
  useEffect(() => {
@@ -224,8 +223,25 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
224
223
  const transitions = metadata?.transitions;
225
224
  const grouped = useMemo(() => groupByStage(records, groupByKey, stages), [records, groupByKey, stages]);
226
225
  const { title: titleCol, fields: fieldCols } = useMemo(() => (metadata ? selectCardColumns(metadata) : { title: null, fields: [] }), [metadata]);
227
- // Row-placement actions reused verbatim from the table's plumbing.
228
- const rowActions = useModelActions(model, ['row'], metadata?.actions);
226
+ // Row-placement actions resolved EXACTLY like DynamicTable's action column:
227
+ // capability-gated (when a <PermissionsProvider> is mounted) and with the
228
+ // implicit View/Edit/Delete trio materialized for CRUD models. An action the
229
+ // user lacks permission for never appears.
230
+ const can = useCan();
231
+ const permissionsActive = usePermissionsActive();
232
+ const rowActions = useMemo(() => metadata
233
+ ? resolveRowActions(metadata, model, can, permissionsActive, (k, fb) => t(k, { defaultValue: fb }))
234
+ : [], [metadata, model, can, permissionsActive, t]);
235
+ // Shared row-action dispatch + dialogs — view/edit/delete/link/custom behave
236
+ // identically to a table row (the card menu used to forward the raw action
237
+ // object to the host and silently no-op).
238
+ const { handleInternalAction, dialogs: rowActionDialogs } = useDynamicRowActions({
239
+ model,
240
+ endpoint,
241
+ metadata,
242
+ onAction,
243
+ onRefresh: fetchData,
244
+ });
229
245
  const cardById = useMemo(() => {
230
246
  const m = new Map();
231
247
  for (const r of records)
@@ -307,14 +323,8 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
307
323
  const droppableAllowed = !activeId ||
308
324
  stage.key === activeStage ||
309
325
  isTransitionAllowed(transitions, activeStage, stage.key);
310
- return (_jsx(KanbanLane, { stage: stage, count: cards.length, isDark: isDark, dimmed: !!activeId && !droppableAllowed, disabled: !!activeId && !droppableAllowed, children: loadingData && cards.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-20 w-full" }), _jsx(Skeleton, { className: "h-20 w-full" })] })) : cards.length === 0 ? (_jsx("p", { className: "px-1 py-6 text-center text-xs text-muted-foreground", children: t('kanban.emptyLane', { defaultValue: 'Sin tarjetas' }) })) : (cards.map((card) => (_jsx(KanbanCard, { card: card, titleCol: titleCol, fieldCols: fieldCols, actions: rowActions, locale: i18n.language, timeZone: timeZone, currency: currency, onClick: onCardClick, onAction: (action, record) => setActionModal({ action, record }) }, String(card.id))))) }, stage.key));
311
- }) }), _jsx(DragOverlay, { children: activeCard ? (_jsx(CardPreview, { card: activeCard, titleCol: titleCol, fieldCols: fieldCols, locale: i18n.language, timeZone: timeZone, currency: currency })) : null }), actionModal.action && (_jsx(ActionModalDispatcher, { open: !!actionModal.action, onOpenChange: (open) => {
312
- if (!open)
313
- setActionModal({ action: null, record: null });
314
- }, action: actionModal.action, model: model, record: actionModal.record ?? {}, endpoint: endpoint ?? `/data/${model}/me`, onSuccess: () => {
315
- setActionModal({ action: null, record: null });
316
- void fetchData();
317
- } }))] }));
326
+ return (_jsx(KanbanLane, { stage: stage, count: cards.length, isDark: isDark, dimmed: !!activeId && !droppableAllowed, disabled: !!activeId && !droppableAllowed, children: loadingData && cards.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-20 w-full" }), _jsx(Skeleton, { className: "h-20 w-full" })] })) : cards.length === 0 ? (_jsx("p", { className: "px-1 py-6 text-center text-xs text-muted-foreground", children: t('kanban.emptyLane', { defaultValue: 'Sin tarjetas' }) })) : (cards.map((card) => (_jsx(KanbanCard, { card: card, titleCol: titleCol, fieldCols: fieldCols, actions: rowActions, locale: i18n.language, timeZone: timeZone, currency: currency, onClick: onCardClick, onAction: handleInternalAction }, String(card.id))))) }, stage.key));
327
+ }) }), _jsx(DragOverlay, { children: activeCard ? (_jsx(CardPreview, { card: activeCard, titleCol: titleCol, fieldCols: fieldCols, locale: i18n.language, timeZone: timeZone, currency: currency })) : null }), rowActionDialogs] }));
318
328
  }
319
329
  function KanbanLane({ stage, count, isDark, dimmed, disabled, children }) {
320
330
  const { t } = useTranslation();
@@ -332,12 +342,12 @@ function KanbanCard({ card, titleCol, fieldCols, actions, locale, timeZone, curr
332
342
  const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
333
343
  id: String(card.id),
334
344
  });
335
- const visibleActions = actions.filter((a) => isActionAllowedForRowState(a, card));
345
+ const visibleActions = actions.filter((a) => isRowActionVisible(a, card));
336
346
  return (_jsx(Card, { ref: setNodeRef, ...attributes, ...listeners, className: "w-full min-w-0 cursor-grab active:cursor-grabbing border-border/70 shadow-sm", style: { opacity: isDragging ? 0.4 : 1 }, onClick: () => onClick?.(card), "data-card-id": String(card.id), children: _jsxs(CardContent, { className: "space-y-1.5 p-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsx("div", { className: "min-w-0 flex-1 break-words text-sm font-medium leading-snug", children: titleCol ? (_jsx(ActivityValueRenderer, { value: card[titleCol.key], col: titleCol, locale: locale, timeZone: timeZone, currency: currency })) : (_jsx("span", { className: "truncate", children: String(card.id) })) }), visibleActions.length > 0 && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-6 w-6 shrink-0 -mr-1 -mt-1",
337
347
  // Don't start a drag / card click from the menu button.
338
348
  onPointerDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) }) }), _jsx(DropdownMenuContent, { align: "end", onClick: (e) => e.stopPropagation(), children: visibleActions.map((a) => (_jsxs(DropdownMenuItem, { onClick: (e) => {
339
349
  e.stopPropagation();
340
- onAction(a, card);
350
+ onAction(a.key, card);
341
351
  }, children: [_jsx(DynamicIcon, { name: a.icon || 'Zap', className: "mr-2 h-4 w-4" }), a.label] }, a.key))) })] }))] }), fieldCols.map((col) => (_jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs text-muted-foreground", children: [_jsxs("span", { className: "shrink-0 opacity-70", children: [col.label, ":"] }), _jsx("span", { className: "min-w-0 break-words", children: _jsx(ActivityValueRenderer, { value: card[col.key], col: col, locale: locale, timeZone: timeZone, currency: currency }) })] }, col.key)))] }) }));
342
352
  }
343
353
  // Static preview rendered inside the DragOverlay (no dnd hooks, no menu).
@@ -0,0 +1,36 @@
1
+ import type { TableMetadata } from './types';
2
+ export interface UseDynamicRowActionsParams {
3
+ /** Model key as registered on the backend (e.g. "issue"). */
4
+ model: string;
5
+ /**
6
+ * Data endpoint base. The per-record DELETE hits `<endpoint>/<id>`; when
7
+ * omitted it falls back to `/data/<model>/<id>`. Pass the SAME base the
8
+ * caller lists from (e.g. `/data/<model>/me`) so writes stay org-scoped.
9
+ */
10
+ endpoint?: string;
11
+ /** Raw model metadata — used to resolve link/custom action definitions. */
12
+ metadata: TableMetadata | null;
13
+ /**
14
+ * Host hook for `view`/`edit` (and any unhandled key). STRING contract:
15
+ * receives the action key, not the action object. When absent, `view`/`edit`
16
+ * fall back to the built-in record dialog.
17
+ */
18
+ onAction?: (action: string, row: any) => void;
19
+ /** Called after a successful delete / custom action to refetch the list. */
20
+ onRefresh: () => void;
21
+ }
22
+ export interface DynamicRowActions {
23
+ /** Dispatch a row action by its STRING key. */
24
+ handleInternalAction: (action: string, row: any) => Promise<void>;
25
+ /**
26
+ * The delete-confirm dialog + record dialog + action modal this handler
27
+ * drives. Render it ONCE in the consuming component's tree.
28
+ */
29
+ dialogs: React.ReactElement;
30
+ }
31
+ /**
32
+ * Owns the state + dialogs + dispatch logic for per-row actions, shared by
33
+ * DynamicTable and DynamicKanban so both behave identically.
34
+ */
35
+ export declare function useDynamicRowActions({ model, endpoint, metadata, onAction, onRefresh, }: UseDynamicRowActionsParams): DynamicRowActions;
36
+ //# sourceMappingURL=dynamic-row-actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-row-actions.d.ts","sourceRoot":"","sources":["../src/dynamic-row-actions.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,SAAS,CAAA;AAE5D,MAAM,WAAW,0BAA0B;IACvC,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,4EAA4E;IAC5E,SAAS,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,iBAAiB;IAC9B,+CAA+C;IAC/C,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjE;;;OAGG;IACH,OAAO,EAAE,KAAK,CAAC,YAAY,CAAA;CAC9B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,SAAS,GACZ,EAAE,0BAA0B,GAAG,iBAAiB,CAmHhD"}
@@ -0,0 +1,110 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // useDynamicRowActions — the single source of truth for what a per-row action
3
+ // (the "…" kebab → Ver / Editar / Eliminar / custom / link) DOES when clicked.
4
+ //
5
+ // Extracted verbatim from DynamicTable so the kanban board's per-card menu
6
+ // behaves IDENTICALLY to a table row (it used to forward the raw action object
7
+ // to the host and silently no-op). Both renderers call the returned
8
+ // `handleInternalAction(actionKey, row)` with the action's STRING key and mount
9
+ // the returned `dialogs` node once in their tree:
10
+ //
11
+ // delete → opens the SDK's own confirm dialog, then DELETEs.
12
+ // view / edit → host `onAction(action, row)` when provided (string
13
+ // contract), else the built-in record dialog.
14
+ // link action → navigate to the action's templated `linkUrl`.
15
+ // custom (fields/confirm/
16
+ // executable) action → opens the ActionModal via the dispatcher.
17
+ // anything else → host `onAction` + refresh (or just refresh).
18
+ //
19
+ // The host `onAction` contract is a STRING action key on purpose — the existing
20
+ // ops `handleAction(action: string, row)` keeps working unchanged.
21
+ import { useCallback, useState } from 'react';
22
+ import { useNavigate } from '@tanstack/react-router';
23
+ import { useTranslation } from 'react-i18next';
24
+ import { toast } from 'sonner';
25
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@asteby/metacore-ui/primitives';
26
+ import { useApi } from './api-context';
27
+ import { ActionModalDispatcher } from './action-modal-dispatcher';
28
+ import { DynamicRecordDialog } from './dialogs/dynamic-record';
29
+ /**
30
+ * Owns the state + dialogs + dispatch logic for per-row actions, shared by
31
+ * DynamicTable and DynamicKanban so both behave identically.
32
+ */
33
+ export function useDynamicRowActions({ model, endpoint, metadata, onAction, onRefresh, }) {
34
+ const { t } = useTranslation();
35
+ const api = useApi();
36
+ const navigate = useNavigate();
37
+ const [recordDialog, setRecordDialog] = useState({ open: false, mode: 'view', recordId: null });
38
+ const [rowToDelete, setRowToDelete] = useState(null);
39
+ const [isDeleting, setIsDeleting] = useState(false);
40
+ const [actionModal, setActionModal] = useState({ open: false, action: null, record: null });
41
+ const handleInternalAction = useCallback(async (action, row) => {
42
+ if (action === 'delete') {
43
+ setRowToDelete(row);
44
+ return;
45
+ }
46
+ if (action === 'view' || action === 'edit') {
47
+ if (onAction)
48
+ await Promise.resolve(onAction(action, row));
49
+ else
50
+ setRecordDialog({ open: true, mode: action, recordId: row.id });
51
+ return;
52
+ }
53
+ const linkDef = metadata?.actions?.find((a) => a.key === action && a.type === 'link');
54
+ if (linkDef?.linkUrl) {
55
+ const url = linkDef.linkUrl.replace(/\{(\w+)\}/g, (_, field) => String(row[field] ?? ''));
56
+ navigate({ to: url });
57
+ return;
58
+ }
59
+ const actionDef = metadata?.actions?.find((a) => a.key === action);
60
+ if (actionDef && (actionDef.fields?.length || actionDef.confirm || actionDef.executable)) {
61
+ setActionModal({
62
+ open: true,
63
+ action: {
64
+ key: actionDef.key,
65
+ label: actionDef.label,
66
+ icon: actionDef.icon || 'Zap',
67
+ color: actionDef.color,
68
+ confirm: actionDef.confirm,
69
+ confirmMessage: actionDef.confirmMessage,
70
+ fields: actionDef.fields,
71
+ requiresState: actionDef.requiresState,
72
+ executable: actionDef.executable,
73
+ },
74
+ record: row,
75
+ });
76
+ return;
77
+ }
78
+ if (onAction) {
79
+ await Promise.resolve(onAction(action, row));
80
+ onRefresh();
81
+ }
82
+ else
83
+ onRefresh();
84
+ }, [onAction, onRefresh, metadata, navigate]);
85
+ const confirmDelete = async () => {
86
+ if (!rowToDelete)
87
+ return;
88
+ setIsDeleting(true);
89
+ try {
90
+ const deleteEndpoint = endpoint ? `${endpoint}/${rowToDelete.id}` : `/data/${model}/${rowToDelete.id}`;
91
+ const res = await api.delete(deleteEndpoint);
92
+ if (res.data.success) {
93
+ toast.success(res.data.message || 'Eliminado correctamente');
94
+ onRefresh();
95
+ }
96
+ else
97
+ toast.error(res.data.message || 'Error al eliminar');
98
+ }
99
+ catch (error) {
100
+ console.error('Error al eliminar', error);
101
+ toast.error('Error al eliminar el registro');
102
+ }
103
+ finally {
104
+ setIsDeleting(false);
105
+ setRowToDelete(null);
106
+ }
107
+ };
108
+ const dialogs = (_jsxs(_Fragment, { children: [_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(DynamicRecordDialog, { open: recordDialog.open, onOpenChange: (open) => setRecordDialog((prev) => ({ ...prev, open })), mode: recordDialog.mode, model: model, recordId: recordDialog.recordId, endpoint: endpoint, onSaved: onRefresh }), actionModal.action && (_jsx(ActionModalDispatcher, { open: actionModal.open, onOpenChange: (open) => setActionModal((prev) => ({ ...prev, open })), action: actionModal.action, model: model, record: actionModal.record, endpoint: endpoint, onSuccess: onRefresh }))] }));
109
+ return { handleInternalAction, dialogs };
110
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAWnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,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;CACpB;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,GACX,EAAE,iBAAiB,+BAw5BnB"}
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;AAUnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,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;CACpB;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,GACX,EAAE,iBAAiB,+BA2zBnB"}
@@ -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
@@ -10,7 +10,8 @@ export * from './addon-loader';
10
10
  export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, type AddonLayout, type AddonLayoutProviderProps, } from './addon-layout-context';
11
11
  export * from './slot';
12
12
  export * from './capability-gate';
13
- export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, type CanFn, type PermissionsProviderProps, } from './permissions-context';
13
+ export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, resolveRowActions, type CanFn, type PermissionsProviderProps, } from './permissions-context';
14
+ export { useDynamicRowActions, type UseDynamicRowActionsParams, type DynamicRowActions, } from './dynamic-row-actions';
14
15
  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
16
  export * from './org-runtime-context';
16
17
  export * from './org-runtime-provider';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,EACvB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,gBAAgB,GACxB,MAAM,gBAAgB,CAAA;AACvB,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,SAAS,GACjB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,cAAc,EACd,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,UAAU,EACV,KAAK,mBAAmB,EACxB,KAAK,SAAS,IAAI,uBAAuB,GAC5C,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzE,YAAY,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC5G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,0BAA0B,GAClC,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,GAC1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,gBAAgB,EAChB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,aAAa,EACb,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACR,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,UAAU,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,KAAK,iBAAiB,GACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,cAAc,EACd,cAAc,EACd,SAAS,EACT,UAAU,EACV,KAAK,mBAAmB,GAC3B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,UAAU,EACV,SAAS,EACT,WAAW,EACX,WAAW,EACX,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -15,7 +15,8 @@ export * from './addon-loader';
15
15
  export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, } from './addon-layout-context';
16
16
  export * from './slot';
17
17
  export * from './capability-gate';
18
- export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, } from './permissions-context';
18
+ export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, resolveRowActions, } from './permissions-context';
19
+ export { useDynamicRowActions, } from './dynamic-row-actions';
19
20
  export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, normalizeCatalogGroups, flattenGroups, filterModuleGroups, } from './permissions-manager';
20
21
  export * from './org-runtime-context';
21
22
  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