@asteby/metacore-runtime-react 18.15.0 → 18.16.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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 18.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cc4851a: PermissionsManager: la elección de módulo pasa de un **árbol con acordeones/folders** a una **lista plana idéntica al sidebar**.
8
+ - Cada grupo se dibuja como un **header gris no colapsable** (uppercase tracking, estilo "Módulos"/"Sistema" del sidebar) seguido de sus módulos como **filas clickeables** (ícono + label + badge contador N/M). El click directo en una fila selecciona el módulo y muestra su grid de acciones a la derecha. CERO Collapsible/acordeón/folder. La búsqueda filtra las filas (accent/case-insensitive por label de módulo o título de grupo) y oculta los grupos sin coincidencias.
9
+ - **Nuevo shape de entrada (desacoplado)**: `loadModules()` ahora puede devolver `{ groups: ModuleGroup[]; general: GeneralPermissionDef[] }` donde `ModuleGroup = { title: string; modules: ModuleDef[] }` (`title: ''` → sin header) y cada módulo es `{ key, label, icon?, kind: 'model' | 'screen', actions: ActionDef[] }`. La capability final es `${module.key}.${action.key}`; para pantallas no-modelo el host manda `key: 'screen.<navKey>'` + una acción `{ key: 'access', label: 'Acceder', icon: 'Eye', kind: 'screen' }` → capability `screen.<navKey>.access`.
10
+ - **Retrocompat**: si `loadModules` devuelve el shape viejo `{ modules, general }` (flat, sin `kind`), se envuelve en grupos (agrupados por `addon_label`/`addon_key`, fallback "Sistema") y cada módulo se trata como `kind: 'model'`. Los hosts que aún mandan el shape viejo siguen funcionando.
11
+ - Tipos exportados nuevos: `ModuleGroup`, `GroupedPermissionsCatalog`, `FlatPermissionsCatalog` y `kind` en `PermissionModuleDef`/`PermissionActionDef`. Helpers exportados: `normalizeCatalogGroups`, `flattenGroups`, `filterModuleGroups` (firma actualizada a `ModuleGroup`). Se eliminó `groupModules` (reemplazado por `normalizeCatalogGroups`).
12
+ - Intacto: selector de rol limpio (edit/delete inline), permisos generales, dirty tracking + guardar (sync = set completo), guard de cambios sin guardar, i18n español, `createRole`/`updateRole`/`deleteRole` opcionales.
13
+
3
14
  ## 18.15.0
4
15
 
5
16
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareA
9
9
  export * from './slot';
10
10
  export * from './capability-gate';
11
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';
12
+ 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';
13
13
  export * from './org-runtime-context';
14
14
  export * from './org-runtime-provider';
15
15
  export * from './navigation-builder';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,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,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,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,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"}
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,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,SAAS,GACjB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,cAAc,EACd,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,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,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,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"}
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareA
14
14
  export * from './slot';
15
15
  export * from './capability-gate';
16
16
  export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, } from './permissions-context';
17
- export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, } from './permissions-manager';
17
+ export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, normalizeCatalogGroups, flattenGroups, filterModuleGroups, } from './permissions-manager';
18
18
  export * from './org-runtime-context';
19
19
  export * from './org-runtime-provider';
20
20
  export * from './navigation-builder';
@@ -1,37 +1,68 @@
1
1
  import * as React from 'react';
2
2
  export interface PermissionActionDef {
3
- /** Canonical action key (`index`, `create`, …, or a custom key like `pagar`). */
3
+ /** Canonical action key (`index`, `create`, …, a custom `pagar`, or `access`). */
4
4
  key: string;
5
- /** Localized label ("Listar", "Pagar"). */
5
+ /** Localized label ("Listar", "Pagar", "Acceder"). */
6
6
  label: string;
7
7
  /** Lucide icon name from the manifest action (optional). */
8
8
  icon?: string;
9
- /** `crud` for the derived CRUD set, `custom` for manifest actions. */
10
- kind?: 'crud' | 'custom' | string;
9
+ /**
10
+ * `crud` for the derived CRUD set, `custom` for manifest actions,
11
+ * `screen` for the single `access` action of a non-model screen.
12
+ */
13
+ kind?: 'crud' | 'custom' | 'screen' | string;
11
14
  }
12
15
  export interface PermissionModuleDef {
13
- /** Module key = lowercase model table (`pos_orders`). */
16
+ /**
17
+ * Module key.
18
+ * - model: lowercase model table (`pos_orders`).
19
+ * - screen: `screen.<navKey>` (the host prefixes it).
20
+ */
14
21
  key: string;
15
- /** Localized module label ("Pedidos POS"). */
22
+ /** Localized module label ("Pedidos POS", "Terminal"). */
16
23
  label: string;
17
24
  /** Module icon (lucide name) — mirrors the sidebar entry. */
18
25
  icon?: string;
19
- /** Owning addon key (`pos`). */
26
+ /** Whether this entry is a data model or a non-model screen. */
27
+ kind?: 'model' | 'screen';
28
+ /** Owning addon key (`pos`) — legacy shape only, used for grouping. */
20
29
  addon_key?: string;
21
- /** Localized addon label ("Punto de venta") — used to group the tree. */
30
+ /** Localized addon label ("Punto de venta") — legacy shape only. */
22
31
  addon_label?: string;
23
32
  actions: PermissionActionDef[];
24
33
  }
34
+ /**
35
+ * A sidebar-style group: a grey (non-collapsible) header + its modules.
36
+ * `title === ''` → no header (e.g. core/infra modules).
37
+ */
38
+ export interface ModuleGroup {
39
+ title: string;
40
+ modules: PermissionModuleDef[];
41
+ }
25
42
  export interface GeneralPermissionDef {
26
43
  /** Full capability key (`general.work_after_hours`). */
27
44
  key: string;
28
45
  label: string;
29
46
  description?: string;
30
47
  }
31
- export interface PermissionsCatalog {
48
+ /**
49
+ * What `loadModules()` may return.
50
+ *
51
+ * New (preferred) shape — pre-grouped flat list, mirrors the host sidebar:
52
+ * { groups: ModuleGroup[], general }
53
+ *
54
+ * Legacy shape (still accepted, wrapped into a single untitled group) —
55
+ * { modules: PermissionModuleDef[], general }
56
+ */
57
+ export interface GroupedPermissionsCatalog {
58
+ groups: ModuleGroup[];
59
+ general: GeneralPermissionDef[];
60
+ }
61
+ export interface FlatPermissionsCatalog {
32
62
  modules: PermissionModuleDef[];
33
63
  general: GeneralPermissionDef[];
34
64
  }
65
+ export type PermissionsCatalog = GroupedPermissionsCatalog | FlatPermissionsCatalog;
35
66
  export interface RoleDef {
36
67
  id: string;
37
68
  /** Stable role key ("cashier"). */
@@ -47,7 +78,7 @@ export interface RoleInput {
47
78
  color?: string;
48
79
  }
49
80
  export interface PermissionsManagerProps {
50
- /** Loads the module×action universe + general flags. */
81
+ /** Loads the module×action universe + general flags (grouped or flat). */
51
82
  loadModules: () => Promise<PermissionsCatalog>;
52
83
  /** Loads every assignable role. */
53
84
  loadRoles: () => Promise<RoleDef[]>;
@@ -72,13 +103,19 @@ export declare function grantedCountForModule(granted: ReadonlySet<string>, modu
72
103
  export declare function capabilitySetsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean;
73
104
  /** Default lucide icon when the manifest action doesn't declare one. */
74
105
  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. */
106
+ /**
107
+ * Normalize whatever `loadModules` returned into the canonical grouped shape.
108
+ *
109
+ * - New shape (`{ groups }`): passed through (modules default to kind:'model').
110
+ * - Legacy flat shape (`{ modules }`): grouped by `addon_label`/`addon_key`
111
+ * (falling back to "Sistema") so old hosts keep their familiar buckets,
112
+ * every module defaulting to kind:'model'. The grey headers still render,
113
+ * just derived from the addon instead of the sidebar group.
114
+ */
115
+ export declare function normalizeCatalogGroups(catalog: PermissionsCatalog): ModuleGroup[];
116
+ /** Flat list of every module across groups, in render order. */
117
+ export declare function flattenGroups(groups: ModuleGroup[]): PermissionModuleDef[];
118
+ /** Filter the grouped flat list by a folded query against module + group titles. */
82
119
  export declare function filterModuleGroups(groups: ModuleGroup[], query: string): ModuleGroup[];
83
120
  export declare function PermissionsManager({ loadModules, loadRoles, loadRolePermissions, syncRolePermissions, createRole, updateRole, deleteRole, title, className, }: PermissionsManagerProps): React.JSX.Element;
84
121
  //# sourceMappingURL=permissions-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions-manager.d.ts","sourceRoot":"","sources":["../src/permissions-manager.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permissions-manager.d.ts","sourceRoot":"","sources":["../src/permissions-manager.tsx"],"names":[],"mappings":"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"}
@@ -4,16 +4,18 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  // Transport-agnostic: every read/write arrives via props (loaders/mutators),
5
5
  // so each host wires them to its own api client (ops → /api/permissions/*).
6
6
  // The capability universe (modules × actions + general flags) is derived from
7
- // the installed manifests server-side; this component only renders it.
7
+ // the installed manifests + the real sidebar nav server/host-side; this
8
+ // component only renders it.
8
9
  //
9
- // Layout (mirrors the app sidebar so admins recognise what they grant):
10
+ // Layout — a *flat list* that mirrors the app sidebar (NO accordions/folders):
10
11
  // header — title + "Nuevo rol" (primary) + "Guardar permisos" (green).
11
12
  // left — Card "Rol": clean role combobox with inline Editar/Eliminar
12
13
  // icons (no removable chip) + "Permisos Generales" flags.
13
- // — Card "Módulos": a searchable, accordion *tree* grouped by
14
- // `addon_label` (modules without one "Sistema"). Each group
15
- // lists its modules with their icon + a granted-count badge.
16
- // Clicking a module selects it and reveals its action grid.
14
+ // — Card "Módulos": a searchable flat list. Each group renders a
15
+ // non-collapsible grey header (uppercase tracking, like "Módulos"
16
+ // / "Sistema" in the sidebar) followed by its modules as clickable
17
+ // rows (icon + label + granted-count badge). Clicking a row selects
18
+ // that module and reveals its action grid on the right.
17
19
  // right — Card "Acciones permitidas": granted counter N/M, mark-all /
18
20
  // clear, checkbox grid (icon + label per action). Clear empty
19
21
  // states for "pick a role" / "pick a module" / loading.
@@ -23,10 +25,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
23
25
  // state is tracked against the loaded baseline and surfaced next to the
24
26
  // save button.
25
27
  import * as React from 'react';
26
- import { Check, ChevronRight, ChevronsUpDown, CheckCheck, Eraser, Folder, Pencil, Plus, Save, Search, Shield, Trash2, } from 'lucide-react';
28
+ import { Check, ChevronsUpDown, CheckCheck, Eraser, Pencil, Plus, Save, Search, Shield, Trash2, } from 'lucide-react';
27
29
  import { toast } from 'sonner';
28
30
  import { cn } from '@asteby/metacore-ui/lib';
29
- import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Popover, PopoverContent, PopoverTrigger, Separator, Skeleton, } from '@asteby/metacore-ui/primitives';
31
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Popover, PopoverContent, PopoverTrigger, Separator, Skeleton, } from '@asteby/metacore-ui/primitives';
30
32
  import { DynamicIcon } from './dynamic-icon';
31
33
  // ---------------------------------------------------------------------------
32
34
  // Pure helpers (exported for hosts/tests)
@@ -66,16 +68,22 @@ export function defaultActionIcon(actionKey, kind) {
66
68
  return 'Download';
67
69
  case 'import':
68
70
  return 'Upload';
71
+ case 'access':
72
+ return 'Eye';
69
73
  default:
70
- return kind === 'crud' ? 'List' : 'Zap';
74
+ if (kind === 'crud')
75
+ return 'List';
76
+ if (kind === 'screen')
77
+ return 'Eye';
78
+ return 'Zap';
71
79
  }
72
80
  }
73
- /** Group label fallback when a module has no addon ("Sistema" = core/infra). */
81
+ /** Group label fallback when a legacy module has no addon ("Sistema" = core). */
74
82
  const SYSTEM_GROUP = 'Sistema';
75
- function moduleGroupLabel(mod) {
83
+ function legacyGroupLabel(mod) {
76
84
  return mod.addon_label || mod.addon_key || SYSTEM_GROUP;
77
85
  }
78
- /** Accent-insensitive, lowercase fold for tree search. */
86
+ /** Accent-insensitive, lowercase fold for search. */
79
87
  function fold(s) {
80
88
  return s
81
89
  .normalize('NFD')
@@ -99,32 +107,57 @@ const ROLE_COLORS = [
99
107
  '#ec4899',
100
108
  '#6b7280',
101
109
  ];
102
- export function groupModules(modules) {
110
+ /**
111
+ * Normalize whatever `loadModules` returned into the canonical grouped shape.
112
+ *
113
+ * - New shape (`{ groups }`): passed through (modules default to kind:'model').
114
+ * - Legacy flat shape (`{ modules }`): grouped by `addon_label`/`addon_key`
115
+ * (falling back to "Sistema") so old hosts keep their familiar buckets,
116
+ * every module defaulting to kind:'model'. The grey headers still render,
117
+ * just derived from the addon instead of the sidebar group.
118
+ */
119
+ export function normalizeCatalogGroups(catalog) {
120
+ const withKind = (m) => ({
121
+ ...m,
122
+ kind: m.kind ?? 'model',
123
+ });
124
+ if ('groups' in catalog && Array.isArray(catalog.groups)) {
125
+ return catalog.groups.map((g) => ({
126
+ title: g.title ?? '',
127
+ modules: g.modules.map(withKind),
128
+ }));
129
+ }
130
+ const modules = ('modules' in catalog && catalog.modules) || [];
103
131
  const order = [];
104
132
  const byGroup = new Map();
105
- for (const mod of modules) {
106
- const g = moduleGroupLabel(mod);
133
+ for (const raw of modules) {
134
+ const mod = withKind(raw);
135
+ const g = legacyGroupLabel(mod);
107
136
  if (!byGroup.has(g)) {
108
137
  byGroup.set(g, []);
109
138
  order.push(g);
110
139
  }
111
140
  byGroup.get(g).push(mod);
112
141
  }
113
- return order.map((label) => ({ label, modules: byGroup.get(label) }));
142
+ return order.map((title) => ({ title, modules: byGroup.get(title) }));
143
+ }
144
+ /** Flat list of every module across groups, in render order. */
145
+ export function flattenGroups(groups) {
146
+ return groups.flatMap((g) => g.modules);
114
147
  }
115
- /** Filter the grouped tree by a folded query against module + group labels. */
148
+ /** Filter the grouped flat list by a folded query against module + group titles. */
116
149
  export function filterModuleGroups(groups, query) {
117
150
  const q = fold(query).trim();
118
151
  if (!q)
119
152
  return groups;
120
153
  const out = [];
121
154
  for (const g of groups) {
122
- const groupMatches = fold(g.label).includes(q);
155
+ const groupMatches = g.title.length > 0 && fold(g.title).includes(q);
123
156
  const mods = groupMatches
124
157
  ? g.modules
125
158
  : g.modules.filter((m) => fold(m.label).includes(q) || fold(m.key).includes(q));
126
159
  if (mods.length)
127
- out.push({ label: g.label, modules: mods });
160
+ out.push({ title: g.title, modules: mods });
128
161
  }
129
162
  return out;
130
163
  }
@@ -142,17 +175,18 @@ function CapabilityCheck({ checked, disabled, onToggle, icon, label, description
142
175
  }
143
176
  }, className: cn('flex items-start gap-2.5 rounded-md border border-border/60 bg-card px-3 py-2.5 text-sm transition-colors', disabled ? 'opacity-50' : 'cursor-pointer hover:bg-muted/40', checked && 'border-primary/40 bg-primary/5'), children: [_jsx(Checkbox, { checked: checked, "aria-hidden": "true", tabIndex: -1, className: "pointer-events-none mt-0.5 shrink-0" }), icon && (_jsx(DynamicIcon, { name: icon, className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" })), _jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "block truncate font-medium text-foreground", children: label }), description && (_jsx("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: description }))] })] }));
144
177
  }
145
- /** One module row inside the tree. */
146
- function ModuleTreeItem({ module, active, granted, total, onSelect, }) {
178
+ /** One clickable module row in the flat list (mirrors a sidebar item). */
179
+ function ModuleRow({ module, active, granted, total, onSelect, }) {
147
180
  return (_jsxs("button", { type: "button", onClick: onSelect, "aria-current": active || undefined, className: cn('group flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors', active
148
181
  ? 'bg-primary/10 font-medium text-foreground'
149
- : 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'), children: [_jsx(DynamicIcon, { name: module.icon || 'Square', className: cn('h-4 w-4 shrink-0', active ? 'text-primary' : 'text-muted-foreground') }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: module.label }), granted > 0 && (_jsxs(Badge, { variant: granted === total ? 'default' : 'secondary', className: "h-5 shrink-0 px-1.5 text-[10px] tabular-nums", children: [granted, "/", total] }))] }));
182
+ : 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'), children: [_jsx(DynamicIcon, { name: module.icon || (module.kind === 'screen' ? 'Eye' : 'Square'), className: cn('h-4 w-4 shrink-0', active ? 'text-primary' : 'text-muted-foreground') }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: module.label }), granted > 0 && (_jsxs(Badge, { variant: granted === total ? 'default' : 'secondary', className: "h-5 shrink-0 px-1.5 text-[10px] tabular-nums", children: [granted, "/", total] }))] }));
150
183
  }
151
184
  // ---------------------------------------------------------------------------
152
185
  // Component
153
186
  // ---------------------------------------------------------------------------
154
187
  export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions, syncRolePermissions, createRole, updateRole, deleteRole, title = 'Permisos y Roles', className, }) {
155
- const [catalog, setCatalog] = React.useState(null);
188
+ const [groups, setGroups] = React.useState(null);
189
+ const [general, setGeneral] = React.useState(null);
156
190
  const [roles, setRoles] = React.useState(null);
157
191
  const [loadError, setLoadError] = React.useState(false);
158
192
  const [activeRoleId, setActiveRoleId] = React.useState(null);
@@ -164,15 +198,14 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
164
198
  const [saving, setSaving] = React.useState(false);
165
199
  const [roleOpen, setRoleOpen] = React.useState(false);
166
200
  const [moduleQuery, setModuleQuery] = React.useState('');
167
- // Groups the user explicitly collapsed (default: every group open).
168
- const [collapsedGroups, setCollapsedGroups] = React.useState(new Set());
169
201
  // Pending role switch while there are unsaved changes.
170
202
  const [pendingRoleId, setPendingRoleId] = React.useState(null);
171
203
  const [roleDialog, setRoleDialog] = React.useState({ open: false, mode: 'create', label: '', color: ROLE_COLORS[5] });
172
204
  const [roleSaving, setRoleSaving] = React.useState(false);
173
205
  const [deleteOpen, setDeleteOpen] = React.useState(false);
174
206
  const [deleting, setDeleting] = React.useState(false);
175
- const loading = catalog === null || roles === null;
207
+ const loading = groups === null || roles === null;
208
+ const allModules = React.useMemo(() => (groups ? flattenGroups(groups) : []), [groups]);
176
209
  // ---- initial load: catalog + roles in parallel -------------------------
177
210
  React.useEffect(() => {
178
211
  let cancelled = false;
@@ -180,10 +213,12 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
180
213
  .then(([cat, rs]) => {
181
214
  if (cancelled)
182
215
  return;
183
- setCatalog(cat);
216
+ const grouped = normalizeCatalogGroups(cat);
217
+ setGroups(grouped);
218
+ setGeneral(cat.general ?? []);
184
219
  setRoles(rs);
185
220
  setActiveRoleId((prev) => prev ?? rs[0]?.id ?? null);
186
- setActiveModuleKey((prev) => prev ?? cat.modules[0]?.key ?? null);
221
+ setActiveModuleKey((prev) => prev ?? flattenGroups(grouped)[0]?.key ?? null);
187
222
  })
188
223
  .catch(() => {
189
224
  if (!cancelled)
@@ -227,12 +262,10 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
227
262
  // eslint-disable-next-line react-hooks/exhaustive-deps
228
263
  }, [activeRoleId]);
229
264
  const activeRole = React.useMemo(() => roles?.find((r) => r.id === activeRoleId) ?? null, [roles, activeRoleId]);
230
- const activeModule = React.useMemo(() => catalog?.modules.find((m) => m.key === activeModuleKey) ?? null, [catalog, activeModuleKey]);
265
+ const activeModule = React.useMemo(() => allModules.find((m) => m.key === activeModuleKey) ?? null, [allModules, activeModuleKey]);
231
266
  const dirty = baseline !== null && draft !== null && !capabilitySetsEqual(baseline, draft);
232
- // Module tree: grouped by addon label, optionally filtered by the search.
233
- const allGroups = React.useMemo(() => groupModules(catalog?.modules ?? []), [catalog]);
234
- const visibleGroups = React.useMemo(() => filterModuleGroups(allGroups, moduleQuery), [allGroups, moduleQuery]);
235
- const searching = moduleQuery.trim().length > 0;
267
+ // Flat module list, optionally filtered by the search.
268
+ const visibleGroups = React.useMemo(() => filterModuleGroups(groups ?? [], moduleQuery), [groups, moduleQuery]);
236
269
  // ---- capability edits ---------------------------------------------------
237
270
  const toggleCapability = React.useCallback((cap) => {
238
271
  setDraft((prev) => {
@@ -288,14 +321,6 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
288
321
  else
289
322
  setActiveRoleId(roleId);
290
323
  };
291
- const toggleGroup = (label) => setCollapsedGroups((prev) => {
292
- const next = new Set(prev);
293
- if (next.has(label))
294
- next.delete(label);
295
- else
296
- next.add(label);
297
- return next;
298
- });
299
324
  // ---- role CRUD -----------------------------------------------------------
300
325
  const refreshRoles = async (selectId) => {
301
326
  const rs = await loadRoles();
@@ -378,6 +403,12 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
378
403
  const moduleGranted = activeModule && draft ? grantedCountForModule(draft, activeModule) : 0;
379
404
  const moduleTotal = activeModule?.actions.length ?? 0;
380
405
  const checksDisabled = !activeRole || !draft || loadingPerms || saving;
406
+ const activeModuleGroupTitle = React.useMemo(() => {
407
+ if (!activeModule || !groups)
408
+ return '';
409
+ const g = groups.find((grp) => grp.modules.some((m) => m.key === activeModule.key));
410
+ return g?.title ?? '';
411
+ }, [activeModule, groups]);
381
412
  // ---- render --------------------------------------------------------------
382
413
  if (loadError) {
383
414
  return (_jsxs("div", { className: cn('flex flex-col items-center justify-center gap-2 py-16 text-muted-foreground', className), children: [_jsx(Shield, { className: "h-8 w-8 opacity-40" }), _jsx("p", { className: "text-sm", children: "No se pudo cargar el cat\u00E1logo de permisos." })] }));
@@ -399,15 +430,12 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
399
430
  setRoleOpen(false);
400
431
  }, children: [_jsx("span", { className: "mr-2 h-2 w-2 shrink-0 rounded-full", style: {
401
432
  background: role.color || '#6b7280',
402
- }, "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: role.label || role.name }), role.id === activeRoleId && (_jsx(Check, { className: "ml-auto h-4 w-4" }))] }, role.id))) })] })] }) })] }), updateRole && (_jsx(Button, { variant: "outline", size: "icon", className: "h-9 w-9 shrink-0", "aria-label": "Editar rol", disabled: !activeRole, onClick: openEditRole, children: _jsx(Pencil, { className: "h-4 w-4" }) })), deleteRole && (_jsx(Button, { variant: "outline", size: "icon", className: "h-9 w-9 shrink-0 text-destructive hover:text-destructive", "aria-label": "Eliminar rol", disabled: !activeRole, onClick: () => setDeleteOpen(true), children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }), (catalog?.general.length ?? 0) > 0 && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx("h3", { className: "mb-2 text-sm font-semibold", children: "Permisos Generales" }), _jsx("div", { className: "flex flex-col gap-2", children: catalog.general.map((g) => (_jsx(CapabilityCheck, { checked: draft?.has(g.key) ?? false, disabled: checksDisabled, onToggle: () => toggleCapability(g.key), label: g.label, description: g.description }, g.key))) })] })] }))] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "M\u00F3dulos" }), _jsx(CardDescription, { children: "Elige el m\u00F3dulo cuyas acciones quieres configurar." })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: moduleQuery, onChange: (e) => setModuleQuery(e.target.value), placeholder: "Buscar m\u00F3dulo\u2026", "aria-label": "Buscar m\u00F3dulo", className: "pl-8" })] }), _jsx("div", { role: "tree", "aria-label": "M\u00F3dulos", className: "-mx-1 max-h-[460px] overflow-y-auto px-1", children: visibleGroups.length === 0 ? (_jsx("p", { className: "px-2 py-6 text-center text-sm text-muted-foreground", children: "Sin m\u00F3dulos." })) : (visibleGroups.map((group) => {
403
- // While searching, force every matching group open.
404
- const open = searching || !collapsedGroups.has(group.label);
405
- return (_jsxs(Collapsible, { open: open, onOpenChange: () => !searching && toggleGroup(group.label), children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-xs font-semibold uppercase tracking-wide text-muted-foreground transition-colors hover:bg-muted/40", children: [_jsx(ChevronRight, { className: cn('h-3.5 w-3.5 shrink-0 transition-transform', open && 'rotate-90') }), _jsx(Folder, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "min-w-0 flex-1 truncate normal-case", children: group.label }), _jsx("span", { className: "shrink-0 text-[10px] tabular-nums opacity-70", children: group.modules.length })] }) }), _jsx(CollapsibleContent, { children: _jsx("div", { className: "ml-3 flex flex-col gap-0.5 border-l border-border/60 pl-1.5", children: group.modules.map((mod) => (_jsx(ModuleTreeItem, { module: mod, active: mod.key === activeModuleKey, granted: draft
406
- ? grantedCountForModule(draft, mod)
407
- : 0, total: mod.actions.length, onSelect: () => setActiveModuleKey(mod.key) }, mod.key))) }) })] }, group.label));
408
- })) })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [activeModule && (_jsx(DynamicIcon, { name: activeModule.icon || 'Square', className: "h-4 w-4 shrink-0 text-primary" })), _jsx("span", { className: "truncate", children: activeModule ? activeModule.label : 'Acciones permitidas' })] }), _jsx(CardDescription, { children: activeModule
409
- ? `${moduleGroupLabel(activeModule)} · configura las acciones permitidas`
410
- : 'Configura los permisos del módulo seleccionado.' })] }), activeRole && activeModule && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Badge, { variant: "secondary", className: "tabular-nums", children: [moduleGranted, "/", moduleTotal] }), _jsxs(Button, { variant: "outline", size: "sm", className: "h-8", disabled: checksDisabled || moduleGranted === moduleTotal, onClick: () => setModuleAll(true), children: [_jsx(CheckCheck, { className: "mr-1.5 h-3.5 w-3.5" }), " Marcar todo"] }), _jsxs(Button, { variant: "outline", size: "sm", className: "h-8", disabled: checksDisabled || moduleGranted === 0, onClick: () => setModuleAll(false), children: [_jsx(Eraser, { className: "mr-1.5 h-3.5 w-3.5" }), " Limpiar"] })] }))] }) }), _jsx(CardContent, { children: !activeRole ? (_jsx(EmptyHint, { text: "Selecciona un rol para configurar sus permisos." })) : loadingPerms ? (_jsx("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsx(Skeleton, { className: "h-11 w-full" }, i))) })) : !activeModule ? (_jsx(EmptyHint, { text: "Selecciona un m\u00F3dulo del \u00E1rbol para ver sus acciones." })) : (_jsx("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-3", children: activeModule.actions.map((action) => {
433
+ }, "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: role.label || role.name }), role.id === activeRoleId && (_jsx(Check, { className: "ml-auto h-4 w-4" }))] }, role.id))) })] })] }) })] }), updateRole && (_jsx(Button, { variant: "outline", size: "icon", className: "h-9 w-9 shrink-0", "aria-label": "Editar rol", disabled: !activeRole, onClick: openEditRole, children: _jsx(Pencil, { className: "h-4 w-4" }) })), deleteRole && (_jsx(Button, { variant: "outline", size: "icon", className: "h-9 w-9 shrink-0 text-destructive hover:text-destructive", "aria-label": "Eliminar rol", disabled: !activeRole, onClick: () => setDeleteOpen(true), children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }), (general?.length ?? 0) > 0 && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx("h3", { className: "mb-2 text-sm font-semibold", children: "Permisos Generales" }), _jsx("div", { className: "flex flex-col gap-2", children: general.map((g) => (_jsx(CapabilityCheck, { checked: draft?.has(g.key) ?? false, disabled: checksDisabled, onToggle: () => toggleCapability(g.key), label: g.label, description: g.description }, g.key))) })] })] }))] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "M\u00F3dulos" }), _jsx(CardDescription, { children: "Elige el m\u00F3dulo cuyas acciones quieres configurar." })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: moduleQuery, onChange: (e) => setModuleQuery(e.target.value), placeholder: "Buscar m\u00F3dulo\u2026", "aria-label": "Buscar m\u00F3dulo", className: "pl-8" })] }), _jsx("div", { role: "list", "aria-label": "M\u00F3dulos", className: "-mx-1 flex max-h-[460px] flex-col gap-0.5 overflow-y-auto px-1", children: visibleGroups.length === 0 ? (_jsx("p", { className: "px-2 py-6 text-center text-sm text-muted-foreground", children: "Sin m\u00F3dulos." })) : (visibleGroups.map((group, gi) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [group.title && (_jsx("div", { role: "heading", "aria-level": 3, className: cn('px-2 pb-1 pt-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground', gi === 0 && 'pt-1'), children: group.title })), group.modules.map((mod) => (_jsx(ModuleRow, { module: mod, active: mod.key === activeModuleKey, granted: draft
434
+ ? grantedCountForModule(draft, mod)
435
+ : 0, total: mod.actions.length, onSelect: () => setActiveModuleKey(mod.key) }, mod.key)))] }, group.title || `__untitled_${gi}`)))) })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [activeModule && (_jsx(DynamicIcon, { name: activeModule.icon ||
436
+ (activeModule.kind === 'screen' ? 'Eye' : 'Square'), className: "h-4 w-4 shrink-0 text-primary" })), _jsx("span", { className: "truncate", children: activeModule ? activeModule.label : 'Acciones permitidas' })] }), _jsx(CardDescription, { children: activeModule
437
+ ? `${activeModuleGroupTitle || 'Sistema'} · configura las acciones permitidas`
438
+ : 'Configura los permisos del módulo seleccionado.' })] }), activeRole && activeModule && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Badge, { variant: "secondary", className: "tabular-nums", children: [moduleGranted, "/", moduleTotal] }), _jsxs(Button, { variant: "outline", size: "sm", className: "h-8", disabled: checksDisabled || moduleGranted === moduleTotal, onClick: () => setModuleAll(true), children: [_jsx(CheckCheck, { className: "mr-1.5 h-3.5 w-3.5" }), " Marcar todo"] }), _jsxs(Button, { variant: "outline", size: "sm", className: "h-8", disabled: checksDisabled || moduleGranted === 0, onClick: () => setModuleAll(false), children: [_jsx(Eraser, { className: "mr-1.5 h-3.5 w-3.5" }), " Limpiar"] })] }))] }) }), _jsx(CardContent, { children: !activeRole ? (_jsx(EmptyHint, { text: "Selecciona un rol para configurar sus permisos." })) : loadingPerms ? (_jsx("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsx(Skeleton, { className: "h-11 w-full" }, i))) })) : !activeModule ? (_jsx(EmptyHint, { text: "Selecciona un m\u00F3dulo de la lista para ver sus acciones." })) : (_jsx("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-3", children: activeModule.actions.map((action) => {
411
439
  const cap = moduleActionCapability(activeModule.key, action.key);
412
440
  return (_jsx(CapabilityCheck, { checked: draft?.has(cap) ?? false, disabled: checksDisabled, onToggle: () => toggleCapability(cap), icon: action.icon || defaultActionIcon(action.key, action.kind), label: action.label }, action.key));
413
441
  }) })) })] })] }), _jsx(AlertDialog, { open: pendingRoleId !== null, onOpenChange: (open) => !open && setPendingRoleId(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Cambios sin guardar" }), _jsx(AlertDialogDescription, { children: "Tienes cambios sin guardar en este rol. Si cambias de rol se descartar\u00E1n." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Cancelar" }), _jsx(AlertDialogAction, { onClick: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "18.15.0",
3
+ "version": "18.16.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",