@asteby/metacore-runtime-react 18.13.3 → 18.15.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 +35 -0
- package/dist/dynamic-crud-page.d.ts.map +1 -1
- package/dist/dynamic-crud-page.js +8 -3
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +20 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/model-action-toolbar.d.ts.map +1 -1
- package/dist/model-action-toolbar.js +6 -1
- package/dist/permissions-context.d.ts +48 -0
- package/dist/permissions-context.d.ts.map +1 -0
- package/dist/permissions-context.js +124 -0
- package/dist/permissions-manager.d.ts +84 -0
- package/dist/permissions-manager.d.ts.map +1 -0
- package/dist/permissions-manager.js +429 -0
- package/package.json +11 -9
- package/src/__tests__/dynamic-table-permissions.test.tsx +147 -0
- package/src/__tests__/permissions-context.test.tsx +102 -0
- package/src/__tests__/permissions-manager.test.tsx +258 -0
- package/src/dynamic-crud-page.tsx +8 -3
- package/src/dynamic-table.tsx +22 -9
- package/src/index.ts +26 -0
- package/src/model-action-toolbar.tsx +9 -1
- package/src/permissions-context.tsx +158 -0
- package/src/permissions-manager.tsx +1143 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 18.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cbcedd9: PermissionsManager: rediseño de UX para que la elección de módulo refleje el sidebar.
|
|
8
|
+
- El selector de módulo plano se reemplaza por un **árbol jerárquico** agrupado por `addon_label` (acordeón colapsable, ícono por módulo, badge de acciones otorgadas N/M, búsqueda que filtra el árbol). Los módulos sin addon caen en el grupo "Sistema".
|
|
9
|
+
- El selector de **rol** queda limpio: combobox con acciones de **editar** y **eliminar** inline (íconos lápiz/basurero a la derecha), sin el chip removible separado.
|
|
10
|
+
- Estados claros del panel de acciones: "elige un rol" / "elige un módulo" / skeleton de carga; el grid se habilita en cuanto hay rol + módulo. El panel titula con el módulo activo y su addon.
|
|
11
|
+
- Nuevo campo opcional `icon` en `PermissionModuleDef` (lucide) para mostrar el ícono del módulo en el árbol y el panel.
|
|
12
|
+
- Helpers exportados nuevos: `groupModules`, `filterModuleGroups` (+ tipo `ModuleGroup`).
|
|
13
|
+
|
|
14
|
+
Sin cambios en la firma de props de `PermissionsManager` (solo render interno; `PermissionModuleDef.icon` es aditivo/opcional).
|
|
15
|
+
|
|
16
|
+
## 18.14.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- 184afb8: Permisos dinámicos rol × módulo × acción (Pieza C del contrato de permisos dinámicos).
|
|
21
|
+
|
|
22
|
+
**Primitivas de runtime (`permissions-context`)**
|
|
23
|
+
- `<PermissionsProvider permissions={string[]} isAdmin={boolean}>` — el host carga `/permissions/me` y monta el provider una vez en el root.
|
|
24
|
+
- `useCan(): (capability: string) => boolean` — `isAdmin` ⇒ todo permitido; la lista permite la capability exacta o el wildcard `*`. **Sin provider montado devuelve siempre `true`**, así que los hosts existentes no cambian de comportamiento hasta que opten por el gating.
|
|
25
|
+
- `usePermissionsActive()`, `makeCan()`, `modelCapability()`, `capabilityForActionKey()` (mapea `view→index`, `edit→update`) y `gateTableMetadata()` exportados para hosts con tablas propias.
|
|
26
|
+
|
|
27
|
+
**`<PermissionsManager>` — vista pro "Permisos y Roles"**
|
|
28
|
+
|
|
29
|
+
Transport-agnostic (loaders/mutators por props: `loadModules`, `loadRoles`, `loadRolePermissions`, `syncRolePermissions`, `createRole?/updateRole?/deleteRole?`). Panel de rol con selector buscable + chip removible y CRUD de rol (oculto si faltan los mutators), sección "Permisos Generales" (`general.*` del mismo rol), selector de módulo agrupado por addon y buscable, grid "Acciones permitidas" con ícono + label por acción, contador N/M, marcar-todo/limpiar, estado dirty visible y guardado que sincroniza el set completo del rol activo. Textos en español, estética shadcn del SDK.
|
|
30
|
+
|
|
31
|
+
**Gating en las superficies dinámicas (solo con provider activo)**
|
|
32
|
+
- `DynamicTable`: Exportar/Importar requieren `model.export|import`; las row actions (custom y el trío implícito Ver/Editar/Eliminar) se filtran por `can(lowercase(model).<action>)`.
|
|
33
|
+
- `DynamicCRUDPage`: botón Crear/Exportar/Importar gated por `model.create|export|import`.
|
|
34
|
+
- `ModelActionToolbar`: actions `table`/`create` filtradas por capability.
|
|
35
|
+
|
|
36
|
+
Sin `<PermissionsProvider>` todo queda visible exactamente como hoy.
|
|
37
|
+
|
|
3
38
|
## 18.13.3
|
|
4
39
|
|
|
5
40
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;AAad,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;mDAC+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AASD,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACjC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,sBAAsB,CAAA;IAC7B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,sDAAsD;IACtD,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,qBAmM1D"}
|
|
@@ -33,6 +33,7 @@ import { ExportDialog } from './dialogs/export';
|
|
|
33
33
|
import { ImportDialog } from './dialogs/import';
|
|
34
34
|
import { getModelExtension } from './model-extension-registry';
|
|
35
35
|
import { ModelActionToolbar } from './model-action-toolbar';
|
|
36
|
+
import { useCan, modelCapability } from './permissions-context';
|
|
36
37
|
const defaultStrings = {
|
|
37
38
|
refresh: 'Refresh',
|
|
38
39
|
export: 'Export',
|
|
@@ -96,9 +97,13 @@ export function DynamicCRUDPage(props) {
|
|
|
96
97
|
// showing one in the page chrome is just visual duplication. Apps that
|
|
97
98
|
// want it back can pass `hideRefresh={false}`.
|
|
98
99
|
const effectiveHideRefresh = hideRefresh ?? ext?.hideRefresh ?? true;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// Capability gating — no-op unless the host mounts <PermissionsProvider>
|
|
101
|
+
// (useCan defaults to always-true), in which case create/export/import
|
|
102
|
+
// require `lowercase(model).create|export|import`.
|
|
103
|
+
const can = useCan();
|
|
104
|
+
const showCreate = enableCRUD && !effectiveHideCreate && can(modelCapability(model, 'create'));
|
|
105
|
+
const showImport = enableCRUD && !effectiveHideImport && can(modelCapability(model, 'import'));
|
|
106
|
+
const showExport = !effectiveHideExport && can(modelCapability(model, 'export'));
|
|
102
107
|
const showRefresh = !effectiveHideRefresh;
|
|
103
108
|
const handleRefresh = useCallback(() => {
|
|
104
109
|
setRefreshKey((k) => k + 1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAWnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,GACX,EAAE,iBAAiB,+BA24BnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -28,6 +28,7 @@ import { defaultGetDynamicColumns, DATE_CELL_TYPES, aggregateOf, formatAggregate
|
|
|
28
28
|
import { OptionsContext } from './options-context';
|
|
29
29
|
import { ActionModalDispatcher } from './action-modal-dispatcher';
|
|
30
30
|
import { getSearchableColumnKeys } from './column-visibility';
|
|
31
|
+
import { useCan, usePermissionsActive, gateTableMetadata } from './permissions-context';
|
|
31
32
|
import { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
32
33
|
import { ExportDialog } from './dialogs/export';
|
|
33
34
|
import { ImportDialog } from './dialogs/import';
|
|
@@ -271,6 +272,18 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
271
272
|
// column" behaviour by not narrowing the request. An empty array means
|
|
272
273
|
// every column was explicitly opted out → skip sending `search` at all.
|
|
273
274
|
const searchableKeys = useMemo(() => (metadata ? getSearchableColumnKeys(metadata) : null), [metadata]);
|
|
275
|
+
// Permission gating — only active when the host mounts <PermissionsProvider>.
|
|
276
|
+
// Without it `viewMetadata === metadata` and nothing changes (everything
|
|
277
|
+
// stays visible, exactly the legacy behaviour). With it, export/import
|
|
278
|
+
// buttons and row actions (incl. the implicit View/Edit/Delete trio) are
|
|
279
|
+
// filtered by `can(lowercase(model).<action>)`.
|
|
280
|
+
const can = useCan();
|
|
281
|
+
const permissionsActive = usePermissionsActive();
|
|
282
|
+
const viewMetadata = useMemo(() => {
|
|
283
|
+
if (!metadata || !permissionsActive)
|
|
284
|
+
return metadata;
|
|
285
|
+
return gateTableMetadata(metadata, model, can, (key, fallback) => t(key, { defaultValue: fallback }));
|
|
286
|
+
}, [metadata, permissionsActive, can, model, t]);
|
|
274
287
|
const buildFilterParams = useCallback(() => {
|
|
275
288
|
const params = {};
|
|
276
289
|
if (sorting.length > 0) {
|
|
@@ -576,20 +589,20 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
576
589
|
return map;
|
|
577
590
|
}, [metadata, filterOptionsMap, dynamicFilters, handleDynamicFilterChange]);
|
|
578
591
|
const columns = useMemo(() => {
|
|
579
|
-
if (!
|
|
592
|
+
if (!viewMetadata)
|
|
580
593
|
return [];
|
|
581
594
|
// Row-action column only renders per-row actions. Table-level placements
|
|
582
595
|
// ("table"/"create") are surfaced by <ModelActionToolbar> at the page
|
|
583
596
|
// level, so strip them here to avoid a meaningless per-row button.
|
|
584
|
-
const rowMetadata =
|
|
585
|
-
? { ...
|
|
586
|
-
:
|
|
597
|
+
const rowMetadata = viewMetadata.actions?.some((a) => a.placement === 'table' || a.placement === 'create')
|
|
598
|
+
? { ...viewMetadata, actions: viewMetadata.actions.filter((a) => !a.placement || a.placement === 'row') }
|
|
599
|
+
: viewMetadata;
|
|
587
600
|
const baseColumns = getDynamicColumns(rowMetadata, handleInternalAction, t, i18n.language, columnFilterConfigs, timeZone, currency);
|
|
588
601
|
const filteredBase = baseColumns.filter((col) => !hiddenColumns.includes(col.id));
|
|
589
602
|
const actionsCol = filteredBase.find((c) => c.id === 'actions');
|
|
590
603
|
const otherCols = filteredBase.filter((c) => c.id !== 'actions');
|
|
591
604
|
return [...otherCols, ...extraColumns, ...(actionsCol ? [actionsCol] : [])];
|
|
592
|
-
}, [
|
|
605
|
+
}, [viewMetadata, handleInternalAction, hiddenColumns, extraColumns, t, i18n.language, columnFilterConfigs, getDynamicColumns, timeZone, currency]);
|
|
593
606
|
const filters = useMemo(() => [], []);
|
|
594
607
|
const table = useReactTable({
|
|
595
608
|
data,
|
|
@@ -620,7 +633,7 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
620
633
|
if (!metadata) {
|
|
621
634
|
return _jsx("div", { className: "text-center text-muted-foreground py-8", children: "Error al cargar la configuraci\u00F3n de la tabla." });
|
|
622
635
|
}
|
|
623
|
-
return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [
|
|
636
|
+
return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [viewMetadata?.canExport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), viewMetadata?.canImport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsx("div", { className: 'hidden sm:block flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: _jsxs(Table, { noWrapper: true, className: cn('min-w-max w-full', aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && 'h-full'), children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
|
|
624
637
|
const isActionsColumn = header.id === 'actions';
|
|
625
638
|
return (_jsx(TableHead, { colSpan: header.colSpan, style: header.column.columnDef.size ? { width: header.column.columnDef.size } : undefined, className: cn('bg-card border-b h-10', isActionsColumn && 'sticky right-0 z-20 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
|
|
626
639
|
}) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : table.getRowModel().rows?.length ? (_jsxs(_Fragment, { children: [table.getRowModel().rows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', className: cn(onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: row.getVisibleCells().map((cell) => {
|
|
@@ -646,5 +659,5 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
646
659
|
const label = typeof header === 'string' ? header : cell.column.id;
|
|
647
660
|
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));
|
|
648
661
|
}), 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));
|
|
649
|
-
})) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), _jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] })) }), _jsx("div", { className: 'shrink-0 pt-4', children: _jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions }) })] }), _jsx(AlertDialog, { open: !!rowToDelete, onOpenChange: (open) => !open && setRowToDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "\u00BFEst\u00E1 absolutamente seguro?" }), _jsx(AlertDialogDescription, { children: "Esta acci\u00F3n no se puede deshacer. Esto eliminar\u00E1 permanentemente el registro seleccionado de nuestros servidores." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isDeleting, children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmDelete(); }, className: "bg-red-600 hover:bg-red-700", disabled: isDeleting, children: isDeleting ? 'Eliminando...' : 'Eliminar' })] })] }) }), _jsx(AlertDialog, { open: showBulkDeleteConfirm, onOpenChange: (open) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: isBulkDeleting ? 'Eliminando registros...' : '¿Eliminar múltiples registros?' }), _jsx(AlertDialogDescription, { children: isBulkDeleting ? (_jsxs("div", { className: "space-y-4 mt-4", children: [_jsx(Progress, { value: (bulkDeleteProgress / bulkDeleteTotal) * 100 }), _jsxs("p", { className: "text-center text-sm", children: ["Procesando ", bulkDeleteProgress, " de ", bulkDeleteTotal, " registros..."] })] })) : (_jsxs(_Fragment, { children: ["Esta acci\u00F3n no se puede deshacer. Se eliminar\u00E1n permanentemente ", _jsx("strong", { children: Object.keys(rowSelection).length }), " registro(s) de nuestros servidores."] })) })] }), !isBulkDeleting && (_jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmBulkDelete(); }, className: "bg-red-600 hover:bg-red-700", children: "Eliminar todos" })] }))] }) }), _jsx(DynamicRecordDialog, { open: recordDialog.open, onOpenChange: (open) => setRecordDialog((prev) => ({ ...prev, open })), mode: recordDialog.mode, model: model, recordId: recordDialog.recordId, endpoint: endpoint, onSaved: handleRefresh }),
|
|
662
|
+
})) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), _jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] })) }), _jsx("div", { className: 'shrink-0 pt-4', children: _jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions }) })] }), _jsx(AlertDialog, { open: !!rowToDelete, onOpenChange: (open) => !open && setRowToDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "\u00BFEst\u00E1 absolutamente seguro?" }), _jsx(AlertDialogDescription, { children: "Esta acci\u00F3n no se puede deshacer. Esto eliminar\u00E1 permanentemente el registro seleccionado de nuestros servidores." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isDeleting, children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmDelete(); }, className: "bg-red-600 hover:bg-red-700", disabled: isDeleting, children: isDeleting ? 'Eliminando...' : 'Eliminar' })] })] }) }), _jsx(AlertDialog, { open: showBulkDeleteConfirm, onOpenChange: (open) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: isBulkDeleting ? 'Eliminando registros...' : '¿Eliminar múltiples registros?' }), _jsx(AlertDialogDescription, { children: isBulkDeleting ? (_jsxs("div", { className: "space-y-4 mt-4", children: [_jsx(Progress, { value: (bulkDeleteProgress / bulkDeleteTotal) * 100 }), _jsxs("p", { className: "text-center text-sm", children: ["Procesando ", bulkDeleteProgress, " de ", bulkDeleteTotal, " registros..."] })] })) : (_jsxs(_Fragment, { children: ["Esta acci\u00F3n no se puede deshacer. Se eliminar\u00E1n permanentemente ", _jsx("strong", { children: Object.keys(rowSelection).length }), " registro(s) de nuestros servidores."] })) })] }), !isBulkDeleting && (_jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmBulkDelete(); }, className: "bg-red-600 hover:bg-red-700", children: "Eliminar todos" })] }))] }) }), _jsx(DynamicRecordDialog, { open: recordDialog.open, onOpenChange: (open) => setRecordDialog((prev) => ({ ...prev, open })), mode: recordDialog.mode, model: model, recordId: recordDialog.recordId, endpoint: endpoint, onSaved: handleRefresh }), viewMetadata?.canExport && (_jsx(ExportDialog, { open: exportOpen, onOpenChange: setExportOpen, model: model, metadata: metadata, currentFilters: buildFilterParams(), hasActiveFilters: hasActiveFilters })), viewMetadata?.canImport && (_jsx(ImportDialog, { open: importOpen, onOpenChange: setImportOpen, model: model, metadata: metadata, onImported: handleRefresh })), actionModal.action && (_jsx(ActionModalDispatcher, { open: actionModal.open, onOpenChange: (open) => setActionModal((prev) => ({ ...prev, open })), action: actionModal.action, model: model, record: actionModal.record, endpoint: endpoint, onSuccess: handleRefresh })), _jsx(DataTableBulkActions, { table: table, entityName: "registro", children: _jsxs(Button, { variant: "destructive", size: "sm", className: "h-8", onClick: () => setShowBulkDeleteConfirm(true), children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5" }), " Eliminar"] }) })] }));
|
|
650
663
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export * from './addon-loader';
|
|
|
8
8
|
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, type AddonLayout, type AddonLayoutProviderProps, } from './addon-layout-context';
|
|
9
9
|
export * from './slot';
|
|
10
10
|
export * from './capability-gate';
|
|
11
|
+
export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, type CanFn, type PermissionsProviderProps, } from './permissions-context';
|
|
12
|
+
export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, type PermissionsManagerProps, type PermissionsCatalog, type PermissionModuleDef, type PermissionActionDef, type GeneralPermissionDef, type RoleDef, type RoleInput, } from './permissions-manager';
|
|
11
13
|
export * from './org-runtime-context';
|
|
12
14
|
export * from './org-runtime-provider';
|
|
13
15
|
export * from './navigation-builder';
|
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":"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"}
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ export * from './addon-loader';
|
|
|
13
13
|
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, } from './addon-layout-context';
|
|
14
14
|
export * from './slot';
|
|
15
15
|
export * from './capability-gate';
|
|
16
|
+
export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, } from './permissions-context';
|
|
17
|
+
export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, } from './permissions-manager';
|
|
16
18
|
export * from './org-runtime-context';
|
|
17
19
|
export * from './org-runtime-provider';
|
|
18
20
|
export * from './navigation-builder';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-action-toolbar.d.ts","sourceRoot":"","sources":["../src/model-action-toolbar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"model-action-toolbar.d.ts","sourceRoot":"","sources":["../src/model-action-toolbar.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,gBAAgB,EAAiC,MAAM,SAAS,CAAA;AAE9E,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExD,MAAM,WAAW,uBAAuB;IACpC,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAmBD;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,eAAe,EAAuB,EAClD,QAAQ,CAAC,EAAE,gBAAgB,EAAE,GAC9B,gBAAgB,EAAE,CA2BpB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,UAA+B,EAC/B,QAAQ,EACR,SAAS,GACZ,EAAE,uBAAuB,sCAmDzB"}
|
|
@@ -24,6 +24,7 @@ import { useApi } from './api-context';
|
|
|
24
24
|
import { useMetadataCache } from './metadata-cache';
|
|
25
25
|
import { DynamicIcon } from './dynamic-icon';
|
|
26
26
|
import { ActionModalDispatcher } from './action-modal-dispatcher';
|
|
27
|
+
import { useCan, modelCapability } from './permissions-context';
|
|
27
28
|
const DEFAULT_PLACEMENTS = ['table', 'create'];
|
|
28
29
|
function toActionMetadata(a) {
|
|
29
30
|
return {
|
|
@@ -70,7 +71,11 @@ export function useModelActions(model, placements = DEFAULT_PLACEMENTS, provided
|
|
|
70
71
|
return useMemo(() => all.filter((a) => placements.includes((a.placement ?? 'row'))), [all, placements]);
|
|
71
72
|
}
|
|
72
73
|
export function ModelActionToolbar({ model, endpoint, actions, placements = DEFAULT_PLACEMENTS, onChange, className, }) {
|
|
73
|
-
const
|
|
74
|
+
const all = useModelActions(model, placements, actions);
|
|
75
|
+
// Capability gating — always-true without a <PermissionsProvider>. Custom
|
|
76
|
+
// table/create actions map onto `lowercase(model).<action_key>`.
|
|
77
|
+
const can = useCan();
|
|
78
|
+
const surfaced = useMemo(() => all.filter((a) => can(modelCapability(model, a.key))), [all, can, model]);
|
|
74
79
|
const [active, setActive] = useState(null);
|
|
75
80
|
const dataEndpoint = endpoint ?? `/data/${model}/me`;
|
|
76
81
|
if (surfaced.length === 0)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { TableMetadata } from './types';
|
|
3
|
+
/** Predicate answering "can the current user use this capability?". */
|
|
4
|
+
export type CanFn = (capability: string) => boolean;
|
|
5
|
+
export interface PermissionsProviderProps {
|
|
6
|
+
/** Granted capabilities (`"pos_orders.create"`, `"general.x"`, or `"*"`). */
|
|
7
|
+
permissions: string[];
|
|
8
|
+
/** Superrole bypass — admins/owners see everything, no filtering at all. */
|
|
9
|
+
isAdmin: boolean;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Builds the capability predicate from a raw permission list. Pure — exported
|
|
14
|
+
* so hosts/tests can evaluate permissions outside React.
|
|
15
|
+
*/
|
|
16
|
+
export declare function makeCan(permissions: string[], isAdmin: boolean): CanFn;
|
|
17
|
+
export declare function PermissionsProvider({ permissions, isAdmin, children }: PermissionsProviderProps): React.JSX.Element;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the capability predicate. Without a <PermissionsProvider> ancestor
|
|
20
|
+
* it returns an always-true function — existing hosts that never mount the
|
|
21
|
+
* provider keep today's "everything visible" behaviour.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useCan(): CanFn;
|
|
24
|
+
/** True when a <PermissionsProvider> is mounted above (permission gating active). */
|
|
25
|
+
export declare function usePermissionsActive(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Maps a row/table action key onto the capability action segment. The UI's
|
|
28
|
+
* legacy `view`/`edit` keys correspond to the kernel's `index`/`update`
|
|
29
|
+
* capabilities; everything else (delete, custom keys) maps verbatim.
|
|
30
|
+
*/
|
|
31
|
+
export declare function capabilityForActionKey(actionKey: string): string;
|
|
32
|
+
/** Canonical capability for an action on a model: `lowercase(model).<action>`. */
|
|
33
|
+
export declare function modelCapability(model: string, actionKey: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Applies the capability predicate to a model's table metadata:
|
|
36
|
+
* - `canExport` / `canImport` are ANDed with `can(model.export|import)`.
|
|
37
|
+
* - explicit row/table actions are filtered by `can(model.<key>)` (with the
|
|
38
|
+
* view→index / edit→update mapping above).
|
|
39
|
+
* - when the metadata has NO explicit actions but `enableCRUDActions` is on,
|
|
40
|
+
* the implicit View/Edit/Delete trio is materialized here as explicit
|
|
41
|
+
* actions so individual entries can be dropped; `tx` resolves their labels
|
|
42
|
+
* (defaults to the Spanish fallbacks used by the column factory).
|
|
43
|
+
*
|
|
44
|
+
* Pure + idempotent. Callers should only invoke it when a provider is active
|
|
45
|
+
* (`usePermissionsActive()`), otherwise pass the metadata through untouched.
|
|
46
|
+
*/
|
|
47
|
+
export declare function gateTableMetadata(metadata: TableMetadata, model: string, can: CanFn, tx?: (i18nKey: string, fallback: string) => string): TableMetadata;
|
|
48
|
+
//# sourceMappingURL=permissions-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions-context.d.ts","sourceRoot":"","sources":["../src/permissions-context.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAA6C,MAAM,OAAO,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAoB,MAAM,SAAS,CAAA;AAM9D,uEAAuE;AACvE,MAAM,MAAM,KAAK,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAA;AAEnD,MAAM,WAAW,wBAAwB;IACrC,6EAA6E;IAC7E,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAMD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,KAAK,CAKtE;AAMD,wBAAgB,mBAAmB,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,wBAAwB,qBAG/F;AAED;;;;GAIG;AACH,wBAAgB,MAAM,IAAI,KAAK,CAE9B;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAOD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,KAAK,EACV,EAAE,GAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAmC,GAC/E,aAAa,CAkCf"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// permissions-context — runtime permission primitives for dynamic hosts.
|
|
3
|
+
//
|
|
4
|
+
// The host loads the session's capability set (e.g. ops `GET /permissions/me`)
|
|
5
|
+
// and mounts <PermissionsProvider permissions={caps} isAdmin={me.is_admin}>
|
|
6
|
+
// once at the root. Any SDK component (or host code) then calls `useCan()` to
|
|
7
|
+
// gate UI by capability:
|
|
8
|
+
//
|
|
9
|
+
// const can = useCan()
|
|
10
|
+
// can('pos_orders.create') // → boolean
|
|
11
|
+
//
|
|
12
|
+
// Capability format is the canonical `lowercase(<model_table>).<action_key>`
|
|
13
|
+
// derived from installed manifests (CRUD: index|create|update|delete|export|
|
|
14
|
+
// import; custom actions use their own key, e.g. `pos_orders.pagar`). General
|
|
15
|
+
// flags live under the `general` module (`general.work_after_hours`).
|
|
16
|
+
//
|
|
17
|
+
// Semantics:
|
|
18
|
+
// - isAdmin → every capability allowed (superrole bypass mirror).
|
|
19
|
+
// - the list contains the exact capability or the `*` wildcard → allowed.
|
|
20
|
+
// - NO provider mounted → `useCan()` returns an always-true function, so
|
|
21
|
+
// every existing host keeps its current behaviour (nothing is hidden).
|
|
22
|
+
// Deny-by-default only kicks in once the host opts in by mounting the
|
|
23
|
+
// provider; the backend enforcement remains the source of truth.
|
|
24
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Core
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/**
|
|
29
|
+
* Builds the capability predicate from a raw permission list. Pure — exported
|
|
30
|
+
* so hosts/tests can evaluate permissions outside React.
|
|
31
|
+
*/
|
|
32
|
+
export function makeCan(permissions, isAdmin) {
|
|
33
|
+
if (isAdmin)
|
|
34
|
+
return () => true;
|
|
35
|
+
const set = new Set(permissions);
|
|
36
|
+
if (set.has('*'))
|
|
37
|
+
return () => true;
|
|
38
|
+
return (capability) => set.has(capability);
|
|
39
|
+
}
|
|
40
|
+
const ALWAYS_ALLOW = () => true;
|
|
41
|
+
const PermissionsContext = createContext(null);
|
|
42
|
+
export function PermissionsProvider({ permissions, isAdmin, children }) {
|
|
43
|
+
const can = useMemo(() => makeCan(permissions, isAdmin), [permissions, isAdmin]);
|
|
44
|
+
return _jsx(PermissionsContext.Provider, { value: can, children: children });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns the capability predicate. Without a <PermissionsProvider> ancestor
|
|
48
|
+
* it returns an always-true function — existing hosts that never mount the
|
|
49
|
+
* provider keep today's "everything visible" behaviour.
|
|
50
|
+
*/
|
|
51
|
+
export function useCan() {
|
|
52
|
+
return useContext(PermissionsContext) ?? ALWAYS_ALLOW;
|
|
53
|
+
}
|
|
54
|
+
/** True when a <PermissionsProvider> is mounted above (permission gating active). */
|
|
55
|
+
export function usePermissionsActive() {
|
|
56
|
+
return useContext(PermissionsContext) !== null;
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Table-metadata gating (consumed by DynamicTable / DynamicCRUDPage /
|
|
60
|
+
// ModelActionToolbar — exported for hosts with bespoke tables)
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* Maps a row/table action key onto the capability action segment. The UI's
|
|
64
|
+
* legacy `view`/`edit` keys correspond to the kernel's `index`/`update`
|
|
65
|
+
* capabilities; everything else (delete, custom keys) maps verbatim.
|
|
66
|
+
*/
|
|
67
|
+
export function capabilityForActionKey(actionKey) {
|
|
68
|
+
if (actionKey === 'view')
|
|
69
|
+
return 'index';
|
|
70
|
+
if (actionKey === 'edit')
|
|
71
|
+
return 'update';
|
|
72
|
+
return actionKey;
|
|
73
|
+
}
|
|
74
|
+
/** Canonical capability for an action on a model: `lowercase(model).<action>`. */
|
|
75
|
+
export function modelCapability(model, actionKey) {
|
|
76
|
+
return `${model.toLowerCase()}.${capabilityForActionKey(actionKey)}`;
|
|
77
|
+
}
|
|
78
|
+
const DEFAULT_TRIO = [
|
|
79
|
+
{ key: 'view', i18nKey: 'datatable.view', fallback: 'Ver', icon: 'Eye' },
|
|
80
|
+
{ key: 'edit', i18nKey: 'datatable.edit', fallback: 'Editar', icon: 'Pencil' },
|
|
81
|
+
{ key: 'delete', i18nKey: 'datatable.delete', fallback: 'Eliminar', icon: 'Trash2' },
|
|
82
|
+
];
|
|
83
|
+
/**
|
|
84
|
+
* Applies the capability predicate to a model's table metadata:
|
|
85
|
+
* - `canExport` / `canImport` are ANDed with `can(model.export|import)`.
|
|
86
|
+
* - explicit row/table actions are filtered by `can(model.<key>)` (with the
|
|
87
|
+
* view→index / edit→update mapping above).
|
|
88
|
+
* - when the metadata has NO explicit actions but `enableCRUDActions` is on,
|
|
89
|
+
* the implicit View/Edit/Delete trio is materialized here as explicit
|
|
90
|
+
* actions so individual entries can be dropped; `tx` resolves their labels
|
|
91
|
+
* (defaults to the Spanish fallbacks used by the column factory).
|
|
92
|
+
*
|
|
93
|
+
* Pure + idempotent. Callers should only invoke it when a provider is active
|
|
94
|
+
* (`usePermissionsActive()`), otherwise pass the metadata through untouched.
|
|
95
|
+
*/
|
|
96
|
+
export function gateTableMetadata(metadata, model, can, tx = (_k, fallback) => fallback) {
|
|
97
|
+
const allowed = (key) => can(modelCapability(model, key));
|
|
98
|
+
const explicit = metadata.actions ?? [];
|
|
99
|
+
const hasExplicit = (metadata.hasActions ?? explicit.length > 0) && explicit.length > 0;
|
|
100
|
+
const base = hasExplicit
|
|
101
|
+
? explicit
|
|
102
|
+
: metadata.enableCRUDActions
|
|
103
|
+
? DEFAULT_TRIO.map((a) => ({
|
|
104
|
+
key: a.key,
|
|
105
|
+
name: a.key,
|
|
106
|
+
label: tx(a.i18nKey, a.fallback),
|
|
107
|
+
icon: a.icon,
|
|
108
|
+
}))
|
|
109
|
+
: [];
|
|
110
|
+
const actions = base.filter((a) => allowed(a.key));
|
|
111
|
+
return {
|
|
112
|
+
...metadata,
|
|
113
|
+
actions,
|
|
114
|
+
hasActions: actions.length > 0,
|
|
115
|
+
// The column factory synthesizes the implicit CRUD trio whenever the
|
|
116
|
+
// action list is empty and this flag is on — turn it off once gating
|
|
117
|
+
// has materialized (and possibly emptied) the list so nothing leaks
|
|
118
|
+
// back in.
|
|
119
|
+
enableCRUDActions: metadata.enableCRUDActions && actions.length > 0,
|
|
120
|
+
canExport: Boolean(metadata.canExport) && allowed('export'),
|
|
121
|
+
canImport: Boolean(metadata.canImport) && allowed('import'),
|
|
122
|
+
canCreate: metadata.canCreate === undefined ? undefined : metadata.canCreate && allowed('create'),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface PermissionActionDef {
|
|
3
|
+
/** Canonical action key (`index`, `create`, …, or a custom key like `pagar`). */
|
|
4
|
+
key: string;
|
|
5
|
+
/** Localized label ("Listar", "Pagar"). */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Lucide icon name from the manifest action (optional). */
|
|
8
|
+
icon?: string;
|
|
9
|
+
/** `crud` for the derived CRUD set, `custom` for manifest actions. */
|
|
10
|
+
kind?: 'crud' | 'custom' | string;
|
|
11
|
+
}
|
|
12
|
+
export interface PermissionModuleDef {
|
|
13
|
+
/** Module key = lowercase model table (`pos_orders`). */
|
|
14
|
+
key: string;
|
|
15
|
+
/** Localized module label ("Pedidos POS"). */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Module icon (lucide name) — mirrors the sidebar entry. */
|
|
18
|
+
icon?: string;
|
|
19
|
+
/** Owning addon key (`pos`). */
|
|
20
|
+
addon_key?: string;
|
|
21
|
+
/** Localized addon label ("Punto de venta") — used to group the tree. */
|
|
22
|
+
addon_label?: string;
|
|
23
|
+
actions: PermissionActionDef[];
|
|
24
|
+
}
|
|
25
|
+
export interface GeneralPermissionDef {
|
|
26
|
+
/** Full capability key (`general.work_after_hours`). */
|
|
27
|
+
key: string;
|
|
28
|
+
label: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface PermissionsCatalog {
|
|
32
|
+
modules: PermissionModuleDef[];
|
|
33
|
+
general: GeneralPermissionDef[];
|
|
34
|
+
}
|
|
35
|
+
export interface RoleDef {
|
|
36
|
+
id: string;
|
|
37
|
+
/** Stable role key ("cashier"). */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Human label ("Cajero"). Falls back to `name` when omitted. */
|
|
40
|
+
label?: string;
|
|
41
|
+
/** Accent color (hex) for the role chip. */
|
|
42
|
+
color?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface RoleInput {
|
|
45
|
+
name: string;
|
|
46
|
+
label?: string;
|
|
47
|
+
color?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface PermissionsManagerProps {
|
|
50
|
+
/** Loads the module×action universe + general flags. */
|
|
51
|
+
loadModules: () => Promise<PermissionsCatalog>;
|
|
52
|
+
/** Loads every assignable role. */
|
|
53
|
+
loadRoles: () => Promise<RoleDef[]>;
|
|
54
|
+
/** Loads the capabilities currently granted to a role. */
|
|
55
|
+
loadRolePermissions: (roleId: string) => Promise<string[]>;
|
|
56
|
+
/** Persists the FULL granted capability set of a role. */
|
|
57
|
+
syncRolePermissions: (roleId: string, capabilities: string[]) => Promise<void>;
|
|
58
|
+
/** Optional role CRUD — omitting one hides its control. */
|
|
59
|
+
createRole?: (input: RoleInput) => Promise<RoleDef | void>;
|
|
60
|
+
updateRole?: (roleId: string, input: RoleInput) => Promise<RoleDef | void>;
|
|
61
|
+
deleteRole?: (roleId: string) => Promise<void>;
|
|
62
|
+
/** Page heading. Defaults to "Permisos y Roles". */
|
|
63
|
+
title?: string;
|
|
64
|
+
className?: string;
|
|
65
|
+
}
|
|
66
|
+
/** Capability for a catalog module action: `lowercase(moduleKey).actionKey`. */
|
|
67
|
+
export declare function moduleActionCapability(moduleKey: string, actionKey: string): string;
|
|
68
|
+
/** All capabilities of one module. */
|
|
69
|
+
export declare function moduleCapabilities(module: PermissionModuleDef): string[];
|
|
70
|
+
/** How many of the module's capabilities are in the granted set. */
|
|
71
|
+
export declare function grantedCountForModule(granted: ReadonlySet<string>, module: PermissionModuleDef): number;
|
|
72
|
+
export declare function capabilitySetsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean;
|
|
73
|
+
/** Default lucide icon when the manifest action doesn't declare one. */
|
|
74
|
+
export declare function defaultActionIcon(actionKey: string, kind?: string): string;
|
|
75
|
+
/** [groupLabel, modules] buckets in stable insertion order. */
|
|
76
|
+
export interface ModuleGroup {
|
|
77
|
+
label: string;
|
|
78
|
+
modules: PermissionModuleDef[];
|
|
79
|
+
}
|
|
80
|
+
export declare function groupModules(modules: PermissionModuleDef[]): ModuleGroup[];
|
|
81
|
+
/** Filter the grouped tree by a folded query against module + group labels. */
|
|
82
|
+
export declare function filterModuleGroups(groups: ModuleGroup[], query: string): ModuleGroup[];
|
|
83
|
+
export declare function PermissionsManager({ loadModules, loadRoles, loadRolePermissions, syncRolePermissions, createRole, updateRole, deleteRole, title, className, }: PermissionsManagerProps): React.JSX.Element;
|
|
84
|
+
//# sourceMappingURL=permissions-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions-manager.d.ts","sourceRoot":"","sources":["../src/permissions-manager.tsx"],"names":[],"mappings":"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"}
|