@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.
- package/CHANGELOG.md +43 -0
- package/dist/dynamic-columns.d.ts +12 -0
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +31 -20
- package/dist/dynamic-kanban.d.ts +13 -1
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +66 -29
- package/dist/dynamic-row-actions.d.ts +36 -0
- package/dist/dynamic-row-actions.d.ts.map +1 -0
- package/dist/dynamic-row-actions.js +110 -0
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +11 -76
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/permissions-context.d.ts +15 -1
- package/dist/permissions-context.d.ts.map +1 -1
- package/dist/permissions-context.js +29 -0
- package/dist/use-dynamic-filters.d.ts +38 -0
- package/dist/use-dynamic-filters.d.ts.map +1 -0
- package/dist/use-dynamic-filters.js +226 -0
- package/package.json +3 -3
- package/src/__tests__/dynamic-kanban.test.tsx +67 -6
- package/src/__tests__/resolve-row-actions.test.ts +80 -0
- package/src/dynamic-columns.tsx +33 -19
- package/src/dynamic-kanban.tsx +147 -47
- package/src/dynamic-row-actions.tsx +195 -0
- package/src/dynamic-table.tsx +12 -107
- package/src/index.ts +11 -0
- package/src/permissions-context.tsx +39 -0
- package/src/use-dynamic-filters.ts +272 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 21.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 84aeaf2: feat(kanban): dynamic column filters on DynamicKanban
|
|
8
|
+
|
|
9
|
+
The board now filters like its DynamicTable sibling. A filter bar above the
|
|
10
|
+
lanes exposes a global search box plus one chip per filterable field (from
|
|
11
|
+
`metadata.filters[]` and every `filterable` column), driving the SAME
|
|
12
|
+
server-side `f_<key>=<op>:<value>` / `search` params — so a model's table and
|
|
13
|
+
kanban views filter identically.
|
|
14
|
+
- **ui**: extracted the per-column filter popover into a new, TanStack-agnostic
|
|
15
|
+
`ColumnFilterControl` (select/boolean/dynamic_select/text/number_range/
|
|
16
|
+
date_range). `FilterableColumnHeader` now delegates its filter UI to it
|
|
17
|
+
(behavior unchanged); it also powers the kanban's labeled filter chips.
|
|
18
|
+
- **runtime-react**: new `useDynamicFilters(metadata)` hook owning the filter
|
|
19
|
+
state, option prefetch, config derivation and param serialization (factored
|
|
20
|
+
out of DynamicTable's inline logic). `DynamicKanban` consumes it and gains a
|
|
21
|
+
`defaultFilters` prop for parity with DynamicTable.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies [84aeaf2]
|
|
26
|
+
- @asteby/metacore-ui@2.7.0
|
|
27
|
+
|
|
28
|
+
## 20.1.7
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- 649de1b: fix(kanban): card "…" actions (Ver / Editar / Eliminar / custom) now work and are permission-gated
|
|
33
|
+
|
|
34
|
+
The per-card kebab menu on the kanban board forwarded the raw action object to a
|
|
35
|
+
no-op, so clicking Ver/Editar/Eliminar did nothing, and the menu was not filtered
|
|
36
|
+
by permission. The dispatch logic was extracted from `DynamicTable` into a shared
|
|
37
|
+
`useDynamicRowActions` hook (delete-confirm dialog, view/edit → host `onAction`
|
|
38
|
+
string contract or the built-in record dialog, link → navigate, custom →
|
|
39
|
+
ActionModal) that both renderers now use, so a kanban card behaves identically to a
|
|
40
|
+
table row. The card actions are resolved through the new `resolveRowActions` helper
|
|
41
|
+
— the same capability gate (`useCan`/`gateTableMetadata`) and implicit View/Edit/
|
|
42
|
+
Delete trio materialization the table's action column uses — so an action the user
|
|
43
|
+
lacks permission for no longer appears. `DynamicKanban` gains an optional
|
|
44
|
+
`onAction(action: string, row)` prop mirroring `DynamicTable`.
|
|
45
|
+
|
|
3
46
|
## 20.1.6
|
|
4
47
|
|
|
5
48
|
### 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;
|
|
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"}
|
package/dist/dynamic-columns.js
CHANGED
|
@@ -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) =>
|
|
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
|
}
|
package/dist/dynamic-kanban.d.ts
CHANGED
|
@@ -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.
|
|
@@ -61,6 +68,11 @@ export interface DynamicKanbanProps {
|
|
|
61
68
|
timeZone?: string;
|
|
62
69
|
/** ISO 4217 currency for money card fields (org config). */
|
|
63
70
|
currency?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Static equality filters always applied to the board (never shown as a
|
|
73
|
+
* removable chip). Same contract as DynamicTable's `defaultFilters`.
|
|
74
|
+
*/
|
|
75
|
+
defaultFilters?: Record<string, any>;
|
|
64
76
|
}
|
|
65
|
-
export declare function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pageSize, timeZone, currency, }: DynamicKanbanProps): React.JSX.Element;
|
|
77
|
+
export declare function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, onAction, pageSize, timeZone, currency, defaultFilters, }: DynamicKanbanProps): React.JSX.Element;
|
|
66
78
|
//# sourceMappingURL=dynamic-kanban.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAuC9B,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;IACjB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACvC;AAED,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,QAAQ,EACR,QAAc,EACd,QAAQ,EACR,QAAQ,EACR,cAAc,GACjB,EAAE,kBAAkB,qBAsXpB"}
|
package/dist/dynamic-kanban.js
CHANGED
|
@@ -2,18 +2,20 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { DndContext, DragOverlay, PointerSensor, useSensor, useSensors, useDraggable, useDroppable, } from '@dnd-kit/core';
|
|
5
|
-
import { MoreHorizontal } from 'lucide-react';
|
|
5
|
+
import { MoreHorizontal, Search, X } from 'lucide-react';
|
|
6
6
|
import { toast } from 'sonner';
|
|
7
|
-
import { Badge, Button, Card, CardContent, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Skeleton, } from '@asteby/metacore-ui/primitives';
|
|
7
|
+
import { Badge, Button, Card, CardContent, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, Skeleton, } from '@asteby/metacore-ui/primitives';
|
|
8
|
+
import { ColumnFilterControl } from '@asteby/metacore-ui/data-table';
|
|
8
9
|
import { generateBadgeStyles, optionColor } from '@asteby/metacore-ui/lib';
|
|
9
10
|
import { useApi } from './api-context';
|
|
11
|
+
import { useDynamicFilters } from './use-dynamic-filters';
|
|
10
12
|
import { useMetadataCache } from './metadata-cache';
|
|
11
13
|
import { ActivityValueRenderer } from './activity-value-renderer';
|
|
12
|
-
import { ActionModalDispatcher } from './action-modal-dispatcher';
|
|
13
|
-
import { useModelActions } from './model-action-toolbar';
|
|
14
14
|
import { DynamicIcon } from './dynamic-icon';
|
|
15
15
|
import { isColumnVisibleInTable } from './column-visibility';
|
|
16
|
-
import {
|
|
16
|
+
import { isRowActionVisible } from './dynamic-columns';
|
|
17
|
+
import { useCan, usePermissionsActive, resolveRowActions } from './permissions-context';
|
|
18
|
+
import { useDynamicRowActions } from './dynamic-row-actions';
|
|
17
19
|
// ---------------------------------------------------------------------------
|
|
18
20
|
// Pure helpers (exported for unit tests — no React, no transport)
|
|
19
21
|
// ---------------------------------------------------------------------------
|
|
@@ -146,7 +148,7 @@ function useIsDarkTheme() {
|
|
|
146
148
|
}, []);
|
|
147
149
|
return isDark;
|
|
148
150
|
}
|
|
149
|
-
export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pageSize = 200, timeZone, currency, }) {
|
|
151
|
+
export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, onAction, pageSize = 200, timeZone, currency, defaultFilters, }) {
|
|
150
152
|
const { t, i18n } = useTranslation();
|
|
151
153
|
const api = useApi();
|
|
152
154
|
const isDark = useIsDarkTheme();
|
|
@@ -158,7 +160,6 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
|
|
|
158
160
|
const [loadingData, setLoadingData] = useState(true);
|
|
159
161
|
// Active drag card id (for the DragOverlay + drop-zone highlighting).
|
|
160
162
|
const [activeId, setActiveId] = useState(null);
|
|
161
|
-
const [actionModal, setActionModal] = useState({ action: null, record: null });
|
|
162
163
|
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
|
|
163
164
|
// ---- metadata fetch (same path as DynamicTable) ----
|
|
164
165
|
useEffect(() => {
|
|
@@ -195,6 +196,10 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
|
|
|
195
196
|
};
|
|
196
197
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
197
198
|
}, [model]);
|
|
199
|
+
// Shared metadata-driven filter engine — the SAME configs, option prefetch
|
|
200
|
+
// and `f_<key>` serialization DynamicTable uses, so the board filters
|
|
201
|
+
// identically to its table sibling.
|
|
202
|
+
const { globalFilter, setGlobalFilter, columnFilterConfigs, filterParams, activeFilterCount, clearAll, } = useDynamicFilters(metadata, { defaultFilters });
|
|
198
203
|
// ---- records fetch (same path as DynamicTable, single large page) ----
|
|
199
204
|
const fetchData = useCallback(async () => {
|
|
200
205
|
if (!metadata)
|
|
@@ -202,7 +207,7 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
|
|
|
202
207
|
setLoadingData(true);
|
|
203
208
|
try {
|
|
204
209
|
const res = (await api.get(endpoint || `/data/${model}`, {
|
|
205
|
-
params: { page: 1, per_page: pageSize },
|
|
210
|
+
params: { page: 1, per_page: pageSize, ...filterParams },
|
|
206
211
|
}));
|
|
207
212
|
if (res.data.success)
|
|
208
213
|
setRecords(res.data.data || []);
|
|
@@ -213,19 +218,57 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
|
|
|
213
218
|
finally {
|
|
214
219
|
setLoadingData(false);
|
|
215
220
|
}
|
|
216
|
-
}, [api, endpoint, model, metadata, pageSize]);
|
|
221
|
+
}, [api, endpoint, model, metadata, pageSize, filterParams]);
|
|
222
|
+
// Refetch when metadata resolves, on an explicit refresh, or when the
|
|
223
|
+
// filters change. `fetchData` is stable while `filterParams` is unchanged
|
|
224
|
+
// (both memoized), so this only re-runs on real input changes. Debounced so
|
|
225
|
+
// typing in the search box doesn't fire a request per keystroke.
|
|
217
226
|
useEffect(() => {
|
|
218
|
-
if (metadata)
|
|
227
|
+
if (!metadata)
|
|
228
|
+
return;
|
|
229
|
+
const handle = setTimeout(() => {
|
|
219
230
|
void fetchData();
|
|
220
|
-
|
|
221
|
-
|
|
231
|
+
}, 200);
|
|
232
|
+
return () => clearTimeout(handle);
|
|
233
|
+
}, [fetchData, metadata, refreshTrigger]);
|
|
234
|
+
// Filterable fields for the toolbar, in metadata order (explicit filters
|
|
235
|
+
// first, then filterable columns), each labeled from its metadata source.
|
|
236
|
+
const filterFields = useMemo(() => {
|
|
237
|
+
if (!metadata)
|
|
238
|
+
return [];
|
|
239
|
+
const out = [];
|
|
240
|
+
for (const [key, config] of columnFilterConfigs) {
|
|
241
|
+
const f = metadata.filters?.find((x) => x.key === key);
|
|
242
|
+
const c = metadata.columns.find((x) => x.key === key);
|
|
243
|
+
const rawLabel = f?.label || c?.label || key;
|
|
244
|
+
out.push({ key, label: t(rawLabel, { defaultValue: rawLabel }), config });
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}, [metadata, columnFilterConfigs, t]);
|
|
222
248
|
const stages = useMemo(() => (metadata ? deriveStages(metadata) : []), [metadata]);
|
|
223
249
|
const groupByKey = metadata?.group_by || '';
|
|
224
250
|
const transitions = metadata?.transitions;
|
|
225
251
|
const grouped = useMemo(() => groupByStage(records, groupByKey, stages), [records, groupByKey, stages]);
|
|
226
252
|
const { title: titleCol, fields: fieldCols } = useMemo(() => (metadata ? selectCardColumns(metadata) : { title: null, fields: [] }), [metadata]);
|
|
227
|
-
// Row-placement actions
|
|
228
|
-
|
|
253
|
+
// Row-placement actions resolved EXACTLY like DynamicTable's action column:
|
|
254
|
+
// capability-gated (when a <PermissionsProvider> is mounted) and with the
|
|
255
|
+
// implicit View/Edit/Delete trio materialized for CRUD models. An action the
|
|
256
|
+
// user lacks permission for never appears.
|
|
257
|
+
const can = useCan();
|
|
258
|
+
const permissionsActive = usePermissionsActive();
|
|
259
|
+
const rowActions = useMemo(() => metadata
|
|
260
|
+
? resolveRowActions(metadata, model, can, permissionsActive, (k, fb) => t(k, { defaultValue: fb }))
|
|
261
|
+
: [], [metadata, model, can, permissionsActive, t]);
|
|
262
|
+
// Shared row-action dispatch + dialogs — view/edit/delete/link/custom behave
|
|
263
|
+
// identically to a table row (the card menu used to forward the raw action
|
|
264
|
+
// object to the host and silently no-op).
|
|
265
|
+
const { handleInternalAction, dialogs: rowActionDialogs } = useDynamicRowActions({
|
|
266
|
+
model,
|
|
267
|
+
endpoint,
|
|
268
|
+
metadata,
|
|
269
|
+
onAction,
|
|
270
|
+
onRefresh: fetchData,
|
|
271
|
+
});
|
|
229
272
|
const cardById = useMemo(() => {
|
|
230
273
|
const m = new Map();
|
|
231
274
|
for (const r of records)
|
|
@@ -302,19 +345,13 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
|
|
|
302
345
|
order: Number.MAX_SAFE_INTEGER,
|
|
303
346
|
});
|
|
304
347
|
}
|
|
305
|
-
return (_jsxs(DndContext, { sensors: sensors, onDragStart: onDragStart, onDragEnd: onDragEnd, children: [_jsx("div", { className: "flex min-w-0 gap-4 overflow-x-auto p-1", "data-testid": "kanban-board", children: lanes.map((stage) => {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
} }))] }));
|
|
348
|
+
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", "data-testid": "kanban-filters", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: globalFilter, onChange: (e) => setGlobalFilter(e.target.value), placeholder: t('kanban.searchPlaceholder', { defaultValue: 'Buscar...' }), className: "h-8 w-52 pl-8 text-sm" })] }), filterFields.map((field) => (_jsx(ColumnFilterControl, { showLabel: true, label: field.label, filterKey: field.config.filterKey, filterType: field.config.filterType, filterOptions: field.config.options, filterLoading: field.config.loading, filterSearchEndpoint: field.config.searchEndpoint, selectedValues: field.config.selectedValues, onFilterChange: field.config.onFilterChange }, field.key))), activeFilterCount > 0 && (_jsxs(Button, { variant: "ghost", size: "sm", className: "h-8 gap-1 text-xs text-muted-foreground", onClick: clearAll, children: [_jsx(X, { className: "h-3.5 w-3.5" }), t('kanban.clearFilters', { defaultValue: 'Limpiar' }), ` (${activeFilterCount})`] }))] }), _jsxs(DndContext, { sensors: sensors, onDragStart: onDragStart, onDragEnd: onDragEnd, children: [_jsx("div", { className: "flex min-w-0 gap-4 overflow-x-auto p-1", "data-testid": "kanban-board", children: lanes.map((stage) => {
|
|
349
|
+
const cards = grouped.get(stage.key) ?? [];
|
|
350
|
+
const droppableAllowed = !activeId ||
|
|
351
|
+
stage.key === activeStage ||
|
|
352
|
+
isTransitionAllowed(transitions, activeStage, stage.key);
|
|
353
|
+
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));
|
|
354
|
+
}) }), _jsx(DragOverlay, { children: activeCard ? (_jsx(CardPreview, { card: activeCard, titleCol: titleCol, fieldCols: fieldCols, locale: i18n.language, timeZone: timeZone, currency: currency })) : null }), rowActionDialogs] })] }));
|
|
318
355
|
}
|
|
319
356
|
function KanbanLane({ stage, count, isDark, dimmed, disabled, children }) {
|
|
320
357
|
const { t } = useTranslation();
|
|
@@ -332,12 +369,12 @@ function KanbanCard({ card, titleCol, fieldCols, actions, locale, timeZone, curr
|
|
|
332
369
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
333
370
|
id: String(card.id),
|
|
334
371
|
});
|
|
335
|
-
const visibleActions = actions.filter((a) =>
|
|
372
|
+
const visibleActions = actions.filter((a) => isRowActionVisible(a, card));
|
|
336
373
|
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
374
|
// Don't start a drag / card click from the menu button.
|
|
338
375
|
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
376
|
e.stopPropagation();
|
|
340
|
-
onAction(a, card);
|
|
377
|
+
onAction(a.key, card);
|
|
341
378
|
}, 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
379
|
}
|
|
343
380
|
// 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":"
|
|
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"}
|