@asteby/metacore-runtime-react 20.1.5 → 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 +31 -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 +8 -1
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +30 -20
- 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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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/package.json +1 -1
- package/src/__tests__/dynamic-kanban.test.tsx +15 -5
- package/src/__tests__/resolve-row-actions.test.ts +80 -0
- package/src/dynamic-columns.tsx +33 -19
- package/src/dynamic-kanban.tsx +51 -41
- package/src/dynamic-row-actions.tsx +195 -0
- package/src/dynamic-table.tsx +12 -107
- package/src/index.ts +6 -0
- package/src/permissions-context.tsx +39 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
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
|
+
|
|
21
|
+
## 20.1.6
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 0ff3d4e: fix(kanban): fixed-width lanes (horizontal scroll, no squish) + drag preview matches card
|
|
26
|
+
|
|
27
|
+
Lanes were `flex-1 min-w-[220px] max-w-[320px]`, so with many stages they compressed
|
|
28
|
+
to a cramped width. They're now a fixed `w-[300px] shrink-0` so columns keep a
|
|
29
|
+
comfortable width and the board scrolls horizontally instead of squishing. The drag
|
|
30
|
+
overlay was a fixed `w-72` that no longer matched the in-lane card; it's now
|
|
31
|
+
`w-[284px]` (lane width minus the column padding) so a dragged card is the same size
|
|
32
|
+
as it sits in the column.
|
|
33
|
+
|
|
3
34
|
## 20.1.5
|
|
4
35
|
|
|
5
36
|
### 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.
|
|
@@ -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":"
|
|
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"}
|
package/dist/dynamic-kanban.js
CHANGED
|
@@ -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 {
|
|
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
|
|
228
|
-
|
|
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)
|
|
@@ -284,7 +300,7 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
|
|
|
284
300
|
}
|
|
285
301
|
}, [api, endpoint, groupByKey, model, records, stageOfCard, t, transitions]);
|
|
286
302
|
if (loading) {
|
|
287
|
-
return (_jsx("div", { className: "flex gap-4 overflow-x-auto p-1", children: [0, 1, 2, 3].map((i) => (_jsxs("div", { className: "
|
|
303
|
+
return (_jsx("div", { className: "flex gap-4 overflow-x-auto p-1", children: [0, 1, 2, 3].map((i) => (_jsxs("div", { className: "w-[300px] shrink-0 space-y-3", children: [_jsx(Skeleton, { className: "h-8 w-full" }), _jsx(Skeleton, { className: "h-24 w-full" }), _jsx(Skeleton, { className: "h-24 w-full" })] }, i))) }));
|
|
288
304
|
}
|
|
289
305
|
if (!metadata || !groupByKey || stages.length === 0) {
|
|
290
306
|
return (_jsx("div", { className: "rounded-md border border-dashed p-8 text-center text-sm text-muted-foreground", children: t('kanban.noStages', {
|
|
@@ -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:
|
|
311
|
-
}) }), _jsx(DragOverlay, { children: activeCard ? (_jsx(CardPreview, { card: activeCard, titleCol: titleCol, fieldCols: fieldCols, locale: i18n.language, timeZone: timeZone, currency: currency })) : null }),
|
|
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();
|
|
@@ -322,7 +332,7 @@ function KanbanLane({ stage, count, isDark, dimmed, disabled, children }) {
|
|
|
322
332
|
const headerStyle = generateBadgeStyles(stage.color || optionColor(stage.key), {
|
|
323
333
|
isDark,
|
|
324
334
|
});
|
|
325
|
-
return (_jsxs("div", { ref: setNodeRef, className: "flex
|
|
335
|
+
return (_jsxs("div", { ref: setNodeRef, className: "flex w-[300px] shrink-0 flex-col rounded-lg border bg-muted/30 transition-opacity", style: {
|
|
326
336
|
opacity: dimmed ? 0.45 : 1,
|
|
327
337
|
outline: isOver && !disabled ? '2px solid var(--ring, #3b82f6)' : 'none',
|
|
328
338
|
outlineOffset: 2,
|
|
@@ -332,15 +342,15 @@ 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) =>
|
|
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).
|
|
344
354
|
function CardPreview({ card, titleCol, fieldCols, locale, timeZone, currency, }) {
|
|
345
|
-
return (_jsx(Card, { className: "w-
|
|
355
|
+
return (_jsx(Card, { className: "w-[284px] cursor-grabbing border-primary/40 shadow-lg", children: _jsxs(CardContent, { className: "space-y-1.5 p-3", children: [_jsx("div", { className: "break-words text-sm font-medium leading-snug", children: titleCol ? (_jsx(ActivityValueRenderer, { value: card[titleCol.key], col: titleCol, locale: locale, timeZone: timeZone, currency: currency })) : (String(card.id)) }), 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)))] }) }));
|
|
346
356
|
}
|
|
@@ -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"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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 }) })] }),
|
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,GACzB,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"}
|