@asteby/metacore-runtime-react 18.14.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,29 @@
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
+
14
+ ## 18.15.0
15
+
16
+ ### Minor Changes
17
+
18
+ - cbcedd9: PermissionsManager: rediseño de UX para que la elección de módulo refleje el sidebar.
19
+ - 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".
20
+ - 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.
21
+ - 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.
22
+ - Nuevo campo opcional `icon` en `PermissionModuleDef` (lucide) para mostrar el ícono del módulo en el árbol y el panel.
23
+ - Helpers exportados nuevos: `groupModules`, `filterModuleGroups` (+ tipo `ModuleGroup`).
24
+
25
+ Sin cambios en la firma de props de `PermissionsManager` (solo render interno; `PermissionModuleDef.icon` es aditivo/opcional).
26
+
3
27
  ## 18.14.0
4
28
 
5
29
  ### 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":"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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
@@ -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,35 +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
- /** Owning addon key (`pos`). */
24
+ /** Module icon (lucide name) — mirrors the sidebar entry. */
25
+ icon?: string;
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. */
18
29
  addon_key?: string;
19
- /** Localized addon label ("Punto de venta") — used to group the selector. */
30
+ /** Localized addon label ("Punto de venta") — legacy shape only. */
20
31
  addon_label?: string;
21
32
  actions: PermissionActionDef[];
22
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
+ }
23
42
  export interface GeneralPermissionDef {
24
43
  /** Full capability key (`general.work_after_hours`). */
25
44
  key: string;
26
45
  label: string;
27
46
  description?: string;
28
47
  }
29
- 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 {
30
62
  modules: PermissionModuleDef[];
31
63
  general: GeneralPermissionDef[];
32
64
  }
65
+ export type PermissionsCatalog = GroupedPermissionsCatalog | FlatPermissionsCatalog;
33
66
  export interface RoleDef {
34
67
  id: string;
35
68
  /** Stable role key ("cashier"). */
@@ -45,7 +78,7 @@ export interface RoleInput {
45
78
  color?: string;
46
79
  }
47
80
  export interface PermissionsManagerProps {
48
- /** Loads the module×action universe + general flags. */
81
+ /** Loads the module×action universe + general flags (grouped or flat). */
49
82
  loadModules: () => Promise<PermissionsCatalog>;
50
83
  /** Loads every assignable role. */
51
84
  loadRoles: () => Promise<RoleDef[]>;
@@ -53,7 +86,7 @@ export interface PermissionsManagerProps {
53
86
  loadRolePermissions: (roleId: string) => Promise<string[]>;
54
87
  /** Persists the FULL granted capability set of a role. */
55
88
  syncRolePermissions: (roleId: string, capabilities: string[]) => Promise<void>;
56
- /** Optional role CRUD — omitting one hides its button. */
89
+ /** Optional role CRUD — omitting one hides its control. */
57
90
  createRole?: (input: RoleInput) => Promise<RoleDef | void>;
58
91
  updateRole?: (roleId: string, input: RoleInput) => Promise<RoleDef | void>;
59
92
  deleteRole?: (roleId: string) => Promise<void>;
@@ -70,5 +103,19 @@ export declare function grantedCountForModule(granted: ReadonlySet<string>, modu
70
103
  export declare function capabilitySetsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean;
71
104
  /** Default lucide icon when the manifest action doesn't declare one. */
72
105
  export declare function defaultActionIcon(actionKey: string, kind?: string): string;
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. */
119
+ export declare function filterModuleGroups(groups: ModuleGroup[], query: string): ModuleGroup[];
73
120
  export declare function PermissionsManager({ loadModules, loadRoles, loadRolePermissions, syncRolePermissions, createRole, updateRole, deleteRole, title, className, }: PermissionsManagerProps): React.JSX.Element;
74
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,23 +4,28 @@ 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 (reference: 7leguas "Permisos y Roles"):
10
+ // Layout a *flat list* that mirrors the app sidebar (NO accordions/folders):
10
11
  // header — title + "Nuevo rol" (primary) + "Guardar permisos" (green).
11
- // left — Card "Rol": searchable role selector with removable chip,
12
- // Editar/Eliminar rol, "Permisos Generales" flag checkboxes.
13
- // — Card "Módulo": searchable module selector grouped by addon,
14
- // removable chip.
12
+ // left — Card "Rol": clean role combobox with inline Editar/Eliminar
13
+ // icons (no removable chip) + "Permisos Generales" flags.
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.
15
19
  // right — Card "Acciones permitidas": granted counter N/M, mark-all /
16
- // clear buttons, checkbox grid (icon + label per action).
20
+ // clear, checkbox grid (icon + label per action). Clear empty
21
+ // states for "pick a role" / "pick a module" / loading.
17
22
  //
18
23
  // Saving calls `syncRolePermissions(roleId, capabilities)` with the FULL
19
24
  // granted set of the active role (baseline + the edits made here). Dirty
20
25
  // state is tracked against the loaded baseline and surfaced next to the
21
26
  // save button.
22
27
  import * as React from 'react';
23
- import { Check, ChevronsUpDown, CheckCheck, Eraser, Pencil, Plus, Save, Shield, Trash2, X, } from 'lucide-react';
28
+ import { Check, ChevronsUpDown, CheckCheck, Eraser, Pencil, Plus, Save, Search, Shield, Trash2, } from 'lucide-react';
24
29
  import { toast } from 'sonner';
25
30
  import { cn } from '@asteby/metacore-ui/lib';
26
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';
@@ -63,15 +68,30 @@ export function defaultActionIcon(actionKey, kind) {
63
68
  return 'Download';
64
69
  case 'import':
65
70
  return 'Upload';
71
+ case 'access':
72
+ return 'Eye';
66
73
  default:
67
- return kind === 'crud' ? 'List' : 'Zap';
74
+ if (kind === 'crud')
75
+ return 'List';
76
+ if (kind === 'screen')
77
+ return 'Eye';
78
+ return 'Zap';
68
79
  }
69
80
  }
70
- function slugify(label) {
71
- return label
81
+ /** Group label fallback when a legacy module has no addon ("Sistema" = core). */
82
+ const SYSTEM_GROUP = 'Sistema';
83
+ function legacyGroupLabel(mod) {
84
+ return mod.addon_label || mod.addon_key || SYSTEM_GROUP;
85
+ }
86
+ /** Accent-insensitive, lowercase fold for search. */
87
+ function fold(s) {
88
+ return s
72
89
  .normalize('NFD')
73
- .replace(/[\u0300-\u036f]/g, '')
74
- .toLowerCase()
90
+ .replace(/[̀-ͯ]/g, '')
91
+ .toLowerCase();
92
+ }
93
+ function slugify(label) {
94
+ return fold(label)
75
95
  .trim()
76
96
  .replace(/[^a-z0-9]+/g, '_')
77
97
  .replace(/^_+|_+$/g, '');
@@ -87,6 +107,60 @@ const ROLE_COLORS = [
87
107
  '#ec4899',
88
108
  '#6b7280',
89
109
  ];
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) || [];
131
+ const order = [];
132
+ const byGroup = new Map();
133
+ for (const raw of modules) {
134
+ const mod = withKind(raw);
135
+ const g = legacyGroupLabel(mod);
136
+ if (!byGroup.has(g)) {
137
+ byGroup.set(g, []);
138
+ order.push(g);
139
+ }
140
+ byGroup.get(g).push(mod);
141
+ }
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);
147
+ }
148
+ /** Filter the grouped flat list by a folded query against module + group titles. */
149
+ export function filterModuleGroups(groups, query) {
150
+ const q = fold(query).trim();
151
+ if (!q)
152
+ return groups;
153
+ const out = [];
154
+ for (const g of groups) {
155
+ const groupMatches = g.title.length > 0 && fold(g.title).includes(q);
156
+ const mods = groupMatches
157
+ ? g.modules
158
+ : g.modules.filter((m) => fold(m.label).includes(q) || fold(m.key).includes(q));
159
+ if (mods.length)
160
+ out.push({ title: g.title, modules: mods });
161
+ }
162
+ return out;
163
+ }
90
164
  // ---------------------------------------------------------------------------
91
165
  // Internal sub-components
92
166
  // ---------------------------------------------------------------------------
@@ -101,15 +175,18 @@ function CapabilityCheck({ checked, disabled, onToggle, icon, label, description
101
175
  }
102
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 }))] })] }));
103
177
  }
104
- /** Removable selection chip (role / module). */
105
- function SelectionChip({ label, color, onRemove, removeAriaLabel, }) {
106
- return (_jsxs(Badge, { variant: "secondary", className: "gap-1.5 pr-1 text-sm font-medium", children: [color && (_jsx("span", { className: "h-2 w-2 rounded-full", style: { background: color }, "aria-hidden": "true" })), _jsx("span", { className: "max-w-[180px] truncate", children: label }), _jsx("button", { type: "button", "aria-label": removeAriaLabel, onClick: onRemove, className: "rounded-sm p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground", children: _jsx(X, { className: "h-3 w-3" }) })] }));
178
+ /** One clickable module row in the flat list (mirrors a sidebar item). */
179
+ function ModuleRow({ module, active, granted, total, onSelect, }) {
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
181
+ ? 'bg-primary/10 font-medium text-foreground'
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] }))] }));
107
183
  }
108
184
  // ---------------------------------------------------------------------------
109
185
  // Component
110
186
  // ---------------------------------------------------------------------------
111
187
  export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions, syncRolePermissions, createRole, updateRole, deleteRole, title = 'Permisos y Roles', className, }) {
112
- const [catalog, setCatalog] = React.useState(null);
188
+ const [groups, setGroups] = React.useState(null);
189
+ const [general, setGeneral] = React.useState(null);
113
190
  const [roles, setRoles] = React.useState(null);
114
191
  const [loadError, setLoadError] = React.useState(false);
115
192
  const [activeRoleId, setActiveRoleId] = React.useState(null);
@@ -120,14 +197,15 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
120
197
  const [loadingPerms, setLoadingPerms] = React.useState(false);
121
198
  const [saving, setSaving] = React.useState(false);
122
199
  const [roleOpen, setRoleOpen] = React.useState(false);
123
- const [moduleOpen, setModuleOpen] = React.useState(false);
200
+ const [moduleQuery, setModuleQuery] = React.useState('');
124
201
  // Pending role switch while there are unsaved changes.
125
202
  const [pendingRoleId, setPendingRoleId] = React.useState(null);
126
203
  const [roleDialog, setRoleDialog] = React.useState({ open: false, mode: 'create', label: '', color: ROLE_COLORS[5] });
127
204
  const [roleSaving, setRoleSaving] = React.useState(false);
128
205
  const [deleteOpen, setDeleteOpen] = React.useState(false);
129
206
  const [deleting, setDeleting] = React.useState(false);
130
- const loading = catalog === null || roles === null;
207
+ const loading = groups === null || roles === null;
208
+ const allModules = React.useMemo(() => (groups ? flattenGroups(groups) : []), [groups]);
131
209
  // ---- initial load: catalog + roles in parallel -------------------------
132
210
  React.useEffect(() => {
133
211
  let cancelled = false;
@@ -135,10 +213,12 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
135
213
  .then(([cat, rs]) => {
136
214
  if (cancelled)
137
215
  return;
138
- setCatalog(cat);
216
+ const grouped = normalizeCatalogGroups(cat);
217
+ setGroups(grouped);
218
+ setGeneral(cat.general ?? []);
139
219
  setRoles(rs);
140
220
  setActiveRoleId((prev) => prev ?? rs[0]?.id ?? null);
141
- setActiveModuleKey((prev) => prev ?? cat.modules[0]?.key ?? null);
221
+ setActiveModuleKey((prev) => prev ?? flattenGroups(grouped)[0]?.key ?? null);
142
222
  })
143
223
  .catch(() => {
144
224
  if (!cancelled)
@@ -182,19 +262,10 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
182
262
  // eslint-disable-next-line react-hooks/exhaustive-deps
183
263
  }, [activeRoleId]);
184
264
  const activeRole = React.useMemo(() => roles?.find((r) => r.id === activeRoleId) ?? null, [roles, activeRoleId]);
185
- 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]);
186
266
  const dirty = baseline !== null && draft !== null && !capabilitySetsEqual(baseline, draft);
187
- // Selector groups: modules bucketed by addon label, stable order.
188
- const moduleGroups = React.useMemo(() => {
189
- const groups = new Map();
190
- for (const mod of catalog?.modules ?? []) {
191
- const group = mod.addon_label || mod.addon_key || 'Otros';
192
- const list = groups.get(group) ?? [];
193
- list.push(mod);
194
- groups.set(group, list);
195
- }
196
- return Array.from(groups.entries());
197
- }, [catalog]);
267
+ // Flat module list, optionally filtered by the search.
268
+ const visibleGroups = React.useMemo(() => filterModuleGroups(groups ?? [], moduleQuery), [groups, moduleQuery]);
198
269
  // ---- capability edits ---------------------------------------------------
199
270
  const toggleCapability = React.useCallback((cap) => {
200
271
  setDraft((prev) => {
@@ -318,29 +389,53 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
318
389
  setDeleting(false);
319
390
  }
320
391
  };
392
+ const openEditRole = () => {
393
+ if (!activeRole)
394
+ return;
395
+ setRoleDialog({
396
+ open: true,
397
+ mode: 'edit',
398
+ label: activeRole.label || activeRole.name,
399
+ color: activeRole.color || ROLE_COLORS[5],
400
+ });
401
+ };
321
402
  // ---- derived for the right panel ----------------------------------------
322
403
  const moduleGranted = activeModule && draft ? grantedCountForModule(draft, activeModule) : 0;
323
404
  const moduleTotal = activeModule?.actions.length ?? 0;
324
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]);
325
412
  // ---- render --------------------------------------------------------------
326
413
  if (loadError) {
327
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." })] }));
328
415
  }
329
416
  if (loading) {
330
- return (_jsxs("div", { className: cn('flex flex-col gap-4', className), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Skeleton, { className: "h-8 w-56" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Skeleton, { className: "h-9 w-28" }), _jsx(Skeleton, { className: "h-9 w-40" })] })] }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-[340px_1fr]", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(Skeleton, { className: "h-64 w-full" }), _jsx(Skeleton, { className: "h-28 w-full" })] }), _jsx(Skeleton, { className: "h-96 w-full" })] })] }));
417
+ return (_jsxs("div", { className: cn('flex flex-col gap-4', className), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Skeleton, { className: "h-8 w-56" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Skeleton, { className: "h-9 w-28" }), _jsx(Skeleton, { className: "h-9 w-40" })] })] }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-[340px_1fr]", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(Skeleton, { className: "h-40 w-full" }), _jsx(Skeleton, { className: "h-80 w-full" })] }), _jsx(Skeleton, { className: "h-96 w-full" })] })] }));
331
418
  }
332
- return (_jsxs("div", { className: cn('flex flex-col gap-4', className), children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Define qu\u00E9 puede hacer cada rol en cada m\u00F3dulo." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [dirty && (_jsx(Badge, { variant: "outline", className: "border-amber-500/50 text-amber-600", children: "Cambios sin guardar" })), createRole && (_jsxs(Button, { onClick: () => setRoleDialog({ open: true, mode: 'create', label: '', color: ROLE_COLORS[5] }), children: [_jsx(Plus, { className: "mr-1.5 h-4 w-4" }), " Nuevo rol"] })), _jsxs(Button, { onClick: handleSave, disabled: !dirty || saving || !activeRole, className: "bg-emerald-600 text-white hover:bg-emerald-700", children: [_jsx(Save, { className: "mr-1.5 h-4 w-4" }), saving ? 'Guardando…' : 'Guardar permisos'] })] })] }), _jsxs("div", { className: "grid items-start gap-4 lg:grid-cols-[340px_1fr]", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Rol" }), _jsx(CardDescription, { children: "Selecciona el rol a configurar." })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [activeRole ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(SelectionChip, { label: activeRole.label || activeRole.name, color: activeRole.color, onRemove: () => requestRoleSwitch(null), removeAriaLabel: "Quitar rol seleccionado" }), _jsxs("div", { className: "flex items-center gap-1", children: [updateRole && (_jsx(Button, { variant: "ghost", size: "sm", className: "h-8 px-2", "aria-label": "Editar rol", onClick: () => setRoleDialog({
333
- open: true,
334
- mode: 'edit',
335
- label: activeRole.label || activeRole.name,
336
- color: activeRole.color || ROLE_COLORS[5],
337
- }), children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) })), deleteRole && (_jsx(Button, { variant: "ghost", size: "sm", className: "h-8 px-2 text-destructive hover:text-destructive", "aria-label": "Eliminar rol", onClick: () => setDeleteOpen(true), children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] })] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Ning\u00FAn rol seleccionado." })), _jsxs(Popover, { open: roleOpen, onOpenChange: setRoleOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", role: "combobox", "aria-expanded": roleOpen, className: "w-full justify-between font-normal", children: [activeRole ? activeRole.label || activeRole.name : 'Seleccionar rol…', _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "w-[300px] p-0", align: "start", children: _jsxs(Command, { children: [_jsx(CommandInput, { placeholder: "Buscar rol\u2026" }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: "Sin resultados." }), _jsx(CommandGroup, { children: (roles ?? []).map((role) => (_jsxs(CommandItem, { value: `${role.label || ''} ${role.name}`, onSelect: () => {
338
- requestRoleSwitch(role.id);
339
- setRoleOpen(false);
340
- }, children: [_jsx("span", { className: "mr-2 h-2 w-2 shrink-0 rounded-full", style: { background: role.color || '#6b7280' }, "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))) })] })] }) })] }), (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\u00F3dulo" }), _jsx(CardDescription, { children: "Elige el m\u00F3dulo cuyas acciones quieres configurar." })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [activeModule ? (_jsx(SelectionChip, { label: activeModule.label, onRemove: () => setActiveModuleKey(null), removeAriaLabel: "Quitar m\u00F3dulo seleccionado" })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Ning\u00FAn m\u00F3dulo seleccionado." })), _jsxs(Popover, { open: moduleOpen, onOpenChange: setModuleOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", role: "combobox", "aria-expanded": moduleOpen, className: "w-full justify-between font-normal", children: [activeModule ? activeModule.label : 'Seleccionar módulo…', _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "w-[300px] p-0", align: "start", children: _jsxs(Command, { children: [_jsx(CommandInput, { placeholder: "Buscar m\u00F3dulo\u2026" }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: "Sin resultados." }), moduleGroups.map(([group, mods]) => (_jsx(CommandGroup, { heading: group, children: mods.map((mod) => (_jsxs(CommandItem, { value: `${mod.label} ${mod.key} ${group}`, onSelect: () => {
341
- setActiveModuleKey(mod.key);
342
- setModuleOpen(false);
343
- }, children: [_jsx("span", { className: "truncate", children: mod.label }), mod.key === activeModuleKey && (_jsx(Check, { className: "ml-auto h-4 w-4" }))] }, mod.key))) }, group)))] })] }) })] })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsx(CardTitle, { className: "text-base", children: "Acciones permitidas" }), _jsx(CardDescription, { children: "Configura los permisos para este m\u00F3dulo." })] }), 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." })) : !activeModule ? (_jsx(EmptyHint, { text: "Selecciona un m\u00F3dulo para ver sus acciones." })) : 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))) })) : (_jsx("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-3", children: activeModule.actions.map((action) => {
419
+ return (_jsxs("div", { className: cn('flex flex-col gap-4', className), children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Define qu\u00E9 puede hacer cada rol en cada m\u00F3dulo." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [dirty && (_jsx(Badge, { variant: "outline", className: "border-amber-500/50 text-amber-600", children: "Cambios sin guardar" })), createRole && (_jsxs(Button, { onClick: () => setRoleDialog({
420
+ open: true,
421
+ mode: 'create',
422
+ label: '',
423
+ color: ROLE_COLORS[5],
424
+ }), children: [_jsx(Plus, { className: "mr-1.5 h-4 w-4" }), " Nuevo rol"] })), _jsxs(Button, { onClick: handleSave, disabled: !dirty || saving || !activeRole, className: "bg-emerald-600 text-white hover:bg-emerald-700", children: [_jsx(Save, { className: "mr-1.5 h-4 w-4" }), saving ? 'Guardando…' : 'Guardar permisos'] })] })] }), _jsxs("div", { className: "grid items-start gap-4 lg:grid-cols-[340px_1fr]", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Rol" }), _jsx(CardDescription, { children: "Selecciona el rol a configurar." })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs(Popover, { open: roleOpen, onOpenChange: setRoleOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", role: "combobox", "aria-expanded": roleOpen, className: "min-w-0 flex-1 justify-between font-normal", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [activeRole && (_jsx("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: {
425
+ background: activeRole.color || '#6b7280',
426
+ }, "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: activeRole
427
+ ? activeRole.label || activeRole.name
428
+ : 'Seleccionar rol…' })] }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "w-[280px] p-0", align: "start", children: _jsxs(Command, { children: [_jsx(CommandInput, { placeholder: "Buscar rol\u2026" }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: "Sin resultados." }), _jsx(CommandGroup, { children: (roles ?? []).map((role) => (_jsxs(CommandItem, { value: `${role.label || ''} ${role.name}`, onSelect: () => {
429
+ requestRoleSwitch(role.id);
430
+ setRoleOpen(false);
431
+ }, children: [_jsx("span", { className: "mr-2 h-2 w-2 shrink-0 rounded-full", style: {
432
+ background: role.color || '#6b7280',
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) => {
344
439
  const cap = moduleActionCapability(activeModule.key, action.key);
345
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));
346
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: () => {
@@ -348,7 +443,11 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
348
443
  setPendingRoleId(null);
349
444
  }, children: "Descartar y cambiar" })] })] }) }), _jsx(Dialog, { open: roleDialog.open, onOpenChange: (open) => setRoleDialog((d) => ({ ...d, open })), children: _jsxs(DialogContent, { className: "sm:max-w-md", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: roleDialog.mode === 'create' ? 'Nuevo rol' : 'Editar rol' }) }), _jsxs("div", { className: "flex flex-col gap-4 py-2", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "pm-role-name", children: "Nombre del rol" }), _jsx(Input, { id: "pm-role-name", value: roleDialog.label, placeholder: "Ej. Cajero", onChange: (e) => setRoleDialog((d) => ({ ...d, label: e.target.value })) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Color" }), _jsx("div", { className: "flex flex-wrap gap-2", children: ROLE_COLORS.map((c) => (_jsx("button", { type: "button", "aria-label": `Color ${c}`, onClick: () => setRoleDialog((d) => ({ ...d, color: c })), className: cn('h-7 w-7 rounded-full border-2 transition-transform', roleDialog.color === c
350
445
  ? 'scale-110 border-foreground'
351
- : 'border-transparent hover:scale-105'), style: { background: c } }, c))) })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setRoleDialog((d) => ({ ...d, open: false })), disabled: roleSaving, children: "Cancelar" }), _jsx(Button, { onClick: handleRoleSubmit, disabled: roleSaving || !roleDialog.label.trim(), children: roleSaving ? 'Guardando…' : roleDialog.mode === 'create' ? 'Crear rol' : 'Guardar' })] })] }) }), _jsx(AlertDialog, { open: deleteOpen, onOpenChange: (open) => !deleting && setDeleteOpen(open), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "\u00BFEliminar el rol?" }), _jsxs(AlertDialogDescription, { children: ["Se eliminar\u00E1 el rol", ' ', _jsx("strong", { children: activeRole ? activeRole.label || activeRole.name : '' }), " y sus asignaciones de permisos. Esta acci\u00F3n no se puede deshacer."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: deleting, children: "Cancelar" }), _jsx(AlertDialogAction, { className: "bg-red-600 hover:bg-red-700", disabled: deleting, onClick: (e) => {
446
+ : 'border-transparent hover:scale-105'), style: { background: c } }, c))) })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setRoleDialog((d) => ({ ...d, open: false })), disabled: roleSaving, children: "Cancelar" }), _jsx(Button, { onClick: handleRoleSubmit, disabled: roleSaving || !roleDialog.label.trim(), children: roleSaving
447
+ ? 'Guardando…'
448
+ : roleDialog.mode === 'create'
449
+ ? 'Crear rol'
450
+ : 'Guardar' })] })] }) }), _jsx(AlertDialog, { open: deleteOpen, onOpenChange: (open) => !deleting && setDeleteOpen(open), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "\u00BFEliminar el rol?" }), _jsxs(AlertDialogDescription, { children: ["Se eliminar\u00E1 el rol", ' ', _jsx("strong", { children: activeRole ? activeRole.label || activeRole.name : '' }), " y sus asignaciones de permisos. Esta acci\u00F3n no se puede deshacer."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: deleting, children: "Cancelar" }), _jsx(AlertDialogAction, { className: "bg-red-600 hover:bg-red-700", disabled: deleting, onClick: (e) => {
352
451
  e.preventDefault();
353
452
  handleDeleteRole();
354
453
  }, children: deleting ? 'Eliminando…' : 'Eliminar' })] })] }) })] }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "18.14.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",