@memberjunction/ng-explorer-settings 5.42.0 → 5.44.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.
Files changed (55) hide show
  1. package/dist/__tests__/role-management-agent-context.test.d.ts +2 -0
  2. package/dist/__tests__/role-management-agent-context.test.d.ts.map +1 -0
  3. package/dist/__tests__/role-management-agent-context.test.js +107 -0
  4. package/dist/__tests__/role-management-agent-context.test.js.map +1 -0
  5. package/dist/__tests__/user-management-agent-context.test.d.ts +2 -0
  6. package/dist/__tests__/user-management-agent-context.test.d.ts.map +1 -0
  7. package/dist/__tests__/user-management-agent-context.test.js +134 -0
  8. package/dist/__tests__/user-management-agent-context.test.js.map +1 -0
  9. package/dist/lib/application-management/application-dialog/application-dialog.component.js +176 -200
  10. package/dist/lib/application-management/application-dialog/application-dialog.component.js.map +1 -1
  11. package/dist/lib/application-management/application-management.component.d.ts +7 -0
  12. package/dist/lib/application-management/application-management.component.d.ts.map +1 -1
  13. package/dist/lib/application-management/application-management.component.js +49 -39
  14. package/dist/lib/application-management/application-management.component.js.map +1 -1
  15. package/dist/lib/application-settings/application-settings.component.js +58 -70
  16. package/dist/lib/application-settings/application-settings.component.js.map +1 -1
  17. package/dist/lib/entity-permissions/entity-permissions.component.d.ts +10 -0
  18. package/dist/lib/entity-permissions/entity-permissions.component.d.ts.map +1 -1
  19. package/dist/lib/entity-permissions/entity-permissions.component.js +32 -16
  20. package/dist/lib/entity-permissions/entity-permissions.component.js.map +1 -1
  21. package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js +32 -33
  22. package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js.map +1 -1
  23. package/dist/lib/module.d.ts +1 -1
  24. package/dist/lib/module.d.ts.map +1 -1
  25. package/dist/lib/module.js +13 -4
  26. package/dist/lib/module.js.map +1 -1
  27. package/dist/lib/notification-preferences/notification-preferences.component.js +30 -35
  28. package/dist/lib/notification-preferences/notification-preferences.component.js.map +1 -1
  29. package/dist/lib/role-management/role-dialog/role-dialog.component.js +26 -46
  30. package/dist/lib/role-management/role-dialog/role-dialog.component.js.map +1 -1
  31. package/dist/lib/role-management/role-management-agent-context.d.ts +96 -0
  32. package/dist/lib/role-management/role-management-agent-context.d.ts.map +1 -0
  33. package/dist/lib/role-management/role-management-agent-context.js +85 -0
  34. package/dist/lib/role-management/role-management-agent-context.js.map +1 -0
  35. package/dist/lib/role-management/role-management.component.d.ts +32 -0
  36. package/dist/lib/role-management/role-management.component.d.ts.map +1 -1
  37. package/dist/lib/role-management/role-management.component.js +236 -41
  38. package/dist/lib/role-management/role-management.component.js.map +1 -1
  39. package/dist/lib/sql-logging/sql-logging.component.js +151 -184
  40. package/dist/lib/sql-logging/sql-logging.component.js.map +1 -1
  41. package/dist/lib/user-app-config/user-app-config.component.js +80 -93
  42. package/dist/lib/user-app-config/user-app-config.component.js.map +1 -1
  43. package/dist/lib/user-management/user-dialog/user-dialog.component.js +20 -21
  44. package/dist/lib/user-management/user-dialog/user-dialog.component.js.map +1 -1
  45. package/dist/lib/user-management/user-management-agent-context.d.ts +107 -0
  46. package/dist/lib/user-management/user-management-agent-context.d.ts.map +1 -0
  47. package/dist/lib/user-management/user-management-agent-context.js +132 -0
  48. package/dist/lib/user-management/user-management-agent-context.js.map +1 -0
  49. package/dist/lib/user-management/user-management.component.d.ts +35 -0
  50. package/dist/lib/user-management/user-management.component.d.ts.map +1 -1
  51. package/dist/lib/user-management/user-management.component.js +311 -71
  52. package/dist/lib/user-management/user-management.component.js.map +1 -1
  53. package/dist/lib/user-profile-settings/user-profile-settings.component.js +52 -70
  54. package/dist/lib/user-profile-settings/user-profile-settings.component.js.map +1 -1
  55. package/package.json +19 -19
@@ -7,9 +7,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { Component } from '@angular/core';
8
8
  import { Subject, BehaviorSubject } from 'rxjs';
9
9
  import { takeUntil, debounceTime, distinctUntilChanged } from 'rxjs/operators';
10
- import { RunView } from '@memberjunction/core';
10
+ import { RunView, CompositeKey } from '@memberjunction/core';
11
11
  import { BaseDashboard } from '@memberjunction/ng-shared';
12
- import { RegisterClass } from '@memberjunction/global';
12
+ import { RegisterClass, UUIDsEqual } from '@memberjunction/global';
13
+ import { buildRoleManagementAgentContext, isValidRoleTypeFilter, resolveRoleByIDOrName, } from './role-management-agent-context';
13
14
  import * as i0 from "@angular/core";
14
15
  import * as i1 from "@memberjunction/ng-shared-generic";
15
16
  import * as i2 from "@memberjunction/ng-ui-components";
@@ -166,19 +167,18 @@ function RoleManagementComponent_Conditional_16_For_2_Template(rf, ctx) { if (rf
166
167
  i0.ɵɵconditional(ctx_r1.isRoleExpanded(role_r4.ID) ? 20 : -1);
167
168
  } }
168
169
  function RoleManagementComponent_Conditional_16_Conditional_3_Template(rf, ctx) { if (rf & 1) {
169
- i0.ɵɵelementStart(0, "div", 24);
170
- i0.ɵɵelement(1, "i", 56);
171
- i0.ɵɵelementStart(2, "p", 57);
172
- i0.ɵɵtext(3, "No roles found");
170
+ const _r6 = i0.ɵɵgetCurrentView();
171
+ i0.ɵɵelementStart(0, "mj-empty-state", 56);
172
+ i0.ɵɵlistener("Action", function RoleManagementComponent_Conditional_16_Conditional_3_Template_mj_empty_state_Action_0_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.resetAllFiltersAndSearch()); });
173
173
  i0.ɵɵelementEnd();
174
- i0.ɵɵelementStart(4, "p", 58);
175
- i0.ɵɵtext(5, "Try adjusting your filters or create a new role");
176
- i0.ɵɵelementEnd()();
174
+ } if (rf & 2) {
175
+ const ctx_r1 = i0.ɵɵnextContext(2);
176
+ i0.ɵɵproperty("Variant", ctx_r1.IsListNarrowed ? "no-results" : "empty")("ActionText", ctx_r1.IsListNarrowed ? "Reset filters" : "");
177
177
  } }
178
178
  function RoleManagementComponent_Conditional_16_Template(rf, ctx) { if (rf & 1) {
179
179
  i0.ɵɵelementStart(0, "div", 14);
180
180
  i0.ɵɵrepeaterCreate(1, RoleManagementComponent_Conditional_16_For_2_Template, 21, 16, "div", 23, _forTrack0);
181
- i0.ɵɵconditionalCreate(3, RoleManagementComponent_Conditional_16_Conditional_3_Template, 6, 0, "div", 24);
181
+ i0.ɵɵconditionalCreate(3, RoleManagementComponent_Conditional_16_Conditional_3_Template, 1, 2, "mj-empty-state", 24);
182
182
  i0.ɵɵelementEnd();
183
183
  } if (rf & 2) {
184
184
  const ctx_r1 = i0.ɵɵnextContext();
@@ -187,55 +187,53 @@ function RoleManagementComponent_Conditional_16_Template(rf, ctx) { if (rf & 1)
187
187
  i0.ɵɵadvance(2);
188
188
  i0.ɵɵconditional(ctx_r1.filteredRoles.length === 0 ? 3 : -1);
189
189
  } }
190
- function RoleManagementComponent_Conditional_18_Conditional_19_Template(rf, ctx) { if (rf & 1) {
191
- i0.ɵɵelement(0, "i", 72);
190
+ function RoleManagementComponent_Conditional_18_Conditional_17_Template(rf, ctx) { if (rf & 1) {
191
+ i0.ɵɵelement(0, "i", 69);
192
192
  i0.ɵɵtext(1, " Deleting... ");
193
193
  } }
194
- function RoleManagementComponent_Conditional_18_Conditional_20_Template(rf, ctx) { if (rf & 1) {
194
+ function RoleManagementComponent_Conditional_18_Conditional_18_Template(rf, ctx) { if (rf & 1) {
195
195
  i0.ɵɵelement(0, "i", 38);
196
196
  i0.ɵɵtext(1, " Delete Role ");
197
197
  } }
198
198
  function RoleManagementComponent_Conditional_18_Template(rf, ctx) { if (rf & 1) {
199
- const _r6 = i0.ɵɵgetCurrentView();
200
- i0.ɵɵelementStart(0, "div", 59);
201
- i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.showDeleteConfirm = false); });
202
- i0.ɵɵelementStart(1, "div", 60);
203
- i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_div_click_1_listener($event) { i0.ɵɵrestoreView(_r6); return i0.ɵɵresetView($event.stopPropagation()); });
204
- i0.ɵɵelementStart(2, "div", 61)(3, "h3", 62);
205
- i0.ɵɵelement(4, "i", 63);
199
+ const _r7 = i0.ɵɵgetCurrentView();
200
+ i0.ɵɵelementStart(0, "div", 57);
201
+ i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.showDeleteConfirm = false); });
202
+ i0.ɵɵelementStart(1, "div", 58);
203
+ i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_div_click_1_listener($event) { i0.ɵɵrestoreView(_r7); return i0.ɵɵresetView($event.stopPropagation()); });
204
+ i0.ɵɵelementStart(2, "div", 59)(3, "h3", 60);
205
+ i0.ɵɵelement(4, "i", 61);
206
206
  i0.ɵɵtext(5, " Confirm Delete ");
207
207
  i0.ɵɵelementEnd();
208
- i0.ɵɵelementStart(6, "button", 64);
209
- i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.showDeleteConfirm = false); });
210
- i0.ɵɵelement(7, "i", 65);
208
+ i0.ɵɵelementStart(6, "button", 62);
209
+ i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.showDeleteConfirm = false); });
210
+ i0.ɵɵelement(7, "i", 63);
211
211
  i0.ɵɵelementEnd()();
212
- i0.ɵɵelementStart(8, "div", 66)(9, "p");
212
+ i0.ɵɵelementStart(8, "div", 64)(9, "p");
213
213
  i0.ɵɵtext(10, "Are you sure you want to delete the role ");
214
214
  i0.ɵɵelementStart(11, "strong");
215
215
  i0.ɵɵtext(12);
216
216
  i0.ɵɵelementEnd();
217
217
  i0.ɵɵtext(13, "?");
218
218
  i0.ɵɵelementEnd();
219
- i0.ɵɵelementStart(14, "p", 67);
220
- i0.ɵɵelement(15, "i", 68);
221
- i0.ɵɵtext(16, " This will affect all users assigned to this role. ");
222
- i0.ɵɵelementEnd()();
223
- i0.ɵɵelementStart(17, "div", 69)(18, "button", 70);
224
- i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_button_click_18_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.deleteRole()); });
225
- i0.ɵɵconditionalCreate(19, RoleManagementComponent_Conditional_18_Conditional_19_Template, 2, 0)(20, RoleManagementComponent_Conditional_18_Conditional_20_Template, 2, 0);
219
+ i0.ɵɵelement(14, "mj-alert", 65);
226
220
  i0.ɵɵelementEnd();
227
- i0.ɵɵelementStart(21, "button", 71);
228
- i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_button_click_21_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.showDeleteConfirm = false); });
229
- i0.ɵɵtext(22, " Cancel ");
221
+ i0.ɵɵelementStart(15, "div", 66)(16, "button", 67);
222
+ i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_button_click_16_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.deleteRole()); });
223
+ i0.ɵɵconditionalCreate(17, RoleManagementComponent_Conditional_18_Conditional_17_Template, 2, 0)(18, RoleManagementComponent_Conditional_18_Conditional_18_Template, 2, 0);
224
+ i0.ɵɵelementEnd();
225
+ i0.ɵɵelementStart(19, "button", 68);
226
+ i0.ɵɵlistener("click", function RoleManagementComponent_Conditional_18_Template_button_click_19_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.showDeleteConfirm = false); });
227
+ i0.ɵɵtext(20, " Cancel ");
230
228
  i0.ɵɵelementEnd()()()();
231
229
  } if (rf & 2) {
232
230
  const ctx_r1 = i0.ɵɵnextContext();
233
231
  i0.ɵɵadvance(12);
234
232
  i0.ɵɵtextInterpolate(ctx_r1.selectedRole.Name);
235
- i0.ɵɵadvance(6);
233
+ i0.ɵɵadvance(4);
236
234
  i0.ɵɵproperty("disabled", ctx_r1.isLoading);
237
235
  i0.ɵɵadvance();
238
- i0.ɵɵconditional(ctx_r1.isLoading ? 19 : 20);
236
+ i0.ɵɵconditional(ctx_r1.isLoading ? 17 : 18);
239
237
  } }
240
238
  let RoleManagementComponent = class RoleManagementComponent extends BaseDashboard {
241
239
  cdr;
@@ -268,6 +266,10 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
268
266
  expandedRoleId = null;
269
267
  // Role permissions (simplified view)
270
268
  rolePermissions = new Map();
269
+ // Read-only entity-permission summary per role id (lazily loaded on selection),
270
+ // used only to surface a non-sensitive count summary to the agent context.
271
+ permissionSummaryCache = new Map();
272
+ selectedRolePermissionSummary = null;
271
273
  destroy$ = new Subject();
272
274
  get metadata() { return this.ProviderToUse; }
273
275
  constructor(cdr, ngZone) {
@@ -280,10 +282,176 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
280
282
  }
281
283
  initDashboard() {
282
284
  this.setupFilterSubscription();
285
+ this.registerAgentClientTools();
283
286
  }
284
287
  loadData() {
285
288
  this.loadInitialData();
286
289
  }
290
+ // ================================================================
291
+ // AI Agent Context & Client Tools
292
+ //
293
+ // 🚨 SAFETY BOUNDARY — READ-ONLY / GOVERNANCE SURFACE 🚨
294
+ // Role Management is a security-sensitive admin surface. The agent context
295
+ // and client tools registered here are strictly READ-ONLY / navigational:
296
+ // type filter, free-text search, SELECTING a role (view, with a read-only
297
+ // permission-count summary), clearing filters, refreshing, and navigating to
298
+ // a role's record for viewing. The mutating operations on this component —
299
+ // create/edit/delete roles — AND any permission-grant/revoke surface are
300
+ // DELIBERATELY NOT exposed to the agent; they remain human-initiated. The
301
+ // permission summary published here is COUNTS ONLY (how many entities the role
302
+ // can read/create/update/delete) — never a grant control. Context exposes only
303
+ // counts, active filter selection, and on-screen display names.
304
+ // ================================================================
305
+ /**
306
+ * Publish the current role-management state to the AI agent. Re-invoked on
307
+ * every meaningful state change (data load, filter, selection). Shaped by the
308
+ * pure {@link buildRoleManagementAgentContext} helper (unit-tested in isolation).
309
+ */
310
+ publishAgentContext() {
311
+ const selected = this.expandedRoleId
312
+ ? this.roles.find(r => UUIDsEqual(r.ID, this.expandedRoleId)) ?? null
313
+ : null;
314
+ const input = {
315
+ TotalRoleCount: this.roles.length,
316
+ FilteredRoleCount: this.filteredRoles.length,
317
+ SystemRoleCount: this.stats.systemRoles,
318
+ CustomRoleCount: this.stats.customRoles,
319
+ TypeFilter: this.filters$.value.type,
320
+ SearchText: this.filters$.value.search,
321
+ SelectedRoleId: selected?.ID ?? null,
322
+ SelectedRoleName: selected?.Name ?? null,
323
+ VisibleRoleNames: this.filteredRoles.map(r => r.Name ?? '').filter(n => n !== ''),
324
+ SelectedRolePermissions: selected ? this.selectedRolePermissionSummary : null,
325
+ };
326
+ this.navigationService.SetAgentContext(this, buildRoleManagementAgentContext(input));
327
+ }
328
+ /**
329
+ * Register the read-only / navigational client tools the agent may invoke.
330
+ * Every Handler is tolerant: validates input and returns
331
+ * `{ Success: false, ErrorMessage }` rather than throwing.
332
+ */
333
+ registerAgentClientTools() {
334
+ this.navigationService.SetAgentClientTools(this, [
335
+ {
336
+ Name: 'FilterRolesByType',
337
+ Description: 'Filter the role list by type. Valid values: all, system, custom.',
338
+ ParameterSchema: { type: 'object', properties: { type: { type: 'string', enum: ['all', 'system', 'custom'] } }, required: ['type'] },
339
+ Handler: async (params) => this.handleFilterByTypeTool(params),
340
+ },
341
+ {
342
+ Name: 'SearchRoles',
343
+ Description: 'Free-text search across role name and description.',
344
+ ParameterSchema: { type: 'object', properties: { searchText: { type: 'string' } }, required: ['searchText'] },
345
+ Handler: async (params) => this.handleSearchTool(params),
346
+ },
347
+ {
348
+ Name: 'SelectRole',
349
+ Description: 'Select a role for VIEWING by ID or name (exact ID → exact name → partial match). Read-only — does not edit the role. Loads a read-only permission-count summary into SelectedRolePermissions.',
350
+ ParameterSchema: { type: 'object', properties: { role: { type: 'string' } }, required: ['role'] },
351
+ Handler: async (params) => this.handleSelectRoleTool(params),
352
+ },
353
+ {
354
+ Name: 'NavigateToRoleRecord',
355
+ Description: 'Open the record for a role (by ID or name) in a tab for VIEWING. Read-only navigation — does not edit the role.',
356
+ ParameterSchema: { type: 'object', properties: { role: { type: 'string' } }, required: ['role'] },
357
+ Handler: async (params) => this.handleNavigateToRoleRecordTool(params),
358
+ },
359
+ {
360
+ Name: 'ClearRoleFilters',
361
+ Description: 'Clear all role filters (type and search) and show the full list.',
362
+ ParameterSchema: { type: 'object', properties: {} },
363
+ Handler: async () => this.handleClearFiltersTool(),
364
+ },
365
+ {
366
+ Name: 'RefreshRoles',
367
+ Description: 'Reload the role list from the server. Read-only — does not mutate any data.',
368
+ ParameterSchema: { type: 'object', properties: {} },
369
+ Handler: async () => this.handleRefreshTool(),
370
+ },
371
+ ]);
372
+ }
373
+ handleFilterByTypeTool(params) {
374
+ const type = params?.['type'];
375
+ if (!isValidRoleTypeFilter(type)) {
376
+ return { Success: false, ErrorMessage: `Invalid type "${String(type)}". Expected one of: all, system, custom.` };
377
+ }
378
+ this.onTypeFilterChange(type);
379
+ this.publishAgentContext();
380
+ return { Success: true };
381
+ }
382
+ handleSearchTool(params) {
383
+ const searchText = params?.['searchText'];
384
+ if (typeof searchText !== 'string') {
385
+ return { Success: false, ErrorMessage: 'searchText must be a string.' };
386
+ }
387
+ this.updateFilter({ search: searchText });
388
+ this.publishAgentContext();
389
+ return { Success: true };
390
+ }
391
+ async handleSelectRoleTool(params) {
392
+ const raw = String(params?.['role'] ?? '');
393
+ const resolved = resolveRoleByIDOrName(raw, this.roles.map(r => ({ ID: r.ID, Name: r.Name ?? '' })));
394
+ if (!resolved.ok) {
395
+ return { Success: false, ErrorMessage: resolved.error };
396
+ }
397
+ this.expandedRoleId = resolved.match.ID;
398
+ this.selectedRolePermissionSummary = await this.loadPermissionSummary(resolved.match.ID);
399
+ this.cdr.markForCheck();
400
+ this.publishAgentContext();
401
+ return { Success: true };
402
+ }
403
+ handleNavigateToRoleRecordTool(params) {
404
+ const raw = String(params?.['role'] ?? '');
405
+ const resolved = resolveRoleByIDOrName(raw, this.roles.map(r => ({ ID: r.ID, Name: r.Name ?? '' })));
406
+ if (!resolved.ok) {
407
+ return { Success: false, ErrorMessage: resolved.error };
408
+ }
409
+ this.navigationService.OpenEntityRecord('MJ: Roles', CompositeKey.FromID(resolved.match.ID));
410
+ return { Success: true };
411
+ }
412
+ /**
413
+ * Lazily load and cache the READ-ONLY entity-permission count summary for a role.
414
+ * Surfaces only how broadly the role is granted access — never a grant control.
415
+ */
416
+ async loadPermissionSummary(roleId) {
417
+ const cached = this.permissionSummaryCache.get(roleId);
418
+ if (cached) {
419
+ return cached;
420
+ }
421
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
422
+ const result = await rv.RunView({
423
+ EntityName: 'MJ: Entity Permissions',
424
+ ExtraFilter: `RoleID='${roleId.replace(/'/g, "''")}'`,
425
+ ResultType: 'entity_object'
426
+ });
427
+ const rows = result.Success ? result.Results : [];
428
+ const summary = {
429
+ EntityCount: rows.length,
430
+ ReadCount: rows.filter(p => p.CanRead).length,
431
+ CreateCount: rows.filter(p => p.CanCreate).length,
432
+ UpdateCount: rows.filter(p => p.CanUpdate).length,
433
+ DeleteCount: rows.filter(p => p.CanDelete).length,
434
+ };
435
+ this.permissionSummaryCache.set(roleId, summary);
436
+ return summary;
437
+ }
438
+ handleClearFiltersTool() {
439
+ this.resetAllFiltersAndSearch();
440
+ this.publishAgentContext();
441
+ return { Success: true };
442
+ }
443
+ async handleRefreshTool() {
444
+ try {
445
+ this.permissionSummaryCache.clear();
446
+ this.selectedRolePermissionSummary = null;
447
+ await this.loadInitialData();
448
+ this.publishAgentContext();
449
+ return { Success: true };
450
+ }
451
+ catch (e) {
452
+ return { Success: false, ErrorMessage: e instanceof Error ? e.message : 'Refresh failed.' };
453
+ }
454
+ }
287
455
  ngOnDestroy() {
288
456
  this.destroy$.next();
289
457
  this.destroy$.complete();
@@ -307,6 +475,7 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
307
475
  this.ngZone.run(() => {
308
476
  this.isLoading = false;
309
477
  this.cdr.markForCheck();
478
+ this.publishAgentContext();
310
479
  });
311
480
  }
312
481
  }
@@ -324,6 +493,7 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
324
493
  .pipe(debounceTime(300), distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), takeUntil(this.destroy$))
325
494
  .subscribe(() => {
326
495
  this.applyFilters();
496
+ this.publishAgentContext();
327
497
  });
328
498
  }
329
499
  applyFilters() {
@@ -405,8 +575,33 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
405
575
  clearAllAppliedFilters() {
406
576
  this.updateFilter({ type: 'all' });
407
577
  }
578
+ /** True when search and/or panel filters are narrowing the list — gates the
579
+ * no-results empty-state "Reset filters" CTA. */
580
+ get IsListNarrowed() {
581
+ return this.filters$.value.search !== '' || this.TotalActiveFilterCount > 0;
582
+ }
583
+ /** Reset everything narrowing the list (search + Type) and refresh
584
+ * immediately. Wired to the no-results empty-state CTA. Unlike
585
+ * clearAllAppliedFilters(), this also clears the search box. */
586
+ resetAllFiltersAndSearch() {
587
+ this.filters$.next({ type: 'all', search: '' });
588
+ this.applyFilters();
589
+ this.cdr.markForCheck();
590
+ }
408
591
  toggleRoleExpansion(roleId) {
409
592
  this.expandedRoleId = this.expandedRoleId === roleId ? null : roleId;
593
+ if (this.expandedRoleId) {
594
+ // Lazy-load the read-only permission summary so the agent context reflects
595
+ // the user's current selection; fire-and-forget (re-publishes on completion).
596
+ void this.loadPermissionSummary(this.expandedRoleId).then(summary => {
597
+ this.selectedRolePermissionSummary = summary;
598
+ this.publishAgentContext();
599
+ });
600
+ }
601
+ else {
602
+ this.selectedRolePermissionSummary = null;
603
+ }
604
+ this.publishAgentContext();
410
605
  }
411
606
  isRoleExpanded(roleId) {
412
607
  return this.expandedRoleId === roleId;
@@ -482,7 +677,7 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
482
677
  }
483
678
  }
484
679
  static ɵfac = function RoleManagementComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RoleManagementComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.NgZone)); };
485
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RoleManagementComponent, selectors: [["mj-role-management"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 19, vars: 14, consts: [["AriaLabel", "Filter roles", "Title", "Roles", "Subtitle", "Define system and custom roles for permissions"], ["meta", ""], ["Label", "roles", 3, "Count", "Total"], ["toolbar", ""], ["Placeholder", "Search roles by name or description...", 3, "ValueChange", "Value"], ["Label", "Filters", "Icon", "fa-solid fa-filter", 3, "ClearAllRequested", "ActiveCount", "ShowClearAll"], [3, "ValuesChange", "Reset", "Fields", "Values"], ["actions", ""], [3, "Clicked", "Loading"], ["mjButton", "", "variant", "primary", "size", "sm", "aria-label", "Add new role", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "mj-action-label"], ["role", "status", "aria-live", "polite", "aria-busy", "true", 1, "loading-container"], ["role", "alert", "aria-live", "assertive", 1, "error-container"], ["role", "list", "aria-label", "Roles list", 1, "roles-list"], [3, "result", "data", "visible"], ["role", "presentation", 1, "modal-backdrop"], ["text", "Loading roles...", "size", "medium"], [1, "error-content"], ["aria-hidden", "true", 1, "fa-solid", "fa-exclamation-triangle", "error-icon"], [1, "error-message"], ["mjButton", "", "variant", "primary", "aria-label", "Retry loading roles", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-refresh"], ["role", "listitem", 1, "role-card", 3, "expanded"], ["role", "status", 1, "empty-state"], ["role", "listitem", 1, "role-card"], ["tabindex", "0", "role", "button", 1, "role-header", 3, "click", "keydown.enter", "keydown.space"], [1, "role-info"], ["aria-hidden", "true", 1, "role-icon-wrapper"], [1, "role-details"], [1, "role-name"], [1, "role-description"], [1, "role-meta"], [1, "role-type-badge"], [1, "role-actions", 3, "click"], ["mjButton", "", "variant", "flat", "size", "sm", "title", "Edit", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-edit"], ["mjButton", "", "variant", "danger", "size", "sm", "title", "Delete", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-trash"], ["aria-label", "Toggle details", 1, "expand-btn"], ["aria-hidden", "true", 1, "fa-solid", "fa-chevron-down"], [1, "role-content"], [1, "mobile-actions-bar", 3, "click"], [1, "mobile-action-buttons"], [1, "btn-label"], [1, "role-stats"], [1, "stat-item"], ["aria-hidden", "true", 1, "fa-solid", "fa-users"], [1, "stat-label"], [1, "stat-value"], ["aria-hidden", "true", 1, "fa-solid", "fa-calendar"], ["aria-hidden", "true", 1, "fa-solid", "fa-clock"], [1, "permissions-preview"], [1, "section-title"], ["aria-hidden", "true", 1, "fa-solid", "fa-key"], [1, "permissions-note"], ["aria-hidden", "true", 1, "fa-solid", "fa-user-tag", "empty-icon"], [1, "empty-text"], [1, "empty-subtext"], ["role", "presentation", 1, "modal-backdrop", 3, "click"], ["role", "dialog", "aria-modal", "true", "aria-labelledby", "delete-dialog-title", "aria-describedby", "delete-dialog-desc", 1, "modal-dialog", 3, "click"], [1, "modal-header"], ["id", "delete-dialog-title", 1, "modal-title"], ["aria-hidden", "true", 1, "fa-solid", "fa-exclamation-triangle", "text-danger"], ["aria-label", "Close dialog", 1, "modal-close", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-times"], ["id", "delete-dialog-desc", 1, "modal-body"], [1, "text-warning"], ["aria-hidden", "true", 1, "fa-solid", "fa-warning"], [1, "modal-footer"], ["mjButton", "", "variant", "danger", 3, "click", "disabled"], ["mjButton", "", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"]], template: function RoleManagementComponent_Template(rf, ctx) { if (rf & 1) {
680
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RoleManagementComponent, selectors: [["mj-role-management"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 19, vars: 14, consts: [["AriaLabel", "Filter roles", "Title", "Roles", "Subtitle", "Define system and custom roles for permissions"], ["meta", ""], ["Label", "roles", 3, "Count", "Total"], ["toolbar", ""], ["Placeholder", "Search roles by name or description...", 3, "ValueChange", "Value"], ["Label", "Filters", "Icon", "fa-solid fa-filter", 3, "ClearAllRequested", "ActiveCount", "ShowClearAll"], [3, "ValuesChange", "Reset", "Fields", "Values"], ["actions", ""], [3, "Clicked", "Loading"], ["mjButton", "", "variant", "primary", "size", "sm", "aria-label", "Add new role", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "mj-action-label"], ["role", "status", "aria-live", "polite", "aria-busy", "true", 1, "loading-container"], ["role", "alert", "aria-live", "assertive", 1, "error-container"], ["role", "list", "aria-label", "Roles list", 1, "roles-list"], [3, "result", "data", "visible"], ["role", "presentation", 1, "modal-backdrop"], ["text", "Loading roles...", "size", "medium"], [1, "error-content"], ["aria-hidden", "true", 1, "fa-solid", "fa-exclamation-triangle", "error-icon"], [1, "error-message"], ["mjButton", "", "variant", "primary", "aria-label", "Retry loading roles", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-refresh"], ["role", "listitem", 1, "role-card", 3, "expanded"], ["Icon", "fa-solid fa-user-tag", "Title", "No roles found", "Message", "Try adjusting your filters or create a new role", "ActionIcon", "fa-solid fa-rotate-left", 3, "Variant", "ActionText"], ["role", "listitem", 1, "role-card"], ["tabindex", "0", "role", "button", 1, "role-header", 3, "click", "keydown.enter", "keydown.space"], [1, "role-info"], ["aria-hidden", "true", 1, "role-icon-wrapper"], [1, "role-details"], [1, "role-name"], [1, "role-description"], [1, "role-meta"], [1, "role-type-badge"], [1, "role-actions", 3, "click"], ["mjButton", "", "variant", "flat", "size", "sm", "title", "Edit", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-edit"], ["mjButton", "", "variant", "danger", "size", "sm", "title", "Delete", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-trash"], ["aria-label", "Toggle details", 1, "expand-btn"], ["aria-hidden", "true", 1, "fa-solid", "fa-chevron-down"], [1, "role-content"], [1, "mobile-actions-bar", 3, "click"], [1, "mobile-action-buttons"], [1, "btn-label"], [1, "role-stats"], [1, "stat-item"], ["aria-hidden", "true", 1, "fa-solid", "fa-users"], [1, "stat-label"], [1, "stat-value"], ["aria-hidden", "true", 1, "fa-solid", "fa-calendar"], ["aria-hidden", "true", 1, "fa-solid", "fa-clock"], [1, "permissions-preview"], [1, "section-title"], ["aria-hidden", "true", 1, "fa-solid", "fa-key"], [1, "permissions-note"], ["Icon", "fa-solid fa-user-tag", "Title", "No roles found", "Message", "Try adjusting your filters or create a new role", "ActionIcon", "fa-solid fa-rotate-left", 3, "Action", "Variant", "ActionText"], ["role", "presentation", 1, "modal-backdrop", 3, "click"], ["role", "dialog", "aria-modal", "true", "aria-labelledby", "delete-dialog-title", "aria-describedby", "delete-dialog-desc", 1, "modal-dialog", 3, "click"], [1, "modal-header"], ["id", "delete-dialog-title", 1, "modal-title"], ["aria-hidden", "true", 1, "fa-solid", "fa-exclamation-triangle", "text-danger"], ["aria-label", "Close dialog", 1, "modal-close", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-times"], ["id", "delete-dialog-desc", 1, "modal-body"], ["Variant", "warning", "Message", "This will affect all users assigned to this role."], [1, "modal-footer"], ["mjButton", "", "variant", "danger", 3, "click", "disabled"], ["mjButton", "", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"]], template: function RoleManagementComponent_Template(rf, ctx) { if (rf & 1) {
486
681
  i0.ɵɵelementStart(0, "mj-page-header-interior", 0)(1, "div", 1);
487
682
  i0.ɵɵelement(2, "mj-stat-badge", 2);
488
683
  i0.ɵɵelementEnd();
@@ -511,7 +706,7 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
511
706
  i0.ɵɵelementStart(17, "mj-role-dialog", 15);
512
707
  i0.ɵɵlistener("result", function RoleManagementComponent_Template_mj_role_dialog_result_17_listener($event) { return ctx.onRoleDialogResult($event); });
513
708
  i0.ɵɵelementEnd();
514
- i0.ɵɵconditionalCreate(18, RoleManagementComponent_Conditional_18_Template, 23, 3, "div", 16);
709
+ i0.ɵɵconditionalCreate(18, RoleManagementComponent_Conditional_18_Template, 21, 3, "div", 16);
515
710
  } if (rf & 2) {
516
711
  i0.ɵɵadvance(2);
517
712
  i0.ɵɵproperty("Count", ctx.filteredRoles.length)("Total", ctx.roles.length);
@@ -533,7 +728,7 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
533
728
  i0.ɵɵproperty("data", ctx.roleDialogData)("visible", ctx.showRoleDialog);
534
729
  i0.ɵɵadvance();
535
730
  i0.ɵɵconditional(ctx.showDeleteConfirm && ctx.selectedRole ? 18 : -1);
536
- } }, dependencies: [i1.LoadingComponent, i2.MJButtonDirective, i2.MJPageHeaderInteriorComponent, i2.MJPageBodyInteriorComponent, i2.MJPageSearchComponent, i2.MJFilterPopoverComponent, i2.MJFilterPanelComponent, i2.MJRefreshButtonComponent, i2.MJStatBadgeComponent, i3.RoleDialogComponent, i4.DatePipe], styles: ["\n\n\n\n\n\n\n\n\n\n\n[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n\n\n\n\n.sticky-header[_ngcontent-%COMP%] {\n flex-shrink: 0;\n background: var(--mj-bg-page);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n z-index: 10;\n}\n\n\n\n\n\n.scrollable-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n padding: 1rem;\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (min-width: 768px) {\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 1.5rem 2rem;\n }\n}\n\n@media (min-width: 1024px) {\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 2rem;\n }\n}\n\n@media (min-width: 1440px) {\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 2rem 2.5rem;\n }\n}\n\n\n\n\n\n.action-buttons[_ngcontent-%COMP%] {\n flex-shrink: 0;\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .action-buttons[_ngcontent-%COMP%] {\n padding: 1rem 1.5rem;\n }\n}\n\n@media (max-width: 639px) {\n .action-buttons[_ngcontent-%COMP%] {\n justify-content: center;\n flex-wrap: wrap;\n }\n}\n\n\n\n\n\n.mj-grid-4[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 0.5rem;\n padding: 0 1rem 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n grid-template-columns: repeat(4, 1fr);\n padding: 0 1.5rem 1rem 1.5rem;\n gap: 0.75rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n gap: 1rem;\n }\n}\n\n\n\n.mj-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n padding: 1rem;\n box-shadow: var(--mj-shadow-sm);\n display: flex;\n align-items: center;\n gap: 1rem;\n border: 1px solid var(--mj-border-default);\n cursor: default;\n pointer-events: none;\n}\n\n@media (min-width: 768px) {\n .mj-card[_ngcontent-%COMP%] {\n padding: 1rem 1.25rem;\n }\n}\n\n\n\n\n\n.stat-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.25rem;\n flex-shrink: 0;\n}\n\n@media (min-width: 768px) {\n .stat-icon[_ngcontent-%COMP%] {\n width: 52px;\n height: 52px;\n font-size: 1.375rem;\n }\n}\n\n.stat-icon-total[_ngcontent-%COMP%] {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n.stat-icon-active[_ngcontent-%COMP%] {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n\n\n\n\n.stat-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.stat-content[_ngcontent-%COMP%] .stat-value[_ngcontent-%COMP%] {\n font-size: 1.75rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 768px) {\n .stat-content[_ngcontent-%COMP%] .stat-value[_ngcontent-%COMP%] {\n font-size: 2rem;\n }\n}\n\n.stat-content[_ngcontent-%COMP%] .stat-label[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-top: 0.25rem;\n}\n\n@media (min-width: 768px) {\n .stat-content[_ngcontent-%COMP%] .stat-label[_ngcontent-%COMP%] {\n font-size: 0.8125rem;\n }\n}\n\n\n\n\n\n.filters-section[_ngcontent-%COMP%] {\n flex-shrink: 0;\n background: var(--mj-bg-surface-card);\n margin: 0 1rem 0.75rem 1rem;\n padding: 0.75rem;\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .filters-section[_ngcontent-%COMP%] {\n margin: 0 1.5rem 1rem 1.5rem;\n padding: 1rem 1.25rem;\n }\n}\n\n.filters-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 1rem;\n align-items: flex-end;\n flex-wrap: wrap;\n}\n\n@media (min-width: 768px) {\n .filters-row[_ngcontent-%COMP%] {\n gap: 1.5rem;\n }\n}\n\n.mj-filter-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.mj-filter-label[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mj-filter-buttons[_ngcontent-%COMP%] {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-full);\n padding: 4px;\n gap: 2px;\n}\n\n\n\n\n\n.mj-search[_ngcontent-%COMP%] {\n position: relative;\n flex: 1;\n min-width: 200px;\n}\n\n@media (min-width: 640px) {\n .mj-search[_ngcontent-%COMP%] {\n min-width: 280px;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-search[_ngcontent-%COMP%] {\n min-width: 400px;\n max-width: 600px;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search[_ngcontent-%COMP%] {\n min-width: 500px;\n max-width: 800px;\n }\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-secondary);\n font-size: 1rem;\n pointer-events: none;\n transition: color 0.2s ease;\n}\n\n@media (min-width: 1024px) {\n .mj-search[_ngcontent-%COMP%] .mj-search-icon[_ngcontent-%COMP%] {\n left: 1.25rem;\n font-size: 1.125rem;\n }\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 0.875rem 1rem 0.875rem 2.75rem;\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full);\n font-size: 1rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n}\n\n@media (min-width: 1024px) {\n .mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%] {\n padding: 1rem 1.25rem 1rem 3.25rem;\n font-size: 1.0625rem;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%] {\n padding: 1.125rem 1.5rem 1.125rem 3.5rem;\n }\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.2);\n background: var(--mj-bg-page);\n}\n\n.mj-search[_ngcontent-%COMP%]:focus-within .mj-search-icon[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n\n\n\n\n.content-area[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n overflow: visible;\n position: relative;\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-xl);\n box-shadow: var(--mj-shadow-sm);\n padding: 1rem;\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .content-area[_ngcontent-%COMP%] {\n padding: 1.5rem;\n }\n}\n\n\n\n\n\n.expand-btn[_ngcontent-%COMP%] {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1rem;\n cursor: pointer;\n transition: all 0.2s ease;\n border-radius: var(--mj-radius-full);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.expand-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.expand-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n\n\n\n\n.status-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.status-badge.status-active[_ngcontent-%COMP%] {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n.status-badge.status-inactive[_ngcontent-%COMP%] {\n background: var(--mj-color-error-100);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-status-error);\n}\n\n.status-badge[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.625rem;\n}\n\n\n\n\n\n.mobile-actions-bar[_ngcontent-%COMP%] {\n display: none;\n}\n\n.mobile-action-buttons[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.5rem;\n}\n\n\n\n\n\n.empty-state[_ngcontent-%COMP%] {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.empty-state[_ngcontent-%COMP%] .empty-icon[_ngcontent-%COMP%] {\n font-size: 4rem;\n color: var(--mj-border-default);\n margin-bottom: 1.5rem;\n}\n\n.empty-state[_ngcontent-%COMP%] .empty-text[_ngcontent-%COMP%] {\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.empty-state[_ngcontent-%COMP%] .empty-subtext[_ngcontent-%COMP%] {\n font-size: 1rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n\n\n\n\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n\n\n\n\n.error-container[_ngcontent-%COMP%] {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.error-container[_ngcontent-%COMP%] .error-content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1rem;\n}\n\n.error-container[_ngcontent-%COMP%] .error-icon[_ngcontent-%COMP%] {\n font-size: 3.5rem;\n color: var(--mj-status-error);\n margin-bottom: 1rem;\n}\n\n.error-container[_ngcontent-%COMP%] .error-message[_ngcontent-%COMP%] {\n font-size: 1.0625rem;\n color: var(--mj-text-primary);\n margin: 0 0 1.5rem 0;\n}\n\n\n\n\n\n.modal-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n padding: 1rem;\n animation: _ngcontent-%COMP%_fadeIn 0.2s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-dialog[_ngcontent-%COMP%] {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl);\n box-shadow: var(--mj-shadow-2xl);\n max-width: 500px;\n width: 100%;\n max-height: 90vh;\n overflow: hidden;\n animation: _ngcontent-%COMP%_slideUp 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.modal-header[_ngcontent-%COMP%] .modal-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.modal-header[_ngcontent-%COMP%] .modal-close[_ngcontent-%COMP%] {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.modal-header[_ngcontent-%COMP%] .modal-close[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.modal-body[_ngcontent-%COMP%] {\n padding: 1.5rem;\n}\n\n.modal-body[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 1rem;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n line-height: 1.5;\n}\n\n.modal-body[_ngcontent-%COMP%] p[_ngcontent-%COMP%]:last-child {\n margin-bottom: 0;\n}\n\n.modal-footer[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (max-width: 639px) {\n .modal-dialog[_ngcontent-%COMP%] {\n width: 95%;\n max-height: 85vh;\n }\n\n .modal-header[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .modal-header[_ngcontent-%COMP%] .modal-title[_ngcontent-%COMP%] {\n font-size: 1.125rem;\n }\n\n .modal-body[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .modal-footer[_ngcontent-%COMP%] {\n padding: 1rem;\n flex-direction: column;\n }\n\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n.filter-button[_ngcontent-%COMP%] {\n display: none;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.25rem;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-full);\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n min-width: 44px;\n}\n\n.filter-button[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.filter-button[_ngcontent-%COMP%]:active {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.filter-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1rem;\n}\n\n\n\n\n\n.filter-modal-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 1000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n padding: 0;\n}\n\n.filter-modal[_ngcontent-%COMP%] {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl) var(--mj-radius-2xl) 0 0;\n box-shadow: var(--mj-shadow-2xl);\n width: 100%;\n max-height: 80vh;\n overflow: hidden;\n animation: _ngcontent-%COMP%_slideUpFromBottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.filter-modal-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.25rem 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-modal-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.filter-modal-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n}\n\n.filter-modal-close[_ngcontent-%COMP%] {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.filter-modal-close[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.filter-modal-body[_ngcontent-%COMP%] {\n padding: 1.5rem;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n flex: 1;\n}\n\n.filter-modal-footer[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.75rem;\n padding: 1.25rem 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n.filter-modal-footer[_ngcontent-%COMP%] button[_ngcontent-%COMP%] {\n flex: 1;\n margin: 0;\n}\n\n\n\n.filter-options-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n}\n\n.filter-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.filter-group-label[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin: 0;\n}\n\n.filter-group-options[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.filter-option[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.875rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 56px;\n}\n\n.filter-option[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.filter-option.selected[_ngcontent-%COMP%] {\n background: var(--mj-color-accent-300);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.filter-option[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%], \n.filter-option[_ngcontent-%COMP%] input[type=\"radio\"][_ngcontent-%COMP%] {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.filter-option-label[_ngcontent-%COMP%] {\n flex: 1;\n font-size: 1rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.filter-option.selected[_ngcontent-%COMP%] .filter-option-label[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n\n\n\n\n.text-danger[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n.text-warning[_ngcontent-%COMP%] {\n color: var(--mj-color-warning-500);\n}\n\n.text-success[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n}\n\n\n\n\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes _ngcontent-%COMP%_slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n@keyframes _ngcontent-%COMP%_slideDown {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes _ngcontent-%COMP%_slideUpFromBottom {\n from {\n transform: translateY(100%);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n\n\n\n\n.mj-search-input[_ngcontent-%COMP%]:focus-visible, \nbutton[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n\n\n\n\n@media (prefers-reduced-motion: reduce) {\n *[_ngcontent-%COMP%], \n *[_ngcontent-%COMP%]::before, \n *[_ngcontent-%COMP%]::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}", "\n\n\n\n\n\n\n\n\n\n\n\n\n.status-chips[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-2);\n flex-shrink: 0;\n}\n\n\n\n\n\n.mj-grid-4[_ngcontent-%COMP%] {\n gap: 0.75rem;\n padding: 0 1rem 1rem 1rem;\n}\n\n@media (min-width: 768px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n padding: 0 1.5rem 1.25rem 1.5rem;\n gap: 1rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n gap: 1.25rem;\n }\n}\n\n\n\n.mj-card[_ngcontent-%COMP%] {\n box-shadow: none;\n}\n\n@media (min-width: 768px) {\n .mj-card[_ngcontent-%COMP%] {\n padding: 1.125rem 1.25rem;\n }\n}\n\n\n\n.stat-icon-system[_ngcontent-%COMP%] {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-700);\n}\n\n.stat-icon-custom[_ngcontent-%COMP%] {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n.stat-icon-active[_ngcontent-%COMP%] {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n\n\n\n\n.roles-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n\n\n\n\n.role-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-xl);\n overflow: hidden;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.role-card[_ngcontent-%COMP%]:hover {\n box-shadow: var(--mj-shadow-md);\n border-color: var(--mj-brand-primary);\n}\n\n.role-card.expanded[_ngcontent-%COMP%] {\n box-shadow: var(--mj-shadow-lg);\n border-color: var(--mj-brand-primary);\n}\n\n.role-card.expanded[_ngcontent-%COMP%] .expand-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n transform: rotate(180deg);\n}\n\n.role-header[_ngcontent-%COMP%] {\n padding: 1.25rem 1.5rem;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n background: var(--mj-bg-surface);\n transition: background-color 0.2s ease;\n}\n\n.role-header[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.role-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex: 1;\n min-width: 0;\n}\n\n.role-icon-wrapper[_ngcontent-%COMP%] {\n width: 52px;\n height: 52px;\n border-radius: var(--mj-radius-lg);\n background: var(--mj-color-accent-300);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n flex-shrink: 0;\n}\n\n.role-details[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.role-details[_ngcontent-%COMP%] .role-name[_ngcontent-%COMP%] {\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.25rem 0;\n word-break: break-word;\n overflow-wrap: break-word;\n}\n\n.role-details[_ngcontent-%COMP%] .role-description[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n color: var(--mj-text-secondary);\n margin: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@media (max-width: 639px) {\n .role-details[_ngcontent-%COMP%] .role-description[_ngcontent-%COMP%] {\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n}\n\n.role-meta[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex-shrink: 0;\n}\n\n\n\n\n\n.role-type-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.role-type-badge.badge-system[_ngcontent-%COMP%] {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-800);\n border: 1px solid var(--mj-color-warning-500);\n}\n\n.role-type-badge.badge-custom[_ngcontent-%COMP%] {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n\n\n\n\n.role-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.375rem;\n}\n\n\n\n.expand-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n\n\n\n\n.role-content[_ngcontent-%COMP%] {\n padding: 1.5rem;\n background: var(--mj-bg-surface);\n border-top: 1px solid var(--mj-border-default);\n animation: slideDown 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.role-stats[_ngcontent-%COMP%] {\n display: flex;\n gap: 2rem;\n margin-bottom: 1.5rem;\n flex-wrap: wrap;\n}\n\n.role-stats[_ngcontent-%COMP%] .stat-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.9375rem;\n padding: 0.5rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n}\n\n.role-stats[_ngcontent-%COMP%] .stat-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 1rem;\n}\n\n.role-stats[_ngcontent-%COMP%] .stat-item[_ngcontent-%COMP%] .stat-label[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n}\n\n.role-stats[_ngcontent-%COMP%] .stat-item[_ngcontent-%COMP%] .stat-value[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n\n\n\n\n.permissions-preview[_ngcontent-%COMP%] {\n padding: 1.25rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n.permissions-preview[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.permissions-preview[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 1.125rem;\n}\n\n.permissions-preview[_ngcontent-%COMP%] .permissions-note[_ngcontent-%COMP%] {\n font-size: 0.9375rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n\n\n.modal-body[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n line-height: 1.6;\n}\n\n.modal-body[_ngcontent-%COMP%] .text-warning[_ngcontent-%COMP%] {\n color: var(--mj-color-warning-500);\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n background: var(--mj-color-warning-50);\n border-radius: var(--mj-radius-md);\n font-weight: 500;\n}\n\n\n\n.modal-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n}\n\n\n\n\n\n@media (min-width: 1440px) {\n .role-management-container[_ngcontent-%COMP%] {\n margin: 0 auto;\n }\n\n .action-buttons[_ngcontent-%COMP%] {\n padding: 1rem 2rem;\n }\n\n .mj-grid-4[_ngcontent-%COMP%] {\n padding: 0 2rem 1.25rem 2rem;\n gap: 1.5rem;\n }\n\n}\n\n\n\n\n\n@media (max-width: 639px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n display: none !important;\n }\n\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 0.75rem;\n }\n\n .modal-backdrop[_ngcontent-%COMP%] {\n padding: 0.5rem;\n }\n\n .modal-dialog[_ngcontent-%COMP%] {\n border-radius: var(--mj-radius-xl);\n max-height: 95vh;\n }\n\n .roles-list[_ngcontent-%COMP%] {\n gap: 0.75rem;\n }\n\n .role-card[_ngcontent-%COMP%] {\n border-radius: var(--mj-radius-lg);\n overflow: hidden;\n }\n\n .role-header[_ngcontent-%COMP%] {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 1rem;\n }\n\n .role-info[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n flex-direction: row;\n align-items: flex-start;\n gap: 0.875rem;\n }\n\n .role-icon-wrapper[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n font-size: 1rem;\n flex-shrink: 0;\n }\n\n .role-details[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n }\n\n .role-details[_ngcontent-%COMP%] .role-name[_ngcontent-%COMP%] {\n font-size: 1rem;\n line-height: 1.3;\n margin-bottom: 0.125rem;\n }\n\n .role-details[_ngcontent-%COMP%] .role-description[_ngcontent-%COMP%] {\n font-size: 0.8125rem;\n -webkit-line-clamp: 1;\n line-clamp: 1;\n }\n\n .role-meta[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-end;\n padding-top: 0;\n border-top: none;\n width: auto;\n }\n\n .role-meta[_ngcontent-%COMP%] .role-type-badge[_ngcontent-%COMP%] {\n display: none;\n }\n\n .role-meta[_ngcontent-%COMP%] .role-actions[_ngcontent-%COMP%] {\n display: none;\n }\n\n .expand-btn[_ngcontent-%COMP%] {\n flex-shrink: 0;\n min-width: 40px;\n min-height: 40px;\n padding: 0.5rem;\n }\n\n .expand-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1rem;\n }\n\n .mobile-actions-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.875rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-lg);\n margin-bottom: 1rem;\n }\n\n .mobile-actions-bar[_ngcontent-%COMP%] .role-type-badge[_ngcontent-%COMP%] {\n padding: 0.375rem 0.75rem;\n font-size: 0.6875rem;\n }\n\n .mobile-action-buttons[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.5rem;\n }\n\n .mobile-action-buttons[_ngcontent-%COMP%] .mj-btn-sm[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.5rem 0.875rem;\n min-height: 36px;\n }\n\n .mobile-action-buttons[_ngcontent-%COMP%] .mj-btn-sm[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n }\n\n .mobile-action-buttons[_ngcontent-%COMP%] .btn-label[_ngcontent-%COMP%] {\n font-size: 0.8125rem;\n font-weight: 500;\n }\n\n .role-stats[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 0.5rem;\n margin-bottom: 1rem;\n }\n\n .role-content[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .permissions-preview[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .empty-state[_ngcontent-%COMP%] {\n padding: 3rem 1.5rem;\n }\n\n .empty-state[_ngcontent-%COMP%] .empty-icon[_ngcontent-%COMP%] {\n font-size: 3rem;\n }\n\n .loading-container[_ngcontent-%COMP%] {\n padding: 3rem 1.5rem;\n }\n}\n\n\n\n@media (max-width: 374px) {\n .action-buttons[_ngcontent-%COMP%] {\n padding: 0.375rem 0.5rem;\n }\n\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 0.5rem;\n }\n\n .role-header[_ngcontent-%COMP%] {\n padding: 0.75rem;\n }\n}"] });
731
+ } }, dependencies: [i1.LoadingComponent, i2.MJButtonDirective, i2.MJPageHeaderInteriorComponent, i2.MJPageBodyInteriorComponent, i2.MJPageSearchComponent, i2.MJFilterPopoverComponent, i2.MJFilterPanelComponent, i2.MJRefreshButtonComponent, i2.MJStatBadgeComponent, i2.MJEmptyStateComponent, i2.MJAlertComponent, i3.RoleDialogComponent, i4.DatePipe], styles: ["\n\n\n\n\n\n\n\n\n\n\n[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n\n\n\n\n.sticky-header[_ngcontent-%COMP%] {\n flex-shrink: 0;\n background: var(--mj-bg-page);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n z-index: 10;\n}\n\n\n\n\n\n.scrollable-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n padding: 1rem;\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (min-width: 768px) {\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 1.5rem 2rem;\n }\n}\n\n@media (min-width: 1024px) {\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 2rem;\n }\n}\n\n@media (min-width: 1440px) {\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 2rem 2.5rem;\n }\n}\n\n\n\n\n\n.action-buttons[_ngcontent-%COMP%] {\n flex-shrink: 0;\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .action-buttons[_ngcontent-%COMP%] {\n padding: 1rem 1.5rem;\n }\n}\n\n@media (max-width: 639px) {\n .action-buttons[_ngcontent-%COMP%] {\n justify-content: center;\n flex-wrap: wrap;\n }\n}\n\n\n\n\n\n.mj-grid-4[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 0.5rem;\n padding: 0 1rem 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n grid-template-columns: repeat(4, 1fr);\n padding: 0 1.5rem 1rem 1.5rem;\n gap: 0.75rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n gap: 1rem;\n }\n}\n\n\n\n.mj-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n padding: 1rem;\n box-shadow: var(--mj-shadow-sm);\n display: flex;\n align-items: center;\n gap: 1rem;\n border: 1px solid var(--mj-border-default);\n cursor: default;\n pointer-events: none;\n}\n\n@media (min-width: 768px) {\n .mj-card[_ngcontent-%COMP%] {\n padding: 1rem 1.25rem;\n }\n}\n\n\n\n\n\n.stat-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.25rem;\n flex-shrink: 0;\n}\n\n@media (min-width: 768px) {\n .stat-icon[_ngcontent-%COMP%] {\n width: 52px;\n height: 52px;\n font-size: 1.375rem;\n }\n}\n\n.stat-icon-total[_ngcontent-%COMP%] {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n.stat-icon-active[_ngcontent-%COMP%] {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n\n\n\n\n.stat-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.stat-content[_ngcontent-%COMP%] .stat-value[_ngcontent-%COMP%] {\n font-size: 1.75rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 768px) {\n .stat-content[_ngcontent-%COMP%] .stat-value[_ngcontent-%COMP%] {\n font-size: 2rem;\n }\n}\n\n.stat-content[_ngcontent-%COMP%] .stat-label[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-top: 0.25rem;\n}\n\n@media (min-width: 768px) {\n .stat-content[_ngcontent-%COMP%] .stat-label[_ngcontent-%COMP%] {\n font-size: 0.8125rem;\n }\n}\n\n\n\n\n\n.filters-section[_ngcontent-%COMP%] {\n flex-shrink: 0;\n background: var(--mj-bg-surface-card);\n margin: 0 1rem 0.75rem 1rem;\n padding: 0.75rem;\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .filters-section[_ngcontent-%COMP%] {\n margin: 0 1.5rem 1rem 1.5rem;\n padding: 1rem 1.25rem;\n }\n}\n\n.filters-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 1rem;\n align-items: flex-end;\n flex-wrap: wrap;\n}\n\n@media (min-width: 768px) {\n .filters-row[_ngcontent-%COMP%] {\n gap: 1.5rem;\n }\n}\n\n.mj-filter-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.mj-filter-label[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mj-filter-buttons[_ngcontent-%COMP%] {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-full);\n padding: 4px;\n gap: 2px;\n}\n\n\n\n\n\n.mj-search[_ngcontent-%COMP%] {\n position: relative;\n flex: 1;\n min-width: 200px;\n}\n\n@media (min-width: 640px) {\n .mj-search[_ngcontent-%COMP%] {\n min-width: 280px;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-search[_ngcontent-%COMP%] {\n min-width: 400px;\n max-width: 600px;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search[_ngcontent-%COMP%] {\n min-width: 500px;\n max-width: 800px;\n }\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-secondary);\n font-size: 1rem;\n pointer-events: none;\n transition: color 0.2s ease;\n}\n\n@media (min-width: 1024px) {\n .mj-search[_ngcontent-%COMP%] .mj-search-icon[_ngcontent-%COMP%] {\n left: 1.25rem;\n font-size: 1.125rem;\n }\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 0.875rem 1rem 0.875rem 2.75rem;\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full);\n font-size: 1rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n}\n\n@media (min-width: 1024px) {\n .mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%] {\n padding: 1rem 1.25rem 1rem 3.25rem;\n font-size: 1.0625rem;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%] {\n padding: 1.125rem 1.5rem 1.125rem 3.5rem;\n }\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.mj-search[_ngcontent-%COMP%] .mj-search-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.2);\n background: var(--mj-bg-page);\n}\n\n.mj-search[_ngcontent-%COMP%]:focus-within .mj-search-icon[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n\n\n\n\n.content-area[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n overflow: visible;\n position: relative;\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-xl);\n box-shadow: var(--mj-shadow-sm);\n padding: 1rem;\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .content-area[_ngcontent-%COMP%] {\n padding: 1.5rem;\n }\n}\n\n\n\n\n\n.expand-btn[_ngcontent-%COMP%] {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1rem;\n cursor: pointer;\n transition: all 0.2s ease;\n border-radius: var(--mj-radius-full);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.expand-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.expand-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n\n\n\n\n.status-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.status-badge.status-active[_ngcontent-%COMP%] {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n.status-badge.status-inactive[_ngcontent-%COMP%] {\n background: var(--mj-color-error-100);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-status-error);\n}\n\n.status-badge[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.625rem;\n}\n\n\n\n\n\n.mobile-actions-bar[_ngcontent-%COMP%] {\n display: none;\n}\n\n.mobile-action-buttons[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.5rem;\n}\n\n\n\n\n\n.empty-state[_ngcontent-%COMP%] {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.empty-state[_ngcontent-%COMP%] .empty-icon[_ngcontent-%COMP%] {\n font-size: 4rem;\n color: var(--mj-border-default);\n margin-bottom: 1.5rem;\n}\n\n.empty-state[_ngcontent-%COMP%] .empty-text[_ngcontent-%COMP%] {\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.empty-state[_ngcontent-%COMP%] .empty-subtext[_ngcontent-%COMP%] {\n font-size: 1rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n\n\n\n\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n\n\n\n\n.error-container[_ngcontent-%COMP%] {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.error-container[_ngcontent-%COMP%] .error-content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1rem;\n}\n\n.error-container[_ngcontent-%COMP%] .error-icon[_ngcontent-%COMP%] {\n font-size: 3.5rem;\n color: var(--mj-status-error);\n margin-bottom: 1rem;\n}\n\n.error-container[_ngcontent-%COMP%] .error-message[_ngcontent-%COMP%] {\n font-size: 1.0625rem;\n color: var(--mj-text-primary);\n margin: 0 0 1.5rem 0;\n}\n\n\n\n\n\n.modal-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n padding: 1rem;\n animation: _ngcontent-%COMP%_fadeIn 0.2s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-dialog[_ngcontent-%COMP%] {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl);\n box-shadow: var(--mj-shadow-2xl);\n max-width: 500px;\n width: 100%;\n max-height: 90vh;\n overflow: hidden;\n animation: _ngcontent-%COMP%_slideUp 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.modal-header[_ngcontent-%COMP%] .modal-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.modal-header[_ngcontent-%COMP%] .modal-close[_ngcontent-%COMP%] {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.modal-header[_ngcontent-%COMP%] .modal-close[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.modal-body[_ngcontent-%COMP%] {\n padding: 1.5rem;\n}\n\n.modal-body[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 1rem;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n line-height: 1.5;\n}\n\n.modal-body[_ngcontent-%COMP%] p[_ngcontent-%COMP%]:last-child {\n margin-bottom: 0;\n}\n\n.modal-footer[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (max-width: 639px) {\n .modal-dialog[_ngcontent-%COMP%] {\n width: 95%;\n max-height: 85vh;\n }\n\n .modal-header[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .modal-header[_ngcontent-%COMP%] .modal-title[_ngcontent-%COMP%] {\n font-size: 1.125rem;\n }\n\n .modal-body[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .modal-footer[_ngcontent-%COMP%] {\n padding: 1rem;\n flex-direction: column;\n }\n\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n.filter-button[_ngcontent-%COMP%] {\n display: none;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.25rem;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-full);\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n min-width: 44px;\n}\n\n.filter-button[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.filter-button[_ngcontent-%COMP%]:active {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.filter-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1rem;\n}\n\n\n\n\n\n.filter-modal-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 1000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n padding: 0;\n}\n\n.filter-modal[_ngcontent-%COMP%] {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl) var(--mj-radius-2xl) 0 0;\n box-shadow: var(--mj-shadow-2xl);\n width: 100%;\n max-height: 80vh;\n overflow: hidden;\n animation: _ngcontent-%COMP%_slideUpFromBottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.filter-modal-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.25rem 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-modal-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.filter-modal-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n}\n\n.filter-modal-close[_ngcontent-%COMP%] {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.filter-modal-close[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.filter-modal-body[_ngcontent-%COMP%] {\n padding: 1.5rem;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n flex: 1;\n}\n\n.filter-modal-footer[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.75rem;\n padding: 1.25rem 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n.filter-modal-footer[_ngcontent-%COMP%] button[_ngcontent-%COMP%] {\n flex: 1;\n margin: 0;\n}\n\n\n\n.filter-options-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n}\n\n.filter-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.filter-group-label[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin: 0;\n}\n\n.filter-group-options[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.filter-option[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.875rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 56px;\n}\n\n.filter-option[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.filter-option.selected[_ngcontent-%COMP%] {\n background: var(--mj-color-accent-300);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.filter-option[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%], \n.filter-option[_ngcontent-%COMP%] input[type=\"radio\"][_ngcontent-%COMP%] {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.filter-option-label[_ngcontent-%COMP%] {\n flex: 1;\n font-size: 1rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.filter-option.selected[_ngcontent-%COMP%] .filter-option-label[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n\n\n\n\n.text-danger[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n.text-warning[_ngcontent-%COMP%] {\n color: var(--mj-color-warning-500);\n}\n\n.text-success[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n}\n\n\n\n\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes _ngcontent-%COMP%_slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n@keyframes _ngcontent-%COMP%_slideDown {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes _ngcontent-%COMP%_slideUpFromBottom {\n from {\n transform: translateY(100%);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n\n\n\n\n.mj-search-input[_ngcontent-%COMP%]:focus-visible, \nbutton[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n\n\n\n\n@media (prefers-reduced-motion: reduce) {\n *[_ngcontent-%COMP%], \n *[_ngcontent-%COMP%]::before, \n *[_ngcontent-%COMP%]::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}", "\n\n\n\n\n\n\n\n\n\n\n\n\n.status-chips[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-2);\n flex-shrink: 0;\n}\n\n\n\n\n\n.mj-grid-4[_ngcontent-%COMP%] {\n gap: 0.75rem;\n padding: 0 1rem 1rem 1rem;\n}\n\n@media (min-width: 768px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n padding: 0 1.5rem 1.25rem 1.5rem;\n gap: 1rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n gap: 1.25rem;\n }\n}\n\n\n\n.mj-card[_ngcontent-%COMP%] {\n box-shadow: none;\n}\n\n@media (min-width: 768px) {\n .mj-card[_ngcontent-%COMP%] {\n padding: 1.125rem 1.25rem;\n }\n}\n\n\n\n.stat-icon-system[_ngcontent-%COMP%] {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-700);\n}\n\n.stat-icon-custom[_ngcontent-%COMP%] {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n.stat-icon-active[_ngcontent-%COMP%] {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n\n\n\n\n.roles-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n\n\n\n\n.role-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-xl);\n overflow: hidden;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.role-card[_ngcontent-%COMP%]:hover {\n box-shadow: var(--mj-shadow-md);\n border-color: var(--mj-brand-primary);\n}\n\n.role-card.expanded[_ngcontent-%COMP%] {\n box-shadow: var(--mj-shadow-lg);\n border-color: var(--mj-brand-primary);\n}\n\n.role-card.expanded[_ngcontent-%COMP%] .expand-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n transform: rotate(180deg);\n}\n\n.role-header[_ngcontent-%COMP%] {\n padding: 1.25rem 1.5rem;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n background: var(--mj-bg-surface);\n transition: background-color 0.2s ease;\n}\n\n.role-header[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.role-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex: 1;\n min-width: 0;\n}\n\n.role-icon-wrapper[_ngcontent-%COMP%] {\n width: 52px;\n height: 52px;\n border-radius: var(--mj-radius-lg);\n background: var(--mj-color-accent-300);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n flex-shrink: 0;\n}\n\n.role-details[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.role-details[_ngcontent-%COMP%] .role-name[_ngcontent-%COMP%] {\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.25rem 0;\n word-break: break-word;\n overflow-wrap: break-word;\n}\n\n.role-details[_ngcontent-%COMP%] .role-description[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n color: var(--mj-text-secondary);\n margin: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@media (max-width: 639px) {\n .role-details[_ngcontent-%COMP%] .role-description[_ngcontent-%COMP%] {\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n}\n\n.role-meta[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex-shrink: 0;\n}\n\n\n\n\n\n.role-type-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.role-type-badge.badge-system[_ngcontent-%COMP%] {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-800);\n border: 1px solid var(--mj-color-warning-500);\n}\n\n.role-type-badge.badge-custom[_ngcontent-%COMP%] {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n\n\n\n\n.role-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.375rem;\n}\n\n\n\n.expand-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n\n\n\n\n.role-content[_ngcontent-%COMP%] {\n padding: 1.5rem;\n background: var(--mj-bg-surface);\n border-top: 1px solid var(--mj-border-default);\n animation: slideDown 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.role-stats[_ngcontent-%COMP%] {\n display: flex;\n gap: 2rem;\n margin-bottom: 1.5rem;\n flex-wrap: wrap;\n}\n\n.role-stats[_ngcontent-%COMP%] .stat-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.9375rem;\n padding: 0.5rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n}\n\n.role-stats[_ngcontent-%COMP%] .stat-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 1rem;\n}\n\n.role-stats[_ngcontent-%COMP%] .stat-item[_ngcontent-%COMP%] .stat-label[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n}\n\n.role-stats[_ngcontent-%COMP%] .stat-item[_ngcontent-%COMP%] .stat-value[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n\n\n\n\n.permissions-preview[_ngcontent-%COMP%] {\n padding: 1.25rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n.permissions-preview[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.permissions-preview[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 1.125rem;\n}\n\n.permissions-preview[_ngcontent-%COMP%] .permissions-note[_ngcontent-%COMP%] {\n font-size: 0.9375rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n\n\n.modal-body[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n line-height: 1.6;\n}\n\n\n\n.modal-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n}\n\n\n\n\n\n@media (min-width: 1440px) {\n .role-management-container[_ngcontent-%COMP%] {\n margin: 0 auto;\n }\n\n .action-buttons[_ngcontent-%COMP%] {\n padding: 1rem 2rem;\n }\n\n .mj-grid-4[_ngcontent-%COMP%] {\n padding: 0 2rem 1.25rem 2rem;\n gap: 1.5rem;\n }\n\n}\n\n\n\n\n\n@media (max-width: 639px) {\n .mj-grid-4[_ngcontent-%COMP%] {\n display: none !important;\n }\n\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 0.75rem;\n }\n\n .modal-backdrop[_ngcontent-%COMP%] {\n padding: 0.5rem;\n }\n\n .modal-dialog[_ngcontent-%COMP%] {\n border-radius: var(--mj-radius-xl);\n max-height: 95vh;\n }\n\n .roles-list[_ngcontent-%COMP%] {\n gap: 0.75rem;\n }\n\n .role-card[_ngcontent-%COMP%] {\n border-radius: var(--mj-radius-lg);\n overflow: hidden;\n }\n\n .role-header[_ngcontent-%COMP%] {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 1rem;\n }\n\n .role-info[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n flex-direction: row;\n align-items: flex-start;\n gap: 0.875rem;\n }\n\n .role-icon-wrapper[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n font-size: 1rem;\n flex-shrink: 0;\n }\n\n .role-details[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n }\n\n .role-details[_ngcontent-%COMP%] .role-name[_ngcontent-%COMP%] {\n font-size: 1rem;\n line-height: 1.3;\n margin-bottom: 0.125rem;\n }\n\n .role-details[_ngcontent-%COMP%] .role-description[_ngcontent-%COMP%] {\n font-size: 0.8125rem;\n -webkit-line-clamp: 1;\n line-clamp: 1;\n }\n\n .role-meta[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-end;\n padding-top: 0;\n border-top: none;\n width: auto;\n }\n\n .role-meta[_ngcontent-%COMP%] .role-type-badge[_ngcontent-%COMP%] {\n display: none;\n }\n\n .role-meta[_ngcontent-%COMP%] .role-actions[_ngcontent-%COMP%] {\n display: none;\n }\n\n .expand-btn[_ngcontent-%COMP%] {\n flex-shrink: 0;\n min-width: 40px;\n min-height: 40px;\n padding: 0.5rem;\n }\n\n .expand-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1rem;\n }\n\n .mobile-actions-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.875rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-lg);\n margin-bottom: 1rem;\n }\n\n .mobile-actions-bar[_ngcontent-%COMP%] .role-type-badge[_ngcontent-%COMP%] {\n padding: 0.375rem 0.75rem;\n font-size: 0.6875rem;\n }\n\n .mobile-action-buttons[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.5rem;\n }\n\n .mobile-action-buttons[_ngcontent-%COMP%] .mj-btn-sm[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.5rem 0.875rem;\n min-height: 36px;\n }\n\n .mobile-action-buttons[_ngcontent-%COMP%] .mj-btn-sm[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n }\n\n .mobile-action-buttons[_ngcontent-%COMP%] .btn-label[_ngcontent-%COMP%] {\n font-size: 0.8125rem;\n font-weight: 500;\n }\n\n .role-stats[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 0.5rem;\n margin-bottom: 1rem;\n }\n\n .role-content[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .permissions-preview[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .loading-container[_ngcontent-%COMP%] {\n padding: 3rem 1.5rem;\n }\n}\n\n\n\n@media (max-width: 374px) {\n .action-buttons[_ngcontent-%COMP%] {\n padding: 0.375rem 0.5rem;\n }\n\n .scrollable-content[_ngcontent-%COMP%] {\n padding: 0.5rem;\n }\n\n .role-header[_ngcontent-%COMP%] {\n padding: 0.75rem;\n }\n}"] });
537
732
  };
538
733
  RoleManagementComponent = __decorate([
539
734
  RegisterClass(BaseDashboard, 'RoleManagement')
@@ -541,7 +736,7 @@ RoleManagementComponent = __decorate([
541
736
  export { RoleManagementComponent };
542
737
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RoleManagementComponent, [{
543
738
  type: Component,
544
- args: [{ standalone: false, selector: 'mj-role-management', template: "<!--\n Interior chrome \u2014 <mj-page-header-interior> is the body-level sibling of\n <mj-page-header>. Same slot conventions ([toolbar] / [meta] / [actions]);\n different visual shape (single-row card). See Section 10 of\n explorer-chrome-conventions.\n-->\n<mj-page-header-interior\n AriaLabel=\"Filter roles\"\n Title=\"Roles\"\n Subtitle=\"Define system and custom roles for permissions\">\n <!-- X-of-Y filtered count earns its meta spot per chrome conventions \u00A72.\n Partition badges (system/custom) stay dropped \u2014 they mirror the\n System/Custom filter chips in [toolbar]. -->\n <div meta>\n <mj-stat-badge\n [Count]=\"filteredRoles.length\"\n [Total]=\"roles.length\"\n Label=\"roles\">\n </mj-stat-badge>\n </div>\n <div toolbar>\n <!-- Concise-chrome control bar: search \u00B7 Filter. Type lives behind the one\n Filter button; applied filters show as removable chips below the card.\n On mobile the popover docks as a bottom sheet. -->\n <mj-page-search\n Placeholder=\"Search roles by name or description...\"\n [Value]=\"filters$.value.search\"\n (ValueChange)=\"updateFilter({ search: $event })\">\n </mj-page-search>\n <mj-filter-popover\n Label=\"Filters\"\n Icon=\"fa-solid fa-filter\"\n [ActiveCount]=\"TotalActiveFilterCount\"\n [ShowClearAll]=\"TotalActiveFilterCount > 0\"\n (ClearAllRequested)=\"clearAllAppliedFilters()\">\n <mj-filter-panel\n [Fields]=\"filterFields\"\n [Values]=\"filterValues\"\n (ValuesChange)=\"onFilterPanelChange($event)\"\n (Reset)=\"clearAllAppliedFilters()\">\n </mj-filter-panel>\n </mj-filter-popover>\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"isLoading\" (Clicked)=\"refreshData()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"createNewRole()\" aria-label=\"Add new role\">\n <i class=\"fa-solid fa-plus\"></i> <span class=\"mj-action-label\">Add Role</span>\n </button>\n </div>\n</mj-page-header-interior>\n\n<!-- Scrollable Content Section \u2014 owned by <mj-page-body-interior>. -->\n<mj-page-body-interior>\n <!-- Loading State -->\n @if (isLoading) {\n <div class=\"loading-container\" role=\"status\" aria-live=\"polite\" aria-busy=\"true\">\n <mj-loading text=\"Loading roles...\" size=\"medium\"></mj-loading>\n </div>\n }\n\n <!-- Error State -->\n @if (error && !isLoading) {\n <div class=\"error-container\" role=\"alert\" aria-live=\"assertive\">\n <div class=\"error-content\">\n <i class=\"fa-solid fa-exclamation-triangle error-icon\" aria-hidden=\"true\"></i>\n <p class=\"error-message\">{{ error }}</p>\n <button mjButton variant=\"primary\" (click)=\"loadInitialData()\" aria-label=\"Retry loading roles\">\n <i class=\"fa-solid fa-refresh\" aria-hidden=\"true\"></i>\n Try Again\n </button>\n </div>\n </div>\n }\n\n <!-- Roles List -->\n @if (!isLoading && !error) {\n <div class=\"roles-list\" role=\"list\" aria-label=\"Roles list\">\n @for (role of filteredRoles; track role.ID) {\n <div\n class=\"role-card\"\n [class.expanded]=\"isRoleExpanded(role.ID)\"\n role=\"listitem\"\n [attr.aria-expanded]=\"isRoleExpanded(role.ID)\"\n >\n <div\n class=\"role-header\"\n (click)=\"toggleRoleExpansion(role.ID)\"\n tabindex=\"0\"\n (keydown.enter)=\"toggleRoleExpansion(role.ID)\"\n (keydown.space)=\"toggleRoleExpansion(role.ID); $event.preventDefault()\"\n role=\"button\"\n [attr.aria-label]=\"'Toggle details for ' + role.Name\"\n >\n <div class=\"role-info\">\n <div class=\"role-icon-wrapper\" aria-hidden=\"true\">\n <i [class]=\"'fa-solid ' + getRoleIcon(role)\"></i>\n </div>\n <div class=\"role-details\">\n <h3 class=\"role-name\">{{ role.Name }}</h3>\n <p class=\"role-description\">{{ role.Description || 'No description available' }}</p>\n </div>\n </div>\n\n <div class=\"role-meta\">\n <span class=\"role-type-badge\" [class]=\"getRoleTypeClass(role)\">\n {{ getRoleTypeLabel(role) }}\n </span>\n <div class=\"role-actions\" (click)=\"$event.stopPropagation()\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editRole(role)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + role.Name\"\n [disabled]=\"isSystemRole(role)\"\n >\n <i class=\"fa-solid fa-edit\" aria-hidden=\"true\"></i>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteRole(role)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + role.Name\"\n [disabled]=\"isSystemRole(role)\"\n >\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <button class=\"expand-btn\" aria-label=\"Toggle details\">\n <i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n @if (isRoleExpanded(role.ID)) {\n <div class=\"role-content\">\n <!-- Mobile-only actions bar -->\n <div class=\"mobile-actions-bar\" (click)=\"$event.stopPropagation()\">\n <span class=\"role-type-badge\" [class]=\"getRoleTypeClass(role)\">\n {{ getRoleTypeLabel(role) }}\n </span>\n <div class=\"mobile-action-buttons\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editRole(role)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + role.Name\"\n [disabled]=\"isSystemRole(role)\"\n >\n <i class=\"fa-solid fa-edit\" aria-hidden=\"true\"></i>\n <span class=\"btn-label\">Edit</span>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteRole(role)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + role.Name\"\n [disabled]=\"isSystemRole(role)\"\n >\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n <span class=\"btn-label\">Delete</span>\n </button>\n </div>\n </div>\n\n <div class=\"role-stats\">\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-users\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Users:</span>\n <span class=\"stat-value\">0</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-calendar\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Created:</span>\n <span class=\"stat-value\">{{ role.__mj_CreatedAt | date:'short' }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Updated:</span>\n <span class=\"stat-value\">{{ role.__mj_UpdatedAt | date:'short' }}</span>\n </div>\n </div>\n\n <div class=\"permissions-preview\">\n <h4 class=\"section-title\">\n <i class=\"fa-solid fa-key\" aria-hidden=\"true\"></i>\n Permissions Preview\n </h4>\n <p class=\"permissions-note\">Full permission management available in the Permissions tab</p>\n </div>\n </div>\n }\n </div>\n }\n\n @if (filteredRoles.length === 0) {\n <div class=\"empty-state\" role=\"status\">\n <i class=\"fa-solid fa-user-tag empty-icon\" aria-hidden=\"true\"></i>\n <p class=\"empty-text\">No roles found</p>\n <p class=\"empty-subtext\">Try adjusting your filters or create a new role</p>\n </div>\n }\n </div>\n }\n </mj-page-body-interior>\n\n <!-- Role Create/Edit Dialog -->\n <mj-role-dialog\n [data]=\"roleDialogData\"\n [visible]=\"showRoleDialog\"\n (result)=\"onRoleDialogResult($event)\">\n </mj-role-dialog>\n\n <!-- Delete Confirmation Dialog -->\n @if (showDeleteConfirm && selectedRole) {\n <div\n class=\"modal-backdrop\"\n (click)=\"showDeleteConfirm = false\"\n role=\"presentation\"\n >\n <div\n class=\"modal-dialog\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"delete-dialog-title\"\n aria-describedby=\"delete-dialog-desc\"\n >\n <div class=\"modal-header\">\n <h3 class=\"modal-title\" id=\"delete-dialog-title\">\n <i class=\"fa-solid fa-exclamation-triangle text-danger\" aria-hidden=\"true\"></i>\n Confirm Delete\n </h3>\n <button\n class=\"modal-close\"\n (click)=\"showDeleteConfirm = false\"\n aria-label=\"Close dialog\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"modal-body\" id=\"delete-dialog-desc\">\n <p>Are you sure you want to delete the role <strong>{{ selectedRole.Name }}</strong>?</p>\n <p class=\"text-warning\">\n <i class=\"fa-solid fa-warning\" aria-hidden=\"true\"></i>\n This will affect all users assigned to this role.\n </p>\n </div>\n <div class=\"modal-footer\">\n <!-- Primary action (Delete) on LEFT per MD3 design guide -->\n <button mjButton variant=\"danger\" (click)=\"deleteRole()\" [disabled]=\"isLoading\">\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n Deleting...\n } @else {\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n Delete Role\n }\n </button>\n <button mjButton (click)=\"showDeleteConfirm = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n\n", styles: ["/* =============================================================================\n Shared Admin Patterns - MJ Design System\n Common styles shared across all admin settings components.\n Component-specific CSS files override these values where needed.\n ============================================================================= */\n\n/* -----------------------------------------------------------------------------\n Host & Container\n ----------------------------------------------------------------------------- */\n:host {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n/* -----------------------------------------------------------------------------\n Sticky Header\n ----------------------------------------------------------------------------- */\n.sticky-header {\n flex-shrink: 0;\n background: var(--mj-bg-page);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n z-index: 10;\n}\n\n/* -----------------------------------------------------------------------------\n Scrollable Content\n ----------------------------------------------------------------------------- */\n.scrollable-content {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n padding: 1rem;\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (min-width: 768px) {\n .scrollable-content {\n padding: 1.5rem 2rem;\n }\n}\n\n@media (min-width: 1024px) {\n .scrollable-content {\n padding: 2rem;\n }\n}\n\n@media (min-width: 1440px) {\n .scrollable-content {\n padding: 2rem 2.5rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Action Buttons (In Sticky Header)\n ----------------------------------------------------------------------------- */\n.action-buttons {\n flex-shrink: 0;\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .action-buttons {\n padding: 1rem 1.5rem;\n }\n}\n\n@media (max-width: 639px) {\n .action-buttons {\n justify-content: center;\n flex-wrap: wrap;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Statistics Cards Grid - Static Display (Non-interactive)\n ----------------------------------------------------------------------------- */\n.mj-grid-4 {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 0.5rem;\n padding: 0 1rem 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .mj-grid-4 {\n grid-template-columns: repeat(4, 1fr);\n padding: 0 1.5rem 1rem 1.5rem;\n gap: 0.75rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4 {\n gap: 1rem;\n }\n}\n\n/* Static Card - No hover effects */\n.mj-card {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n padding: 1rem;\n box-shadow: var(--mj-shadow-sm);\n display: flex;\n align-items: center;\n gap: 1rem;\n border: 1px solid var(--mj-border-default);\n cursor: default;\n pointer-events: none;\n}\n\n@media (min-width: 768px) {\n .mj-card {\n padding: 1rem 1.25rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Stat Icon Containers\n ----------------------------------------------------------------------------- */\n.stat-icon {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.25rem;\n flex-shrink: 0;\n}\n\n@media (min-width: 768px) {\n .stat-icon {\n width: 52px;\n height: 52px;\n font-size: 1.375rem;\n }\n}\n\n.stat-icon-total {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n.stat-icon-active {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Stat Content Typography\n ----------------------------------------------------------------------------- */\n.stat-content {\n flex: 1;\n min-width: 0;\n}\n\n.stat-content .stat-value {\n font-size: 1.75rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 768px) {\n .stat-content .stat-value {\n font-size: 2rem;\n }\n}\n\n.stat-content .stat-label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-top: 0.25rem;\n}\n\n@media (min-width: 768px) {\n .stat-content .stat-label {\n font-size: 0.8125rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Filters Section\n ----------------------------------------------------------------------------- */\n.filters-section {\n flex-shrink: 0;\n background: var(--mj-bg-surface-card);\n margin: 0 1rem 0.75rem 1rem;\n padding: 0.75rem;\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .filters-section {\n margin: 0 1.5rem 1rem 1.5rem;\n padding: 1rem 1.25rem;\n }\n}\n\n.filters-row {\n display: flex;\n gap: 1rem;\n align-items: flex-end;\n flex-wrap: wrap;\n}\n\n@media (min-width: 768px) {\n .filters-row {\n gap: 1.5rem;\n }\n}\n\n.mj-filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.mj-filter-label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mj-filter-buttons {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-full);\n padding: 4px;\n gap: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Search Input\n ----------------------------------------------------------------------------- */\n.mj-search {\n position: relative;\n flex: 1;\n min-width: 200px;\n}\n\n@media (min-width: 640px) {\n .mj-search {\n min-width: 280px;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-search {\n min-width: 400px;\n max-width: 600px;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search {\n min-width: 500px;\n max-width: 800px;\n }\n}\n\n.mj-search .mj-search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-secondary);\n font-size: 1rem;\n pointer-events: none;\n transition: color 0.2s ease;\n}\n\n@media (min-width: 1024px) {\n .mj-search .mj-search-icon {\n left: 1.25rem;\n font-size: 1.125rem;\n }\n}\n\n.mj-search .mj-search-input {\n width: 100%;\n padding: 0.875rem 1rem 0.875rem 2.75rem;\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full);\n font-size: 1rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n}\n\n@media (min-width: 1024px) {\n .mj-search .mj-search-input {\n padding: 1rem 1.25rem 1rem 3.25rem;\n font-size: 1.0625rem;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search .mj-search-input {\n padding: 1.125rem 1.5rem 1.125rem 3.5rem;\n }\n}\n\n.mj-search .mj-search-input::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.mj-search .mj-search-input:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.mj-search .mj-search-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.2);\n background: var(--mj-bg-page);\n}\n\n.mj-search:focus-within .mj-search-icon {\n color: var(--mj-brand-primary);\n}\n\n/* -----------------------------------------------------------------------------\n Content Area\n ----------------------------------------------------------------------------- */\n.content-area {\n flex: 1 1 auto;\n overflow: visible;\n position: relative;\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-xl);\n box-shadow: var(--mj-shadow-sm);\n padding: 1rem;\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .content-area {\n padding: 1.5rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Expand Button\n ----------------------------------------------------------------------------- */\n.expand-btn {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1rem;\n cursor: pointer;\n transition: all 0.2s ease;\n border-radius: var(--mj-radius-full);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.expand-btn:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.expand-btn i {\n transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Status Badge\n ----------------------------------------------------------------------------- */\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.status-badge.status-active {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n.status-badge.status-inactive {\n background: var(--mj-color-error-100);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-status-error);\n}\n\n.status-badge i {\n font-size: 0.625rem;\n}\n\n/* -----------------------------------------------------------------------------\n Mobile Actions Bar\n ----------------------------------------------------------------------------- */\n.mobile-actions-bar {\n display: none;\n}\n\n.mobile-action-buttons {\n display: flex;\n gap: 0.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Empty State\n ----------------------------------------------------------------------------- */\n.empty-state {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.empty-state .empty-icon {\n font-size: 4rem;\n color: var(--mj-border-default);\n margin-bottom: 1.5rem;\n}\n\n.empty-state .empty-text {\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.empty-state .empty-subtext {\n font-size: 1rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Loading State\n ----------------------------------------------------------------------------- */\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n/* -----------------------------------------------------------------------------\n Error State\n ----------------------------------------------------------------------------- */\n.error-container {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.error-container .error-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1rem;\n}\n\n.error-container .error-icon {\n font-size: 3.5rem;\n color: var(--mj-status-error);\n margin-bottom: 1rem;\n}\n\n.error-container .error-message {\n font-size: 1.0625rem;\n color: var(--mj-text-primary);\n margin: 0 0 1.5rem 0;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Dialog\n ----------------------------------------------------------------------------- */\n.modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n padding: 1rem;\n animation: fadeIn 0.2s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-dialog {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl);\n box-shadow: var(--mj-shadow-2xl);\n max-width: 500px;\n width: 100%;\n max-height: 90vh;\n overflow: hidden;\n animation: slideUp 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.modal-header .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.modal-header .modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.modal-header .modal-close:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.modal-body {\n padding: 1.5rem;\n}\n\n.modal-body p {\n font-size: 1rem;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n line-height: 1.5;\n}\n\n.modal-body p:last-child {\n margin-bottom: 0;\n}\n\n.modal-footer {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (max-width: 639px) {\n .modal-dialog {\n width: 95%;\n max-height: 85vh;\n }\n\n .modal-header {\n padding: 1rem;\n }\n\n .modal-header .modal-title {\n font-size: 1.125rem;\n }\n\n .modal-body {\n padding: 1rem;\n }\n\n .modal-footer {\n padding: 1rem;\n flex-direction: column;\n }\n\n}\n\n/* The legacy \"MJ Button System\" rule block (.mj-btn / .mj-btn-primary /\n .mj-btn-secondary / .mj-btn-ghost / .mj-btn-danger / .mj-btn-sm /\n .mj-btn-icon-only / .mj-btn-icon-mobile) was removed 2026-05-19. These\n rules pre-dated the mjButton directive standardization (the directive\n uses BEM modifiers like .mj-btn--primary, single-dash names like\n .mj-btn-primary don't match it anyway). The base .mj-btn rule WAS active\n though \u2014 its component-scoped specificity outranked button.scss's global\n directive styles, so admin sub-page buttons rendered pill-shaped + 44px\n regardless of the variant/size requested via mjButton inputs. Removing the\n rule lets every sub-page fall back to the directive's intended look. */\n\n/* -----------------------------------------------------------------------------\n Filter Button (Mobile Only)\n ----------------------------------------------------------------------------- */\n.filter-button {\n display: none;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.25rem;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-full);\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n min-width: 44px;\n}\n\n.filter-button:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.filter-button:active {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.filter-button i {\n font-size: 1rem;\n}\n\n/* -----------------------------------------------------------------------------\n Filter Modal (Mobile Bottom Sheet)\n ----------------------------------------------------------------------------- */\n.filter-modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n padding: 0;\n}\n\n.filter-modal {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl) var(--mj-radius-2xl) 0 0;\n box-shadow: var(--mj-shadow-2xl);\n width: 100%;\n max-height: 80vh;\n overflow: hidden;\n animation: slideUpFromBottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.filter-modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.25rem 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.filter-modal-title i {\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n}\n\n.filter-modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.filter-modal-close:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.filter-modal-body {\n padding: 1.5rem;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n flex: 1;\n}\n\n.filter-modal-footer {\n display: flex;\n gap: 0.75rem;\n padding: 1.25rem 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n.filter-modal-footer button {\n flex: 1;\n margin: 0;\n}\n\n/* Filter Options Container (Inside Modal) */\n.filter-options-container {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.filter-group-label {\n font-size: 0.875rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin: 0;\n}\n\n.filter-group-options {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.filter-option {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.875rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 56px;\n}\n\n.filter-option:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.filter-option.selected {\n background: var(--mj-color-accent-300);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.filter-option input[type=\"checkbox\"],\n.filter-option input[type=\"radio\"] {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.filter-option-label {\n flex: 1;\n font-size: 1rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.filter-option.selected .filter-option-label {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n/* -----------------------------------------------------------------------------\n Utility Classes\n ----------------------------------------------------------------------------- */\n.text-danger {\n color: var(--mj-status-error);\n}\n\n.text-warning {\n color: var(--mj-color-warning-500);\n}\n\n.text-success {\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Animations\n ----------------------------------------------------------------------------- */\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n@keyframes slideDown {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes slideUpFromBottom {\n from {\n transform: translateY(100%);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Focus Indicators\n ----------------------------------------------------------------------------- */\n.mj-search-input:focus-visible,\nbutton:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Reduced Motion\n ----------------------------------------------------------------------------- */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n", "/* =============================================================================\n Role Management Component - Component-Specific Styles\n Shared patterns are in ../shared/styles/_admin-patterns.css\n ============================================================================= */\n\n/* :host already provides flex column / 100% height / overflow hidden via\n _admin-patterns.css. No bespoke container wrapper \u2014 see Section 10 of\n explorer-chrome-conventions for the standardized sub-page layout contract. */\n\n/* Status / type chip group inside <mj-page-header-interior>'s [toolbar] slot. */\n.status-chips {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-2);\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Stats Grid Override - Different gap/padding values\n ----------------------------------------------------------------------------- */\n.mj-grid-4 {\n gap: 0.75rem;\n padding: 0 1rem 1rem 1rem;\n}\n\n@media (min-width: 768px) {\n .mj-grid-4 {\n padding: 0 1.5rem 1.25rem 1.5rem;\n gap: 1rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4 {\n gap: 1.25rem;\n }\n}\n\n/* Stat Card Override - no box-shadow, different 768px padding */\n.mj-card {\n box-shadow: none;\n}\n\n@media (min-width: 768px) {\n .mj-card {\n padding: 1.125rem 1.25rem;\n }\n}\n\n/* Stat Icon Override - Component-specific colors */\n.stat-icon-system {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-700);\n}\n\n.stat-icon-custom {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n.stat-icon-active {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n/* -----------------------------------------------------------------------------\n Roles List\n ----------------------------------------------------------------------------- */\n.roles-list {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n/* -----------------------------------------------------------------------------\n Role Card\n ----------------------------------------------------------------------------- */\n.role-card {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-xl);\n overflow: hidden;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.role-card:hover {\n box-shadow: var(--mj-shadow-md);\n border-color: var(--mj-brand-primary);\n}\n\n.role-card.expanded {\n box-shadow: var(--mj-shadow-lg);\n border-color: var(--mj-brand-primary);\n}\n\n.role-card.expanded .expand-btn i {\n transform: rotate(180deg);\n}\n\n.role-header {\n padding: 1.25rem 1.5rem;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n background: var(--mj-bg-surface);\n transition: background-color 0.2s ease;\n}\n\n.role-header:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.role-info {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex: 1;\n min-width: 0;\n}\n\n.role-icon-wrapper {\n width: 52px;\n height: 52px;\n border-radius: var(--mj-radius-lg);\n background: var(--mj-color-accent-300);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n flex-shrink: 0;\n}\n\n.role-details {\n flex: 1;\n min-width: 0;\n}\n\n.role-details .role-name {\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.25rem 0;\n word-break: break-word;\n overflow-wrap: break-word;\n}\n\n.role-details .role-description {\n font-size: 0.875rem;\n color: var(--mj-text-secondary);\n margin: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@media (max-width: 639px) {\n .role-details .role-description {\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n}\n\n.role-meta {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Role Type Badge\n ----------------------------------------------------------------------------- */\n.role-type-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.role-type-badge.badge-system {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-800);\n border: 1px solid var(--mj-color-warning-500);\n}\n\n.role-type-badge.badge-custom {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Role Actions\n ----------------------------------------------------------------------------- */\n.role-actions {\n display: flex;\n gap: 0.375rem;\n}\n\n/* Expand Button Override - different transition easing */\n.expand-btn i {\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Role Content (Expanded)\n ----------------------------------------------------------------------------- */\n.role-content {\n padding: 1.5rem;\n background: var(--mj-bg-surface);\n border-top: 1px solid var(--mj-border-default);\n animation: slideDown 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.role-stats {\n display: flex;\n gap: 2rem;\n margin-bottom: 1.5rem;\n flex-wrap: wrap;\n}\n\n.role-stats .stat-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.9375rem;\n padding: 0.5rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n}\n\n.role-stats .stat-item i {\n color: var(--mj-brand-primary);\n font-size: 1rem;\n}\n\n.role-stats .stat-item .stat-label {\n color: var(--mj-text-secondary);\n}\n\n.role-stats .stat-item .stat-value {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n/* -----------------------------------------------------------------------------\n Permissions Preview\n ----------------------------------------------------------------------------- */\n.permissions-preview {\n padding: 1.25rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n.permissions-preview .section-title {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.permissions-preview .section-title i {\n color: var(--mj-brand-primary);\n font-size: 1.125rem;\n}\n\n.permissions-preview .permissions-note {\n font-size: 0.9375rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n/* Modal Body Override - line-height and warning callout */\n.modal-body p {\n line-height: 1.6;\n}\n\n.modal-body .text-warning {\n color: var(--mj-color-warning-500);\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n background: var(--mj-color-warning-50);\n border-radius: var(--mj-radius-md);\n font-weight: 500;\n}\n\n/* Modal Backdrop Override - uses top/left/right/bottom instead of inset */\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Large Desktop (1440px+)\n ----------------------------------------------------------------------------- */\n@media (min-width: 1440px) {\n .role-management-container {\n margin: 0 auto;\n }\n\n .action-buttons {\n padding: 1rem 2rem;\n }\n\n .mj-grid-4 {\n padding: 0 2rem 1.25rem 2rem;\n gap: 1.5rem;\n }\n\n}\n\n/* -----------------------------------------------------------------------------\n Mobile (< 640px)\n ----------------------------------------------------------------------------- */\n@media (max-width: 639px) {\n .mj-grid-4 {\n display: none !important;\n }\n\n .scrollable-content {\n padding: 0.75rem;\n }\n\n .modal-backdrop {\n padding: 0.5rem;\n }\n\n .modal-dialog {\n border-radius: var(--mj-radius-xl);\n max-height: 95vh;\n }\n\n .roles-list {\n gap: 0.75rem;\n }\n\n .role-card {\n border-radius: var(--mj-radius-lg);\n overflow: hidden;\n }\n\n .role-header {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 1rem;\n }\n\n .role-info {\n flex: 1;\n min-width: 0;\n flex-direction: row;\n align-items: flex-start;\n gap: 0.875rem;\n }\n\n .role-icon-wrapper {\n width: 40px;\n height: 40px;\n font-size: 1rem;\n flex-shrink: 0;\n }\n\n .role-details {\n flex: 1;\n min-width: 0;\n }\n\n .role-details .role-name {\n font-size: 1rem;\n line-height: 1.3;\n margin-bottom: 0.125rem;\n }\n\n .role-details .role-description {\n font-size: 0.8125rem;\n -webkit-line-clamp: 1;\n line-clamp: 1;\n }\n\n .role-meta {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-end;\n padding-top: 0;\n border-top: none;\n width: auto;\n }\n\n .role-meta .role-type-badge {\n display: none;\n }\n\n .role-meta .role-actions {\n display: none;\n }\n\n .expand-btn {\n flex-shrink: 0;\n min-width: 40px;\n min-height: 40px;\n padding: 0.5rem;\n }\n\n .expand-btn i {\n font-size: 1rem;\n }\n\n .mobile-actions-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.875rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-lg);\n margin-bottom: 1rem;\n }\n\n .mobile-actions-bar .role-type-badge {\n padding: 0.375rem 0.75rem;\n font-size: 0.6875rem;\n }\n\n .mobile-action-buttons {\n display: flex;\n gap: 0.5rem;\n }\n\n .mobile-action-buttons .mj-btn-sm {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.5rem 0.875rem;\n min-height: 36px;\n }\n\n .mobile-action-buttons .mj-btn-sm i {\n font-size: 0.875rem;\n }\n\n .mobile-action-buttons .btn-label {\n font-size: 0.8125rem;\n font-weight: 500;\n }\n\n .role-stats {\n flex-direction: column;\n gap: 0.5rem;\n margin-bottom: 1rem;\n }\n\n .role-content {\n padding: 1rem;\n }\n\n .permissions-preview {\n padding: 1rem;\n }\n\n .empty-state {\n padding: 3rem 1.5rem;\n }\n\n .empty-state .empty-icon {\n font-size: 3rem;\n }\n\n .loading-container {\n padding: 3rem 1.5rem;\n }\n}\n\n/* Small Mobile (< 375px) */\n@media (max-width: 374px) {\n .action-buttons {\n padding: 0.375rem 0.5rem;\n }\n\n .scrollable-content {\n padding: 0.5rem;\n }\n\n .role-header {\n padding: 0.75rem;\n }\n}\n"] }]
739
+ args: [{ standalone: false, selector: 'mj-role-management', template: "<!--\n Interior chrome \u2014 <mj-page-header-interior> is the body-level sibling of\n <mj-page-header>. Same slot conventions ([toolbar] / [meta] / [actions]);\n different visual shape (single-row card). See Section 10 of\n explorer-chrome-conventions.\n-->\n<mj-page-header-interior\n AriaLabel=\"Filter roles\"\n Title=\"Roles\"\n Subtitle=\"Define system and custom roles for permissions\">\n <!-- X-of-Y filtered count earns its meta spot per chrome conventions \u00A72.\n Partition badges (system/custom) stay dropped \u2014 they mirror the\n System/Custom filter chips in [toolbar]. -->\n <div meta>\n <mj-stat-badge\n [Count]=\"filteredRoles.length\"\n [Total]=\"roles.length\"\n Label=\"roles\">\n </mj-stat-badge>\n </div>\n <div toolbar>\n <!-- Concise-chrome control bar: search \u00B7 Filter. Type lives behind the one\n Filter button; applied filters show as removable chips below the card.\n On mobile the popover docks as a bottom sheet. -->\n <mj-page-search\n Placeholder=\"Search roles by name or description...\"\n [Value]=\"filters$.value.search\"\n (ValueChange)=\"updateFilter({ search: $event })\">\n </mj-page-search>\n <mj-filter-popover\n Label=\"Filters\"\n Icon=\"fa-solid fa-filter\"\n [ActiveCount]=\"TotalActiveFilterCount\"\n [ShowClearAll]=\"TotalActiveFilterCount > 0\"\n (ClearAllRequested)=\"clearAllAppliedFilters()\">\n <mj-filter-panel\n [Fields]=\"filterFields\"\n [Values]=\"filterValues\"\n (ValuesChange)=\"onFilterPanelChange($event)\"\n (Reset)=\"clearAllAppliedFilters()\">\n </mj-filter-panel>\n </mj-filter-popover>\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"isLoading\" (Clicked)=\"refreshData()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"createNewRole()\" aria-label=\"Add new role\">\n <i class=\"fa-solid fa-plus\"></i> <span class=\"mj-action-label\">Add Role</span>\n </button>\n </div>\n</mj-page-header-interior>\n\n<!-- Scrollable Content Section \u2014 owned by <mj-page-body-interior>. -->\n<mj-page-body-interior>\n <!-- Loading State -->\n @if (isLoading) {\n <div class=\"loading-container\" role=\"status\" aria-live=\"polite\" aria-busy=\"true\">\n <mj-loading text=\"Loading roles...\" size=\"medium\"></mj-loading>\n </div>\n }\n\n <!-- Error State -->\n @if (error && !isLoading) {\n <div class=\"error-container\" role=\"alert\" aria-live=\"assertive\">\n <div class=\"error-content\">\n <i class=\"fa-solid fa-exclamation-triangle error-icon\" aria-hidden=\"true\"></i>\n <p class=\"error-message\">{{ error }}</p>\n <button mjButton variant=\"primary\" (click)=\"loadInitialData()\" aria-label=\"Retry loading roles\">\n <i class=\"fa-solid fa-refresh\" aria-hidden=\"true\"></i>\n Try Again\n </button>\n </div>\n </div>\n }\n\n <!-- Roles List -->\n @if (!isLoading && !error) {\n <div class=\"roles-list\" role=\"list\" aria-label=\"Roles list\">\n @for (role of filteredRoles; track role.ID) {\n <div\n class=\"role-card\"\n [class.expanded]=\"isRoleExpanded(role.ID)\"\n role=\"listitem\"\n [attr.aria-expanded]=\"isRoleExpanded(role.ID)\"\n >\n <div\n class=\"role-header\"\n (click)=\"toggleRoleExpansion(role.ID)\"\n tabindex=\"0\"\n (keydown.enter)=\"toggleRoleExpansion(role.ID)\"\n (keydown.space)=\"toggleRoleExpansion(role.ID); $event.preventDefault()\"\n role=\"button\"\n [attr.aria-label]=\"'Toggle details for ' + role.Name\"\n >\n <div class=\"role-info\">\n <div class=\"role-icon-wrapper\" aria-hidden=\"true\">\n <i [class]=\"'fa-solid ' + getRoleIcon(role)\"></i>\n </div>\n <div class=\"role-details\">\n <h3 class=\"role-name\">{{ role.Name }}</h3>\n <p class=\"role-description\">{{ role.Description || 'No description available' }}</p>\n </div>\n </div>\n\n <div class=\"role-meta\">\n <span class=\"role-type-badge\" [class]=\"getRoleTypeClass(role)\">\n {{ getRoleTypeLabel(role) }}\n </span>\n <div class=\"role-actions\" (click)=\"$event.stopPropagation()\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editRole(role)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + role.Name\"\n [disabled]=\"isSystemRole(role)\"\n >\n <i class=\"fa-solid fa-edit\" aria-hidden=\"true\"></i>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteRole(role)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + role.Name\"\n [disabled]=\"isSystemRole(role)\"\n >\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <button class=\"expand-btn\" aria-label=\"Toggle details\">\n <i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n @if (isRoleExpanded(role.ID)) {\n <div class=\"role-content\">\n <!-- Mobile-only actions bar -->\n <div class=\"mobile-actions-bar\" (click)=\"$event.stopPropagation()\">\n <span class=\"role-type-badge\" [class]=\"getRoleTypeClass(role)\">\n {{ getRoleTypeLabel(role) }}\n </span>\n <div class=\"mobile-action-buttons\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editRole(role)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + role.Name\"\n [disabled]=\"isSystemRole(role)\"\n >\n <i class=\"fa-solid fa-edit\" aria-hidden=\"true\"></i>\n <span class=\"btn-label\">Edit</span>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteRole(role)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + role.Name\"\n [disabled]=\"isSystemRole(role)\"\n >\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n <span class=\"btn-label\">Delete</span>\n </button>\n </div>\n </div>\n\n <div class=\"role-stats\">\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-users\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Users:</span>\n <span class=\"stat-value\">0</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-calendar\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Created:</span>\n <span class=\"stat-value\">{{ role.__mj_CreatedAt | date:'short' }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Updated:</span>\n <span class=\"stat-value\">{{ role.__mj_UpdatedAt | date:'short' }}</span>\n </div>\n </div>\n\n <div class=\"permissions-preview\">\n <h4 class=\"section-title\">\n <i class=\"fa-solid fa-key\" aria-hidden=\"true\"></i>\n Permissions Preview\n </h4>\n <p class=\"permissions-note\">Full permission management available in the Permissions tab</p>\n </div>\n </div>\n }\n </div>\n }\n\n @if (filteredRoles.length === 0) {\n <mj-empty-state\n [Variant]=\"IsListNarrowed ? 'no-results' : 'empty'\"\n Icon=\"fa-solid fa-user-tag\"\n Title=\"No roles found\"\n Message=\"Try adjusting your filters or create a new role\"\n [ActionText]=\"IsListNarrowed ? 'Reset filters' : ''\"\n ActionIcon=\"fa-solid fa-rotate-left\"\n (Action)=\"resetAllFiltersAndSearch()\" />\n }\n </div>\n }\n </mj-page-body-interior>\n\n <!-- Role Create/Edit Dialog -->\n <mj-role-dialog\n [data]=\"roleDialogData\"\n [visible]=\"showRoleDialog\"\n (result)=\"onRoleDialogResult($event)\">\n </mj-role-dialog>\n\n <!-- Delete Confirmation Dialog -->\n @if (showDeleteConfirm && selectedRole) {\n <div\n class=\"modal-backdrop\"\n (click)=\"showDeleteConfirm = false\"\n role=\"presentation\"\n >\n <div\n class=\"modal-dialog\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"delete-dialog-title\"\n aria-describedby=\"delete-dialog-desc\"\n >\n <div class=\"modal-header\">\n <h3 class=\"modal-title\" id=\"delete-dialog-title\">\n <i class=\"fa-solid fa-exclamation-triangle text-danger\" aria-hidden=\"true\"></i>\n Confirm Delete\n </h3>\n <button\n class=\"modal-close\"\n (click)=\"showDeleteConfirm = false\"\n aria-label=\"Close dialog\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"modal-body\" id=\"delete-dialog-desc\">\n <p>Are you sure you want to delete the role <strong>{{ selectedRole.Name }}</strong>?</p>\n <mj-alert Variant=\"warning\" Message=\"This will affect all users assigned to this role.\" />\n </div>\n <div class=\"modal-footer\">\n <!-- Primary action (Delete) on LEFT per MD3 design guide -->\n <button mjButton variant=\"danger\" (click)=\"deleteRole()\" [disabled]=\"isLoading\">\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n Deleting...\n } @else {\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n Delete Role\n }\n </button>\n <button mjButton (click)=\"showDeleteConfirm = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n\n", styles: ["/* =============================================================================\n Shared Admin Patterns - MJ Design System\n Common styles shared across all admin settings components.\n Component-specific CSS files override these values where needed.\n ============================================================================= */\n\n/* -----------------------------------------------------------------------------\n Host & Container\n ----------------------------------------------------------------------------- */\n:host {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n/* -----------------------------------------------------------------------------\n Sticky Header\n ----------------------------------------------------------------------------- */\n.sticky-header {\n flex-shrink: 0;\n background: var(--mj-bg-page);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n z-index: 10;\n}\n\n/* -----------------------------------------------------------------------------\n Scrollable Content\n ----------------------------------------------------------------------------- */\n.scrollable-content {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n padding: 1rem;\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (min-width: 768px) {\n .scrollable-content {\n padding: 1.5rem 2rem;\n }\n}\n\n@media (min-width: 1024px) {\n .scrollable-content {\n padding: 2rem;\n }\n}\n\n@media (min-width: 1440px) {\n .scrollable-content {\n padding: 2rem 2.5rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Action Buttons (In Sticky Header)\n ----------------------------------------------------------------------------- */\n.action-buttons {\n flex-shrink: 0;\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .action-buttons {\n padding: 1rem 1.5rem;\n }\n}\n\n@media (max-width: 639px) {\n .action-buttons {\n justify-content: center;\n flex-wrap: wrap;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Statistics Cards Grid - Static Display (Non-interactive)\n ----------------------------------------------------------------------------- */\n.mj-grid-4 {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 0.5rem;\n padding: 0 1rem 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .mj-grid-4 {\n grid-template-columns: repeat(4, 1fr);\n padding: 0 1.5rem 1rem 1.5rem;\n gap: 0.75rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4 {\n gap: 1rem;\n }\n}\n\n/* Static Card - No hover effects */\n.mj-card {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n padding: 1rem;\n box-shadow: var(--mj-shadow-sm);\n display: flex;\n align-items: center;\n gap: 1rem;\n border: 1px solid var(--mj-border-default);\n cursor: default;\n pointer-events: none;\n}\n\n@media (min-width: 768px) {\n .mj-card {\n padding: 1rem 1.25rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Stat Icon Containers\n ----------------------------------------------------------------------------- */\n.stat-icon {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.25rem;\n flex-shrink: 0;\n}\n\n@media (min-width: 768px) {\n .stat-icon {\n width: 52px;\n height: 52px;\n font-size: 1.375rem;\n }\n}\n\n.stat-icon-total {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n.stat-icon-active {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Stat Content Typography\n ----------------------------------------------------------------------------- */\n.stat-content {\n flex: 1;\n min-width: 0;\n}\n\n.stat-content .stat-value {\n font-size: 1.75rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 768px) {\n .stat-content .stat-value {\n font-size: 2rem;\n }\n}\n\n.stat-content .stat-label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-top: 0.25rem;\n}\n\n@media (min-width: 768px) {\n .stat-content .stat-label {\n font-size: 0.8125rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Filters Section\n ----------------------------------------------------------------------------- */\n.filters-section {\n flex-shrink: 0;\n background: var(--mj-bg-surface-card);\n margin: 0 1rem 0.75rem 1rem;\n padding: 0.75rem;\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .filters-section {\n margin: 0 1.5rem 1rem 1.5rem;\n padding: 1rem 1.25rem;\n }\n}\n\n.filters-row {\n display: flex;\n gap: 1rem;\n align-items: flex-end;\n flex-wrap: wrap;\n}\n\n@media (min-width: 768px) {\n .filters-row {\n gap: 1.5rem;\n }\n}\n\n.mj-filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.mj-filter-label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mj-filter-buttons {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-full);\n padding: 4px;\n gap: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Search Input\n ----------------------------------------------------------------------------- */\n.mj-search {\n position: relative;\n flex: 1;\n min-width: 200px;\n}\n\n@media (min-width: 640px) {\n .mj-search {\n min-width: 280px;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-search {\n min-width: 400px;\n max-width: 600px;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search {\n min-width: 500px;\n max-width: 800px;\n }\n}\n\n.mj-search .mj-search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-secondary);\n font-size: 1rem;\n pointer-events: none;\n transition: color 0.2s ease;\n}\n\n@media (min-width: 1024px) {\n .mj-search .mj-search-icon {\n left: 1.25rem;\n font-size: 1.125rem;\n }\n}\n\n.mj-search .mj-search-input {\n width: 100%;\n padding: 0.875rem 1rem 0.875rem 2.75rem;\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full);\n font-size: 1rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n}\n\n@media (min-width: 1024px) {\n .mj-search .mj-search-input {\n padding: 1rem 1.25rem 1rem 3.25rem;\n font-size: 1.0625rem;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search .mj-search-input {\n padding: 1.125rem 1.5rem 1.125rem 3.5rem;\n }\n}\n\n.mj-search .mj-search-input::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.mj-search .mj-search-input:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.mj-search .mj-search-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.2);\n background: var(--mj-bg-page);\n}\n\n.mj-search:focus-within .mj-search-icon {\n color: var(--mj-brand-primary);\n}\n\n/* -----------------------------------------------------------------------------\n Content Area\n ----------------------------------------------------------------------------- */\n.content-area {\n flex: 1 1 auto;\n overflow: visible;\n position: relative;\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-xl);\n box-shadow: var(--mj-shadow-sm);\n padding: 1rem;\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .content-area {\n padding: 1.5rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Expand Button\n ----------------------------------------------------------------------------- */\n.expand-btn {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1rem;\n cursor: pointer;\n transition: all 0.2s ease;\n border-radius: var(--mj-radius-full);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.expand-btn:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.expand-btn i {\n transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Status Badge\n ----------------------------------------------------------------------------- */\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.status-badge.status-active {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n.status-badge.status-inactive {\n background: var(--mj-color-error-100);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-status-error);\n}\n\n.status-badge i {\n font-size: 0.625rem;\n}\n\n/* -----------------------------------------------------------------------------\n Mobile Actions Bar\n ----------------------------------------------------------------------------- */\n.mobile-actions-bar {\n display: none;\n}\n\n.mobile-action-buttons {\n display: flex;\n gap: 0.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Empty State\n ----------------------------------------------------------------------------- */\n.empty-state {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.empty-state .empty-icon {\n font-size: 4rem;\n color: var(--mj-border-default);\n margin-bottom: 1.5rem;\n}\n\n.empty-state .empty-text {\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.empty-state .empty-subtext {\n font-size: 1rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Loading State\n ----------------------------------------------------------------------------- */\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n/* -----------------------------------------------------------------------------\n Error State\n ----------------------------------------------------------------------------- */\n.error-container {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.error-container .error-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1rem;\n}\n\n.error-container .error-icon {\n font-size: 3.5rem;\n color: var(--mj-status-error);\n margin-bottom: 1rem;\n}\n\n.error-container .error-message {\n font-size: 1.0625rem;\n color: var(--mj-text-primary);\n margin: 0 0 1.5rem 0;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Dialog\n ----------------------------------------------------------------------------- */\n.modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n padding: 1rem;\n animation: fadeIn 0.2s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-dialog {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl);\n box-shadow: var(--mj-shadow-2xl);\n max-width: 500px;\n width: 100%;\n max-height: 90vh;\n overflow: hidden;\n animation: slideUp 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.modal-header .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.modal-header .modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.modal-header .modal-close:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.modal-body {\n padding: 1.5rem;\n}\n\n.modal-body p {\n font-size: 1rem;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n line-height: 1.5;\n}\n\n.modal-body p:last-child {\n margin-bottom: 0;\n}\n\n.modal-footer {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (max-width: 639px) {\n .modal-dialog {\n width: 95%;\n max-height: 85vh;\n }\n\n .modal-header {\n padding: 1rem;\n }\n\n .modal-header .modal-title {\n font-size: 1.125rem;\n }\n\n .modal-body {\n padding: 1rem;\n }\n\n .modal-footer {\n padding: 1rem;\n flex-direction: column;\n }\n\n}\n\n/* The legacy \"MJ Button System\" rule block (.mj-btn / .mj-btn-primary /\n .mj-btn-secondary / .mj-btn-ghost / .mj-btn-danger / .mj-btn-sm /\n .mj-btn-icon-only / .mj-btn-icon-mobile) was removed 2026-05-19. These\n rules pre-dated the mjButton directive standardization (the directive\n uses BEM modifiers like .mj-btn--primary, single-dash names like\n .mj-btn-primary don't match it anyway). The base .mj-btn rule WAS active\n though \u2014 its component-scoped specificity outranked button.scss's global\n directive styles, so admin sub-page buttons rendered pill-shaped + 44px\n regardless of the variant/size requested via mjButton inputs. Removing the\n rule lets every sub-page fall back to the directive's intended look. */\n\n/* -----------------------------------------------------------------------------\n Filter Button (Mobile Only)\n ----------------------------------------------------------------------------- */\n.filter-button {\n display: none;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.25rem;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-full);\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n min-width: 44px;\n}\n\n.filter-button:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.filter-button:active {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.filter-button i {\n font-size: 1rem;\n}\n\n/* -----------------------------------------------------------------------------\n Filter Modal (Mobile Bottom Sheet)\n ----------------------------------------------------------------------------- */\n.filter-modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n padding: 0;\n}\n\n.filter-modal {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl) var(--mj-radius-2xl) 0 0;\n box-shadow: var(--mj-shadow-2xl);\n width: 100%;\n max-height: 80vh;\n overflow: hidden;\n animation: slideUpFromBottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.filter-modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.25rem 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.filter-modal-title i {\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n}\n\n.filter-modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.filter-modal-close:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.filter-modal-body {\n padding: 1.5rem;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n flex: 1;\n}\n\n.filter-modal-footer {\n display: flex;\n gap: 0.75rem;\n padding: 1.25rem 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n.filter-modal-footer button {\n flex: 1;\n margin: 0;\n}\n\n/* Filter Options Container (Inside Modal) */\n.filter-options-container {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.filter-group-label {\n font-size: 0.875rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin: 0;\n}\n\n.filter-group-options {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.filter-option {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.875rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 56px;\n}\n\n.filter-option:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.filter-option.selected {\n background: var(--mj-color-accent-300);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.filter-option input[type=\"checkbox\"],\n.filter-option input[type=\"radio\"] {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.filter-option-label {\n flex: 1;\n font-size: 1rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.filter-option.selected .filter-option-label {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n/* -----------------------------------------------------------------------------\n Utility Classes\n ----------------------------------------------------------------------------- */\n.text-danger {\n color: var(--mj-status-error);\n}\n\n.text-warning {\n color: var(--mj-color-warning-500);\n}\n\n.text-success {\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Animations\n ----------------------------------------------------------------------------- */\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n@keyframes slideDown {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes slideUpFromBottom {\n from {\n transform: translateY(100%);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Focus Indicators\n ----------------------------------------------------------------------------- */\n.mj-search-input:focus-visible,\nbutton:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Reduced Motion\n ----------------------------------------------------------------------------- */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n", "/* =============================================================================\n Role Management Component - Component-Specific Styles\n Shared patterns are in ../shared/styles/_admin-patterns.css\n ============================================================================= */\n\n/* :host already provides flex column / 100% height / overflow hidden via\n _admin-patterns.css. No bespoke container wrapper \u2014 see Section 10 of\n explorer-chrome-conventions for the standardized sub-page layout contract. */\n\n/* Status / type chip group inside <mj-page-header-interior>'s [toolbar] slot. */\n.status-chips {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-2);\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Stats Grid Override - Different gap/padding values\n ----------------------------------------------------------------------------- */\n.mj-grid-4 {\n gap: 0.75rem;\n padding: 0 1rem 1rem 1rem;\n}\n\n@media (min-width: 768px) {\n .mj-grid-4 {\n padding: 0 1.5rem 1.25rem 1.5rem;\n gap: 1rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4 {\n gap: 1.25rem;\n }\n}\n\n/* Stat Card Override - no box-shadow, different 768px padding */\n.mj-card {\n box-shadow: none;\n}\n\n@media (min-width: 768px) {\n .mj-card {\n padding: 1.125rem 1.25rem;\n }\n}\n\n/* Stat Icon Override - Component-specific colors */\n.stat-icon-system {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-700);\n}\n\n.stat-icon-custom {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n.stat-icon-active {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n/* -----------------------------------------------------------------------------\n Roles List\n ----------------------------------------------------------------------------- */\n.roles-list {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n/* -----------------------------------------------------------------------------\n Role Card\n ----------------------------------------------------------------------------- */\n.role-card {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-xl);\n overflow: hidden;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.role-card:hover {\n box-shadow: var(--mj-shadow-md);\n border-color: var(--mj-brand-primary);\n}\n\n.role-card.expanded {\n box-shadow: var(--mj-shadow-lg);\n border-color: var(--mj-brand-primary);\n}\n\n.role-card.expanded .expand-btn i {\n transform: rotate(180deg);\n}\n\n.role-header {\n padding: 1.25rem 1.5rem;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n background: var(--mj-bg-surface);\n transition: background-color 0.2s ease;\n}\n\n.role-header:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.role-info {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex: 1;\n min-width: 0;\n}\n\n.role-icon-wrapper {\n width: 52px;\n height: 52px;\n border-radius: var(--mj-radius-lg);\n background: var(--mj-color-accent-300);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n flex-shrink: 0;\n}\n\n.role-details {\n flex: 1;\n min-width: 0;\n}\n\n.role-details .role-name {\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.25rem 0;\n word-break: break-word;\n overflow-wrap: break-word;\n}\n\n.role-details .role-description {\n font-size: 0.875rem;\n color: var(--mj-text-secondary);\n margin: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@media (max-width: 639px) {\n .role-details .role-description {\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n}\n\n.role-meta {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Role Type Badge\n ----------------------------------------------------------------------------- */\n.role-type-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.role-type-badge.badge-system {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-800);\n border: 1px solid var(--mj-color-warning-500);\n}\n\n.role-type-badge.badge-custom {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Role Actions\n ----------------------------------------------------------------------------- */\n.role-actions {\n display: flex;\n gap: 0.375rem;\n}\n\n/* Expand Button Override - different transition easing */\n.expand-btn i {\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Role Content (Expanded)\n ----------------------------------------------------------------------------- */\n.role-content {\n padding: 1.5rem;\n background: var(--mj-bg-surface);\n border-top: 1px solid var(--mj-border-default);\n animation: slideDown 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.role-stats {\n display: flex;\n gap: 2rem;\n margin-bottom: 1.5rem;\n flex-wrap: wrap;\n}\n\n.role-stats .stat-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.9375rem;\n padding: 0.5rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n}\n\n.role-stats .stat-item i {\n color: var(--mj-brand-primary);\n font-size: 1rem;\n}\n\n.role-stats .stat-item .stat-label {\n color: var(--mj-text-secondary);\n}\n\n.role-stats .stat-item .stat-value {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n/* -----------------------------------------------------------------------------\n Permissions Preview\n ----------------------------------------------------------------------------- */\n.permissions-preview {\n padding: 1.25rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n.permissions-preview .section-title {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.permissions-preview .section-title i {\n color: var(--mj-brand-primary);\n font-size: 1.125rem;\n}\n\n.permissions-preview .permissions-note {\n font-size: 0.9375rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n/* Modal Body Override - line-height and warning callout */\n.modal-body p {\n line-height: 1.6;\n}\n\n/* Modal Backdrop Override - uses top/left/right/bottom instead of inset */\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Large Desktop (1440px+)\n ----------------------------------------------------------------------------- */\n@media (min-width: 1440px) {\n .role-management-container {\n margin: 0 auto;\n }\n\n .action-buttons {\n padding: 1rem 2rem;\n }\n\n .mj-grid-4 {\n padding: 0 2rem 1.25rem 2rem;\n gap: 1.5rem;\n }\n\n}\n\n/* -----------------------------------------------------------------------------\n Mobile (< 640px)\n ----------------------------------------------------------------------------- */\n@media (max-width: 639px) {\n .mj-grid-4 {\n display: none !important;\n }\n\n .scrollable-content {\n padding: 0.75rem;\n }\n\n .modal-backdrop {\n padding: 0.5rem;\n }\n\n .modal-dialog {\n border-radius: var(--mj-radius-xl);\n max-height: 95vh;\n }\n\n .roles-list {\n gap: 0.75rem;\n }\n\n .role-card {\n border-radius: var(--mj-radius-lg);\n overflow: hidden;\n }\n\n .role-header {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 1rem;\n }\n\n .role-info {\n flex: 1;\n min-width: 0;\n flex-direction: row;\n align-items: flex-start;\n gap: 0.875rem;\n }\n\n .role-icon-wrapper {\n width: 40px;\n height: 40px;\n font-size: 1rem;\n flex-shrink: 0;\n }\n\n .role-details {\n flex: 1;\n min-width: 0;\n }\n\n .role-details .role-name {\n font-size: 1rem;\n line-height: 1.3;\n margin-bottom: 0.125rem;\n }\n\n .role-details .role-description {\n font-size: 0.8125rem;\n -webkit-line-clamp: 1;\n line-clamp: 1;\n }\n\n .role-meta {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-end;\n padding-top: 0;\n border-top: none;\n width: auto;\n }\n\n .role-meta .role-type-badge {\n display: none;\n }\n\n .role-meta .role-actions {\n display: none;\n }\n\n .expand-btn {\n flex-shrink: 0;\n min-width: 40px;\n min-height: 40px;\n padding: 0.5rem;\n }\n\n .expand-btn i {\n font-size: 1rem;\n }\n\n .mobile-actions-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.875rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-lg);\n margin-bottom: 1rem;\n }\n\n .mobile-actions-bar .role-type-badge {\n padding: 0.375rem 0.75rem;\n font-size: 0.6875rem;\n }\n\n .mobile-action-buttons {\n display: flex;\n gap: 0.5rem;\n }\n\n .mobile-action-buttons .mj-btn-sm {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.5rem 0.875rem;\n min-height: 36px;\n }\n\n .mobile-action-buttons .mj-btn-sm i {\n font-size: 0.875rem;\n }\n\n .mobile-action-buttons .btn-label {\n font-size: 0.8125rem;\n font-weight: 500;\n }\n\n .role-stats {\n flex-direction: column;\n gap: 0.5rem;\n margin-bottom: 1rem;\n }\n\n .role-content {\n padding: 1rem;\n }\n\n .permissions-preview {\n padding: 1rem;\n }\n\n .loading-container {\n padding: 3rem 1.5rem;\n }\n}\n\n/* Small Mobile (< 375px) */\n@media (max-width: 374px) {\n .action-buttons {\n padding: 0.375rem 0.5rem;\n }\n\n .scrollable-content {\n padding: 0.5rem;\n }\n\n .role-header {\n padding: 0.75rem;\n }\n}\n"] }]
545
740
  }], () => [{ type: i0.ChangeDetectorRef }, { type: i0.NgZone }], null); })();
546
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RoleManagementComponent, { className: "RoleManagementComponent", filePath: "src/lib/role-management/role-management.component.ts", lineNumber: 30 }); })();
741
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RoleManagementComponent, { className: "RoleManagementComponent", filePath: "src/lib/role-management/role-management.component.ts", lineNumber: 37 }); })();
547
742
  //# sourceMappingURL=role-management.component.js.map