@flusys/ng-iam 6.2.1 → 6.3.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 (19) hide show
  1. package/fesm2022/{flusys-ng-iam-action-form-page.component-CqrxA8bY.mjs → flusys-ng-iam-action-form-page.component-Cz7DY6JM.mjs} +16 -24
  2. package/fesm2022/flusys-ng-iam-action-form-page.component-Cz7DY6JM.mjs.map +1 -0
  3. package/fesm2022/{flusys-ng-iam-action-list-page.component-P7YcQOur.mjs → flusys-ng-iam-action-list-page.component-D-Lhs8gW.mjs} +2 -2
  4. package/fesm2022/{flusys-ng-iam-action-list-page.component-P7YcQOur.mjs.map → flusys-ng-iam-action-list-page.component-D-Lhs8gW.mjs.map} +1 -1
  5. package/fesm2022/{flusys-ng-iam-flusys-ng-iam-D397Jg30.mjs → flusys-ng-iam-flusys-ng-iam-DW60PO5q.mjs} +287 -62
  6. package/fesm2022/flusys-ng-iam-flusys-ng-iam-DW60PO5q.mjs.map +1 -0
  7. package/fesm2022/flusys-ng-iam-permission-page.component-C1XE1Zes.mjs +203 -0
  8. package/fesm2022/flusys-ng-iam-permission-page.component-C1XE1Zes.mjs.map +1 -0
  9. package/fesm2022/{flusys-ng-iam-role-form-page.component-Cw958yl1.mjs → flusys-ng-iam-role-form-page.component-DMDQjumX.mjs} +2 -2
  10. package/fesm2022/{flusys-ng-iam-role-form-page.component-Cw958yl1.mjs.map → flusys-ng-iam-role-form-page.component-DMDQjumX.mjs.map} +1 -1
  11. package/fesm2022/{flusys-ng-iam-role-list-page.component-Cu3NxRWX.mjs → flusys-ng-iam-role-list-page.component-CUzEIuzS.mjs} +2 -2
  12. package/fesm2022/{flusys-ng-iam-role-list-page.component-Cu3NxRWX.mjs.map → flusys-ng-iam-role-list-page.component-CUzEIuzS.mjs.map} +1 -1
  13. package/fesm2022/flusys-ng-iam.mjs +1 -1
  14. package/package.json +5 -5
  15. package/types/flusys-ng-iam.d.ts +28 -6
  16. package/fesm2022/flusys-ng-iam-action-form-page.component-CqrxA8bY.mjs.map +0 -1
  17. package/fesm2022/flusys-ng-iam-flusys-ng-iam-D397Jg30.mjs.map +0 -1
  18. package/fesm2022/flusys-ng-iam-permission-page.component-DhwjuKxl.mjs +0 -143
  19. package/fesm2022/flusys-ng-iam-permission-page.component-DhwjuKxl.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, signal, input, output, effect, Component, computed, DestroyRef } from '@angular/core';
2
+ import { Injectable, inject, signal, input, output, effect, Component, computed, DestroyRef, viewChild } from '@angular/core';
3
3
  import { ApiResourceService, TranslateService, PermissionValidatorService, AngularModule, TranslatePipe, ROLE_ACTION_PERMISSIONS, HasPermissionDirective, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, USER_ROLE_PERMISSIONS, USER_PERMISSION_PROVIDER, UserSelectComponent, USER_ACTION_PERMISSIONS, PROFILE_PERMISSION_PROVIDER, ACTION_PERMISSIONS, ROLE_PERMISSIONS, permissionGuard, anyPermissionGuard, resolveTranslationModule, SHARED_MESSAGES } from '@flusys/ng-shared';
4
4
  import { APP_CONFIG, getServiceUrl, BaseApiService, isCompanyFeatureEnabled, getPermissionMode } from '@flusys/ng-core';
5
5
  import * as i2 from '@flusys/ng-ui';
@@ -101,10 +101,22 @@ const IAM_MESSAGES = {
101
101
  'iam.role.edit': 'Edit Role',
102
102
  'iam.role.name.placeholder': 'Enter role name',
103
103
  // pages/permission/permission-page.component.ts
104
+ 'iam.permission.subtitle': 'Assign and manage access across roles, users, and companies',
104
105
  'iam.permission.role.actions': 'Role Actions',
105
106
  'iam.permission.user.roles': 'User Roles',
106
107
  'iam.permission.user.actions': 'User Actions',
107
108
  'iam.permission.company.actions': 'Company Actions',
109
+ 'iam.permission.empty.role.actions.title': 'Select a role to get started',
110
+ 'iam.permission.empty.role.actions.description': 'Choose a role from the dropdown above to view and manage its assigned actions.',
111
+ 'iam.permission.empty.user.roles.title': 'Select a user to get started',
112
+ 'iam.permission.empty.user.roles.description': 'Choose a user above to view and manage their assigned roles.',
113
+ 'iam.permission.empty.user.actions.title': 'Select a user to get started',
114
+ 'iam.permission.empty.user.actions.description': 'Choose a user above to view and manage their direct action permissions.',
115
+ 'iam.permission.empty.company.actions.title': 'Select a company to get started',
116
+ 'iam.permission.empty.company.actions.description': 'Choose a company from the dropdown above to view and manage its action whitelist.',
117
+ 'iam.permission.unsaved.changes.title': 'Unsaved Changes',
118
+ 'iam.permission.unsaved.changes.message': 'You have unsaved permission changes. Switching now will discard them.',
119
+ 'iam.permission.unsaved.changes.discard': 'Discard Changes',
108
120
  // components/logic-builder.component.ts
109
121
  'iam.logic.title': 'Permission Logic',
110
122
  'iam.logic.add.logic': 'Add Logic',
@@ -824,6 +836,14 @@ class PermissionApiService extends BaseApiService {
824
836
  getCompanyActions(companyId) {
825
837
  return this.http.post(this.getUrl('permissions/get-company-actions'), { companyId });
826
838
  }
839
+ /** Effective roles for the authenticated user — merges company-wide and login-branch-specific assignments */
840
+ getMyRoles() {
841
+ return this.http.post(this.getUrl('permissions/my-roles'), {});
842
+ }
843
+ /** Effective direct actions for the authenticated user — merges company-wide and login-branch-specific assignments */
844
+ getMyActions() {
845
+ return this.http.post(this.getUrl('permissions/my-actions'), {});
846
+ }
827
847
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: PermissionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
828
848
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: PermissionApiService, providedIn: 'root' });
829
849
  }
@@ -1399,9 +1419,27 @@ function convertActionToTreeNode(actions) {
1399
1419
  };
1400
1420
  return actions.map(convert);
1401
1421
  }
1422
+ function buildActionParentTree(actions, excludeId) {
1423
+ const tree = buildTreeFromFlat(actions);
1424
+ const convert = (nodes) => nodes
1425
+ .filter((node) => node.id !== excludeId)
1426
+ .map((node) => {
1427
+ const { children, ...data } = node;
1428
+ const converted = convert(children);
1429
+ return {
1430
+ key: node.id,
1431
+ label: node.name,
1432
+ data,
1433
+ children: converted.length ? converted : undefined,
1434
+ leaf: converted.length === 0,
1435
+ };
1436
+ });
1437
+ return convert(tree);
1438
+ }
1402
1439
 
1403
1440
  class BaseActionSelectorComponent {
1404
1441
  permissionLogic = inject(ActionPermissionLogicService);
1442
+ confirmationService = inject(ConfirmationService);
1405
1443
  translateAdapter = inject(TranslateService, {
1406
1444
  optional: true,
1407
1445
  });
@@ -1472,6 +1510,23 @@ class BaseActionSelectorComponent {
1472
1510
  translate(key, vars) {
1473
1511
  return this.translateAdapter?.translate(key, vars) ?? key;
1474
1512
  }
1513
+ // Guards a selection change (role/user/company) behind a confirm dialog
1514
+ // when there are unsaved edits; runs onAccept directly otherwise.
1515
+ confirmSelectionChange(onAccept, onReject) {
1516
+ if (!this.hasChanges()) {
1517
+ onAccept();
1518
+ return;
1519
+ }
1520
+ this.confirmationService.confirm({
1521
+ header: this.translate('iam.permission.unsaved.changes.title'),
1522
+ message: this.translate('iam.permission.unsaved.changes.message'),
1523
+ icon: 'exclamation-triangle',
1524
+ acceptLabel: this.translate('iam.permission.unsaved.changes.discard'),
1525
+ rejectLabel: this.translate('shared.cancel'),
1526
+ accept: onAccept,
1527
+ reject: onReject,
1528
+ });
1529
+ }
1475
1530
  hasUnmetPrerequisites(action) {
1476
1531
  return action.id
1477
1532
  ? this.actionsWithUnmetPrerequisites().has(action.id)
@@ -1547,6 +1602,8 @@ class RoleActionSelectorComponent extends BaseActionSelectorComponent {
1547
1602
  ...(ngDevMode ? [{ debugName: "selectedRoleId" }] : /* istanbul ignore next */ []));
1548
1603
  roles = signal([], /* @ts-ignore */
1549
1604
  ...(ngDevMode ? [{ debugName: "roles" }] : /* istanbul ignore next */ []));
1605
+ roleSelectRef = viewChild('roleSelect', /* @ts-ignore */
1606
+ ...(ngDevMode ? [{ debugName: "roleSelectRef" }] : /* istanbul ignore next */ []));
1550
1607
  loadDataAbortController = null;
1551
1608
  constructor() {
1552
1609
  super();
@@ -1570,6 +1627,12 @@ class RoleActionSelectorComponent extends BaseActionSelectorComponent {
1570
1627
  }
1571
1628
  });
1572
1629
  }
1630
+ onRoleSelect(roleId) {
1631
+ const previousRoleId = this.selectedRoleId();
1632
+ if (roleId === previousRoleId)
1633
+ return;
1634
+ this.confirmSelectionChange(() => this.selectedRoleId.set(roleId), () => this.roleSelectRef()?.writeValue(previousRoleId));
1635
+ }
1573
1636
  getDefaultTooltip(isSelected) {
1574
1637
  if (isSelected) {
1575
1638
  return `[${this.translate('shared.assigned')}] ${this.translate('iam.tooltip.assigned.to.role')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove.role')}`;
@@ -1643,17 +1706,19 @@ class RoleActionSelectorComponent extends BaseActionSelectorComponent {
1643
1706
  }
1644
1707
  }
1645
1708
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: RoleActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1646
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: RoleActionSelectorComponent, isStandalone: true, selector: "flusys-role-action-selector", usesInheritance: true, ngImport: i0, template: `
1709
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: RoleActionSelectorComponent, isStandalone: true, selector: "flusys-role-action-selector", viewQueries: [{ propertyName: "roleSelectRef", first: true, predicate: ["roleSelect"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
1647
1710
  <div class="role-action-selector">
1648
- <div class="mb-4">
1649
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
1711
+ <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
1712
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
1650
1713
  <div>
1651
- <label class="block font-semibold mb-2">{{
1652
- 'iam.permission.select.role' | translate
1653
- }}</label>
1714
+ <label class="block font-semibold mb-2">
1715
+ <ui-icon name="shield" class="mr-2 text-primary"></ui-icon>
1716
+ {{ 'iam.permission.select.role' | translate }}
1717
+ </label>
1654
1718
  <f-select
1719
+ #roleSelect
1655
1720
  [ngModel]="selectedRoleId()"
1656
- (ngModelChange)="selectedRoleId.set($event)"
1721
+ (ngModelChange)="onRoleSelect($event)"
1657
1722
  [options]="roles()"
1658
1723
  optionLabel="name"
1659
1724
  optionValue="id"
@@ -1668,6 +1733,20 @@ class RoleActionSelectorComponent extends BaseActionSelectorComponent {
1668
1733
  </div>
1669
1734
  </div>
1670
1735
 
1736
+ @if (!selectedRoleId()) {
1737
+ <div class="empty-state border border-dashed border-surface rounded-border p-8 text-center">
1738
+ <div class="empty-state-icon mx-auto mb-3">
1739
+ <ui-icon name="arrow-up" class="text-primary"></ui-icon>
1740
+ </div>
1741
+ <h5 class="m-0 mb-1">
1742
+ {{ 'iam.permission.empty.role.actions.title' | translate }}
1743
+ </h5>
1744
+ <p class="text-sm text-muted-color m-0">
1745
+ {{ 'iam.permission.empty.role.actions.description' | translate }}
1746
+ </p>
1747
+ </div>
1748
+ }
1749
+
1671
1750
  @if (selectedRoleId()) {
1672
1751
  @if (loading()) {
1673
1752
  <div
@@ -1901,21 +1980,23 @@ class RoleActionSelectorComponent extends BaseActionSelectorComponent {
1901
1980
  }
1902
1981
  }
1903
1982
  </div>
1904
- `, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: UiModule }, { kind: "directive", type: i2.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i2.TagComponent, selector: "f-tag", inputs: ["value", "severity", "icon", "rounded", "color", "styleClass", "style"] }, { kind: "component", type: i2.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "component", type: i2.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i2.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i2.TreeTableComponent, selector: "f-treetable", inputs: ["value", "loading", "dataKey", "styleClass", "tableStyle", "scrollable", "scrollHeight", "resizableColumns", "lazy", "totalRecords", "paginator", "first", "rows", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "globalFilterFields", "filterDelay", "selectionMode", "sortMode", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onNodeExpand", "onNodeCollapse", "onNodeSelect", "onNodeUnselect", "onRowContextMenu"] }, { kind: "component", type: i2.TreeTableTogglerComponent, selector: "f-treetabletoggler", inputs: ["rowNode"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1983
+ `, isInline: true, styles: [":host{display:block}.empty-state-icon{width:3rem;height:3rem;border-radius:9999px;display:flex;align-items:center;justify-content:center;background-color:var(--f-primary-50, rgba(59, 130, 246, .08))}:host-context(.app-dark) .empty-state-icon{background-color:#60a5fa1f}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: UiModule }, { kind: "directive", type: i2.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i2.TagComponent, selector: "f-tag", inputs: ["value", "severity", "icon", "rounded", "color", "styleClass", "style"] }, { kind: "component", type: i2.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "component", type: i2.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i2.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i2.TreeTableComponent, selector: "f-treetable", inputs: ["value", "loading", "dataKey", "styleClass", "tableStyle", "scrollable", "scrollHeight", "resizableColumns", "lazy", "totalRecords", "paginator", "first", "rows", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "globalFilterFields", "filterDelay", "selectionMode", "sortMode", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onNodeExpand", "onNodeCollapse", "onNodeSelect", "onNodeUnselect", "onRowContextMenu"] }, { kind: "component", type: i2.TreeTableTogglerComponent, selector: "f-treetabletoggler", inputs: ["rowNode"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1905
1984
  }
1906
1985
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
1907
1986
  type: Component,
1908
1987
  args: [{ selector: 'flusys-role-action-selector', imports: [FormsModule, UiModule, HasPermissionDirective, TranslatePipe], template: `
1909
1988
  <div class="role-action-selector">
1910
- <div class="mb-4">
1911
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
1989
+ <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
1990
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
1912
1991
  <div>
1913
- <label class="block font-semibold mb-2">{{
1914
- 'iam.permission.select.role' | translate
1915
- }}</label>
1992
+ <label class="block font-semibold mb-2">
1993
+ <ui-icon name="shield" class="mr-2 text-primary"></ui-icon>
1994
+ {{ 'iam.permission.select.role' | translate }}
1995
+ </label>
1916
1996
  <f-select
1997
+ #roleSelect
1917
1998
  [ngModel]="selectedRoleId()"
1918
- (ngModelChange)="selectedRoleId.set($event)"
1999
+ (ngModelChange)="onRoleSelect($event)"
1919
2000
  [options]="roles()"
1920
2001
  optionLabel="name"
1921
2002
  optionValue="id"
@@ -1930,6 +2011,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
1930
2011
  </div>
1931
2012
  </div>
1932
2013
 
2014
+ @if (!selectedRoleId()) {
2015
+ <div class="empty-state border border-dashed border-surface rounded-border p-8 text-center">
2016
+ <div class="empty-state-icon mx-auto mb-3">
2017
+ <ui-icon name="arrow-up" class="text-primary"></ui-icon>
2018
+ </div>
2019
+ <h5 class="m-0 mb-1">
2020
+ {{ 'iam.permission.empty.role.actions.title' | translate }}
2021
+ </h5>
2022
+ <p class="text-sm text-muted-color m-0">
2023
+ {{ 'iam.permission.empty.role.actions.description' | translate }}
2024
+ </p>
2025
+ </div>
2026
+ }
2027
+
1933
2028
  @if (selectedRoleId()) {
1934
2029
  @if (loading()) {
1935
2030
  <div
@@ -2163,8 +2258,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
2163
2258
  }
2164
2259
  }
2165
2260
  </div>
2166
- `, styles: [":host{display:block}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
2167
- }], ctorParameters: () => [] });
2261
+ `, styles: [":host{display:block}.empty-state-icon{width:3rem;height:3rem;border-radius:9999px;display:flex;align-items:center;justify-content:center;background-color:var(--f-primary-50, rgba(59, 130, 246, .08))}:host-context(.app-dark) .empty-state-icon{background-color:#60a5fa1f}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
2262
+ }], ctorParameters: () => [], propDecorators: { roleSelectRef: [{ type: i0.ViewChild, args: ['roleSelect', { isSignal: true }] }] } });
2168
2263
 
2169
2264
  class CompanyActionSelectorComponent extends BaseActionSelectorComponent {
2170
2265
  COMPANY_ACTION_PERMISSIONS = COMPANY_ACTION_PERMISSIONS;
@@ -2172,12 +2267,13 @@ class CompanyActionSelectorComponent extends BaseActionSelectorComponent {
2172
2267
  actionApi = inject(ActionApiService);
2173
2268
  permissionApi = inject(PermissionApiService);
2174
2269
  messageService = inject(MessageService);
2175
- confirmationService = inject(ConfirmationService);
2176
2270
  destroyRef = inject(DestroyRef);
2177
2271
  selectedCompanyId = signal(undefined, /* @ts-ignore */
2178
2272
  ...(ngDevMode ? [{ debugName: "selectedCompanyId" }] : /* istanbul ignore next */ []));
2179
2273
  companies = signal([], /* @ts-ignore */
2180
2274
  ...(ngDevMode ? [{ debugName: "companies" }] : /* istanbul ignore next */ []));
2275
+ companySelectRef = viewChild('companySelect', /* @ts-ignore */
2276
+ ...(ngDevMode ? [{ debugName: "companySelectRef" }] : /* istanbul ignore next */ []));
2181
2277
  loadDataAbortController = null;
2182
2278
  constructor() {
2183
2279
  super();
@@ -2201,6 +2297,12 @@ class CompanyActionSelectorComponent extends BaseActionSelectorComponent {
2201
2297
  }
2202
2298
  });
2203
2299
  }
2300
+ onCompanyChange(companyId) {
2301
+ const previousCompanyId = this.selectedCompanyId();
2302
+ if (companyId === previousCompanyId)
2303
+ return;
2304
+ this.confirmSelectionChange(() => this.selectedCompanyId.set(companyId), () => this.companySelectRef()?.writeValue(previousCompanyId));
2305
+ }
2204
2306
  getDefaultTooltip(isSelected) {
2205
2307
  if (isSelected) {
2206
2308
  return `[${this.translate('iam.tooltip.selected')}] ${this.translate('iam.tooltip.selected')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove')}`;
@@ -2321,17 +2423,19 @@ class CompanyActionSelectorComponent extends BaseActionSelectorComponent {
2321
2423
  });
2322
2424
  }
2323
2425
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: CompanyActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2324
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", usesInheritance: true, ngImport: i0, template: `
2426
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", viewQueries: [{ propertyName: "companySelectRef", first: true, predicate: ["companySelect"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
2325
2427
  <div class="company-action-selector">
2326
- <div class="mb-4">
2327
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
2428
+ <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
2429
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
2328
2430
  <div>
2329
- <label class="block font-semibold mb-2">{{
2330
- 'iam.permission.select.company' | translate
2331
- }}</label>
2431
+ <label class="block font-semibold mb-2">
2432
+ <ui-icon name="building" class="mr-2 text-primary"></ui-icon>
2433
+ {{ 'iam.permission.select.company' | translate }}
2434
+ </label>
2332
2435
  <f-select
2436
+ #companySelect
2333
2437
  [ngModel]="selectedCompanyId()"
2334
- (ngModelChange)="selectedCompanyId.set($event)"
2438
+ (ngModelChange)="onCompanyChange($event)"
2335
2439
  [options]="companies()"
2336
2440
  optionLabel="name"
2337
2441
  optionValue="id"
@@ -2346,6 +2450,20 @@ class CompanyActionSelectorComponent extends BaseActionSelectorComponent {
2346
2450
  </div>
2347
2451
  </div>
2348
2452
 
2453
+ @if (!selectedCompanyId()) {
2454
+ <div class="empty-state border border-dashed border-surface rounded-border p-8 text-center">
2455
+ <div class="empty-state-icon mx-auto mb-3">
2456
+ <ui-icon name="building" class="text-primary"></ui-icon>
2457
+ </div>
2458
+ <h5 class="m-0 mb-1">
2459
+ {{ 'iam.permission.empty.company.actions.title' | translate }}
2460
+ </h5>
2461
+ <p class="text-sm text-muted-color m-0">
2462
+ {{ 'iam.permission.empty.company.actions.description' | translate }}
2463
+ </p>
2464
+ </div>
2465
+ }
2466
+
2349
2467
  @if (selectedCompanyId()) {
2350
2468
  @if (loading()) {
2351
2469
  <div
@@ -2572,21 +2690,23 @@ class CompanyActionSelectorComponent extends BaseActionSelectorComponent {
2572
2690
  }
2573
2691
  }
2574
2692
  </div>
2575
- `, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i2.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i2.TagComponent, selector: "f-tag", inputs: ["value", "severity", "icon", "rounded", "color", "styleClass", "style"] }, { kind: "component", type: i2.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "component", type: i2.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i2.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i2.TreeTableComponent, selector: "f-treetable", inputs: ["value", "loading", "dataKey", "styleClass", "tableStyle", "scrollable", "scrollHeight", "resizableColumns", "lazy", "totalRecords", "paginator", "first", "rows", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "globalFilterFields", "filterDelay", "selectionMode", "sortMode", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onNodeExpand", "onNodeCollapse", "onNodeSelect", "onNodeUnselect", "onRowContextMenu"] }, { kind: "component", type: i2.TreeTableTogglerComponent, selector: "f-treetabletoggler", inputs: ["rowNode"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2693
+ `, isInline: true, styles: [":host{display:block}.empty-state-icon{width:3rem;height:3rem;border-radius:9999px;display:flex;align-items:center;justify-content:center;background-color:var(--f-primary-50, rgba(59, 130, 246, .08))}:host-context(.app-dark) .empty-state-icon{background-color:#60a5fa1f}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i2.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i2.TagComponent, selector: "f-tag", inputs: ["value", "severity", "icon", "rounded", "color", "styleClass", "style"] }, { kind: "component", type: i2.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "component", type: i2.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i2.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i2.TreeTableComponent, selector: "f-treetable", inputs: ["value", "loading", "dataKey", "styleClass", "tableStyle", "scrollable", "scrollHeight", "resizableColumns", "lazy", "totalRecords", "paginator", "first", "rows", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "globalFilterFields", "filterDelay", "selectionMode", "sortMode", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onNodeExpand", "onNodeCollapse", "onNodeSelect", "onNodeUnselect", "onRowContextMenu"] }, { kind: "component", type: i2.TreeTableTogglerComponent, selector: "f-treetabletoggler", inputs: ["rowNode"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2576
2694
  }
2577
2695
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
2578
2696
  type: Component,
2579
2697
  args: [{ selector: 'flusys-company-action-selector', imports: [UiModule, FormsModule, HasPermissionDirective, TranslatePipe], template: `
2580
2698
  <div class="company-action-selector">
2581
- <div class="mb-4">
2582
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
2699
+ <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
2700
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
2583
2701
  <div>
2584
- <label class="block font-semibold mb-2">{{
2585
- 'iam.permission.select.company' | translate
2586
- }}</label>
2702
+ <label class="block font-semibold mb-2">
2703
+ <ui-icon name="building" class="mr-2 text-primary"></ui-icon>
2704
+ {{ 'iam.permission.select.company' | translate }}
2705
+ </label>
2587
2706
  <f-select
2707
+ #companySelect
2588
2708
  [ngModel]="selectedCompanyId()"
2589
- (ngModelChange)="selectedCompanyId.set($event)"
2709
+ (ngModelChange)="onCompanyChange($event)"
2590
2710
  [options]="companies()"
2591
2711
  optionLabel="name"
2592
2712
  optionValue="id"
@@ -2601,6 +2721,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
2601
2721
  </div>
2602
2722
  </div>
2603
2723
 
2724
+ @if (!selectedCompanyId()) {
2725
+ <div class="empty-state border border-dashed border-surface rounded-border p-8 text-center">
2726
+ <div class="empty-state-icon mx-auto mb-3">
2727
+ <ui-icon name="building" class="text-primary"></ui-icon>
2728
+ </div>
2729
+ <h5 class="m-0 mb-1">
2730
+ {{ 'iam.permission.empty.company.actions.title' | translate }}
2731
+ </h5>
2732
+ <p class="text-sm text-muted-color m-0">
2733
+ {{ 'iam.permission.empty.company.actions.description' | translate }}
2734
+ </p>
2735
+ </div>
2736
+ }
2737
+
2604
2738
  @if (selectedCompanyId()) {
2605
2739
  @if (loading()) {
2606
2740
  <div
@@ -2827,8 +2961,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
2827
2961
  }
2828
2962
  }
2829
2963
  </div>
2830
- `, styles: [":host{display:block}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
2831
- }], ctorParameters: () => [] });
2964
+ `, styles: [":host{display:block}.empty-state-icon{width:3rem;height:3rem;border-radius:9999px;display:flex;align-items:center;justify-content:center;background-color:var(--f-primary-50, rgba(59, 130, 246, .08))}:host-context(.app-dark) .empty-state-icon{background-color:#60a5fa1f}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
2965
+ }], ctorParameters: () => [], propDecorators: { companySelectRef: [{ type: i0.ViewChild, args: ['companySelect', { isSignal: true }] }] } });
2832
2966
 
2833
2967
  /**
2834
2968
  * User-Role Selector Component
@@ -2869,12 +3003,33 @@ class UserRoleSelectorComponent {
2869
3003
  roleApi = inject(RoleApiService);
2870
3004
  permissionApi = inject(PermissionApiService);
2871
3005
  messageService = inject(MessageService);
3006
+ confirmationService = inject(ConfirmationService);
2872
3007
  translateAdapter = inject(TranslateService, {
2873
3008
  optional: true,
2874
3009
  });
3010
+ userSelectRef = viewChild('userSelect', /* @ts-ignore */
3011
+ ...(ngDevMode ? [{ debugName: "userSelectRef" }] : /* istanbul ignore next */ []));
2875
3012
  translate(key, vars) {
2876
3013
  return this.translateAdapter?.translate(key, vars) ?? key;
2877
3014
  }
3015
+ onUserChange(userId) {
3016
+ const previousUserId = this.selectedUserId();
3017
+ if (userId === previousUserId)
3018
+ return;
3019
+ if (this.hasChanges()) {
3020
+ this.confirmationService.confirm({
3021
+ header: this.translate('iam.permission.unsaved.changes.title'),
3022
+ message: this.translate('iam.permission.unsaved.changes.message'),
3023
+ icon: 'exclamation-triangle',
3024
+ acceptLabel: this.translate('iam.permission.unsaved.changes.discard'),
3025
+ rejectLabel: this.translate('shared.cancel'),
3026
+ accept: () => this.selectedUserId.set(userId),
3027
+ reject: () => this.userSelectRef()?.value.set(previousUserId),
3028
+ });
3029
+ return;
3030
+ }
3031
+ this.selectedUserId.set(userId);
3032
+ }
2878
3033
  // State - User/Branch Selection
2879
3034
  selectedUserId = signal(null, /* @ts-ignore */
2880
3035
  ...(ngDevMode ? [{ debugName: "selectedUserId" }] : /* istanbul ignore next */ []));
@@ -3148,7 +3303,7 @@ class UserRoleSelectorComponent {
3148
3303
  this._initialSelection.set({});
3149
3304
  }
3150
3305
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: UserRoleSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3151
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: UserRoleSelectorComponent, isStandalone: true, selector: "flusys-user-role-selector", ngImport: i0, template: `
3306
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: UserRoleSelectorComponent, isStandalone: true, selector: "flusys-user-role-selector", viewQueries: [{ propertyName: "userSelectRef", first: true, predicate: ["userSelect"], descendants: true, isSignal: true }], ngImport: i0, template: `
3152
3307
  <div class="user-role-selector">
3153
3308
  <!-- User and Branch Selectors -->
3154
3309
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
@@ -3159,8 +3314,9 @@ class UserRoleSelectorComponent {
3159
3314
  {{ 'iam.permission.select.user' | translate }}
3160
3315
  </label>
3161
3316
  <lib-user-select
3317
+ #userSelect
3162
3318
  [value]="selectedUserId()"
3163
- (valueChange)="selectedUserId.set($event)"
3319
+ (valueChange)="onUserChange($event)"
3164
3320
  [isEditMode]="true"
3165
3321
  [placeHolder]="
3166
3322
  'iam.permission.select.user.placeholder' | translate
@@ -3215,6 +3371,20 @@ class UserRoleSelectorComponent {
3215
3371
  </div>
3216
3372
  </div>
3217
3373
 
3374
+ @if (!selectedUserId()) {
3375
+ <div class="empty-state border border-dashed border-surface rounded-border p-8 text-center">
3376
+ <div class="empty-state-icon mx-auto mb-3">
3377
+ <ui-icon name="user" class="text-primary"></ui-icon>
3378
+ </div>
3379
+ <h5 class="m-0 mb-1">
3380
+ {{ 'iam.permission.empty.user.roles.title' | translate }}
3381
+ </h5>
3382
+ <p class="text-sm text-muted-color m-0">
3383
+ {{ 'iam.permission.empty.user.roles.description' | translate }}
3384
+ </p>
3385
+ </div>
3386
+ }
3387
+
3218
3388
  @if (selectedUserId()) {
3219
3389
  <!-- Loading State -->
3220
3390
  @if (loading()) {
@@ -3407,7 +3577,7 @@ class UserRoleSelectorComponent {
3407
3577
  }
3408
3578
  }
3409
3579
  </div>
3410
- `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: UiModule }, { kind: "directive", type: i2.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i2.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "component", type: i2.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "component", type: i2.TableComponent, selector: "f-table", inputs: ["value", "lazy", "loading", "rows", "totalRecords", "rowHover", "dataKey", "scrollable", "scrollHeight", "paginator", "first", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "tableStyle", "styleClass", "globalFilterFields", "filterDelay", "responsiveLayout", "selectionMode", "sortMode", "rowExpandMode", "resizableColumns", "rowStyleClass", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "expandedRowKeys", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "expandedRowKeysChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onRowSelect", "onRowUnselect", "onSelectAllChange", "onRowExpand", "onRowCollapse", "onColResize", "onRowContextMenu"] }, { kind: "directive", type: i2.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["showClear", "value"], outputs: ["valueChange", "userSelected"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
3580
+ `, isInline: true, styles: [":host{display:block}.empty-state-icon{width:3rem;height:3rem;border-radius:9999px;display:flex;align-items:center;justify-content:center;background-color:var(--f-primary-50, rgba(59, 130, 246, .08))}:host-context(.app-dark) .empty-state-icon{background-color:#60a5fa1f}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: UiModule }, { kind: "directive", type: i2.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i2.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "component", type: i2.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "component", type: i2.TableComponent, selector: "f-table", inputs: ["value", "lazy", "loading", "rows", "totalRecords", "rowHover", "dataKey", "scrollable", "scrollHeight", "paginator", "first", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "tableStyle", "styleClass", "globalFilterFields", "filterDelay", "responsiveLayout", "selectionMode", "sortMode", "rowExpandMode", "resizableColumns", "rowStyleClass", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "expandedRowKeys", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "expandedRowKeysChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onRowSelect", "onRowUnselect", "onSelectAllChange", "onRowExpand", "onRowCollapse", "onColResize", "onRowContextMenu"] }, { kind: "directive", type: i2.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["showClear", "value"], outputs: ["valueChange", "userSelected"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
3411
3581
  }
3412
3582
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
3413
3583
  type: Component,
@@ -3429,8 +3599,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
3429
3599
  {{ 'iam.permission.select.user' | translate }}
3430
3600
  </label>
3431
3601
  <lib-user-select
3602
+ #userSelect
3432
3603
  [value]="selectedUserId()"
3433
- (valueChange)="selectedUserId.set($event)"
3604
+ (valueChange)="onUserChange($event)"
3434
3605
  [isEditMode]="true"
3435
3606
  [placeHolder]="
3436
3607
  'iam.permission.select.user.placeholder' | translate
@@ -3485,6 +3656,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
3485
3656
  </div>
3486
3657
  </div>
3487
3658
 
3659
+ @if (!selectedUserId()) {
3660
+ <div class="empty-state border border-dashed border-surface rounded-border p-8 text-center">
3661
+ <div class="empty-state-icon mx-auto mb-3">
3662
+ <ui-icon name="user" class="text-primary"></ui-icon>
3663
+ </div>
3664
+ <h5 class="m-0 mb-1">
3665
+ {{ 'iam.permission.empty.user.roles.title' | translate }}
3666
+ </h5>
3667
+ <p class="text-sm text-muted-color m-0">
3668
+ {{ 'iam.permission.empty.user.roles.description' | translate }}
3669
+ </p>
3670
+ </div>
3671
+ }
3672
+
3488
3673
  @if (selectedUserId()) {
3489
3674
  <!-- Loading State -->
3490
3675
  @if (loading()) {
@@ -3677,8 +3862,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
3677
3862
  }
3678
3863
  }
3679
3864
  </div>
3680
- `, styles: [":host{display:block}\n"] }]
3681
- }], ctorParameters: () => [] });
3865
+ `, styles: [":host{display:block}.empty-state-icon{width:3rem;height:3rem;border-radius:9999px;display:flex;align-items:center;justify-content:center;background-color:var(--f-primary-50, rgba(59, 130, 246, .08))}:host-context(.app-dark) .empty-state-icon{background-color:#60a5fa1f}\n"] }]
3866
+ }], ctorParameters: () => [], propDecorators: { userSelectRef: [{ type: i0.ViewChild, args: ['userSelect', { isSignal: true }] }] } });
3682
3867
 
3683
3868
  class UserActionSelectorComponent extends BaseActionSelectorComponent {
3684
3869
  USER_ACTION_PERMISSIONS = USER_ACTION_PERMISSIONS;
@@ -3688,6 +3873,8 @@ class UserActionSelectorComponent extends BaseActionSelectorComponent {
3688
3873
  actionApi = inject(ActionApiService);
3689
3874
  permissionApi = inject(PermissionApiService);
3690
3875
  messageService = inject(MessageService);
3876
+ userSelectRef = viewChild('userSelect', /* @ts-ignore */
3877
+ ...(ngDevMode ? [{ debugName: "userSelectRef" }] : /* istanbul ignore next */ []));
3691
3878
  _selectedUserId = signal(null, /* @ts-ignore */
3692
3879
  ...(ngDevMode ? [{ debugName: "_selectedUserId" }] : /* istanbul ignore next */ []));
3693
3880
  selectedUserId = this._selectedUserId.asReadonly();
@@ -3743,7 +3930,10 @@ class UserActionSelectorComponent extends BaseActionSelectorComponent {
3743
3930
  });
3744
3931
  }
3745
3932
  onUserSelect(userId) {
3746
- this._selectedUserId.set(userId);
3933
+ const previousUserId = this.selectedUserId();
3934
+ if (userId === previousUserId)
3935
+ return;
3936
+ this.confirmSelectionChange(() => this._selectedUserId.set(userId), () => this.userSelectRef()?.value.set(previousUserId));
3747
3937
  }
3748
3938
  onBranchChange(branchId) {
3749
3939
  this._selectedBranchId.set(branchId);
@@ -3889,7 +4079,7 @@ class UserActionSelectorComponent extends BaseActionSelectorComponent {
3889
4079
  }
3890
4080
  }
3891
4081
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: UserActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3892
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", usesInheritance: true, ngImport: i0, template: `
4082
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", viewQueries: [{ propertyName: "userSelectRef", first: true, predicate: ["userSelect"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
3893
4083
  <div class="user-action-selector">
3894
4084
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
3895
4085
  <div
@@ -3903,6 +4093,7 @@ class UserActionSelectorComponent extends BaseActionSelectorComponent {
3903
4093
  {{ 'iam.permission.select.user' | translate }}
3904
4094
  </label>
3905
4095
  <lib-user-select
4096
+ #userSelect
3906
4097
  [value]="selectedUserId()"
3907
4098
  (valueChange)="onUserSelect($event)"
3908
4099
  [isEditMode]="true"
@@ -3980,6 +4171,20 @@ class UserActionSelectorComponent extends BaseActionSelectorComponent {
3980
4171
  </div>
3981
4172
  </div>
3982
4173
 
4174
+ @if (!selectedUserId()) {
4175
+ <div class="empty-state border border-dashed border-surface rounded-border p-8 text-center">
4176
+ <div class="empty-state-icon mx-auto mb-3">
4177
+ <ui-icon name="key" class="text-primary"></ui-icon>
4178
+ </div>
4179
+ <h5 class="m-0 mb-1">
4180
+ {{ 'iam.permission.empty.user.actions.title' | translate }}
4181
+ </h5>
4182
+ <p class="text-sm text-muted-color m-0">
4183
+ {{ 'iam.permission.empty.user.actions.description' | translate }}
4184
+ </p>
4185
+ </div>
4186
+ }
4187
+
3983
4188
  @if (selectedUserId()) {
3984
4189
  @if (loading()) {
3985
4190
  <div class="flex justify-center p-5">
@@ -4205,7 +4410,7 @@ class UserActionSelectorComponent extends BaseActionSelectorComponent {
4205
4410
  }
4206
4411
  }
4207
4412
  </div>
4208
- `, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: UiModule }, { kind: "directive", type: i2.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i2.TagComponent, selector: "f-tag", inputs: ["value", "severity", "icon", "rounded", "color", "styleClass", "style"] }, { kind: "component", type: i2.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "component", type: i2.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i2.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i2.TreeTableComponent, selector: "f-treetable", inputs: ["value", "loading", "dataKey", "styleClass", "tableStyle", "scrollable", "scrollHeight", "resizableColumns", "lazy", "totalRecords", "paginator", "first", "rows", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "globalFilterFields", "filterDelay", "selectionMode", "sortMode", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onNodeExpand", "onNodeCollapse", "onNodeSelect", "onNodeUnselect", "onRowContextMenu"] }, { kind: "component", type: i2.TreeTableTogglerComponent, selector: "f-treetabletoggler", inputs: ["rowNode"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["showClear", "value"], outputs: ["valueChange", "userSelected"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
4413
+ `, isInline: true, styles: [":host{display:block}.empty-state-icon{width:3rem;height:3rem;border-radius:9999px;display:flex;align-items:center;justify-content:center;background-color:var(--f-primary-50, rgba(59, 130, 246, .08))}:host-context(.app-dark) .empty-state-icon{background-color:#60a5fa1f}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: UiModule }, { kind: "directive", type: i2.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i2.TagComponent, selector: "f-tag", inputs: ["value", "severity", "icon", "rounded", "color", "styleClass", "style"] }, { kind: "component", type: i2.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "component", type: i2.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i2.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i2.TreeTableComponent, selector: "f-treetable", inputs: ["value", "loading", "dataKey", "styleClass", "tableStyle", "scrollable", "scrollHeight", "resizableColumns", "lazy", "totalRecords", "paginator", "first", "rows", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "globalFilterFields", "filterDelay", "selectionMode", "sortMode", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onNodeExpand", "onNodeCollapse", "onNodeSelect", "onNodeUnselect", "onRowContextMenu"] }, { kind: "component", type: i2.TreeTableTogglerComponent, selector: "f-treetabletoggler", inputs: ["rowNode"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["showClear", "value"], outputs: ["valueChange", "userSelected"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
4209
4414
  }
4210
4415
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
4211
4416
  type: Component,
@@ -4229,6 +4434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
4229
4434
  {{ 'iam.permission.select.user' | translate }}
4230
4435
  </label>
4231
4436
  <lib-user-select
4437
+ #userSelect
4232
4438
  [value]="selectedUserId()"
4233
4439
  (valueChange)="onUserSelect($event)"
4234
4440
  [isEditMode]="true"
@@ -4306,6 +4512,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
4306
4512
  </div>
4307
4513
  </div>
4308
4514
 
4515
+ @if (!selectedUserId()) {
4516
+ <div class="empty-state border border-dashed border-surface rounded-border p-8 text-center">
4517
+ <div class="empty-state-icon mx-auto mb-3">
4518
+ <ui-icon name="key" class="text-primary"></ui-icon>
4519
+ </div>
4520
+ <h5 class="m-0 mb-1">
4521
+ {{ 'iam.permission.empty.user.actions.title' | translate }}
4522
+ </h5>
4523
+ <p class="text-sm text-muted-color m-0">
4524
+ {{ 'iam.permission.empty.user.actions.description' | translate }}
4525
+ </p>
4526
+ </div>
4527
+ }
4528
+
4309
4529
  @if (selectedUserId()) {
4310
4530
  @if (loading()) {
4311
4531
  <div class="flex justify-center p-5">
@@ -4531,8 +4751,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
4531
4751
  }
4532
4752
  }
4533
4753
  </div>
4534
- `, styles: [":host{display:block}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
4535
- }], ctorParameters: () => [] });
4754
+ `, styles: [":host{display:block}.empty-state-icon{width:3rem;height:3rem;border-radius:9999px;display:flex;align-items:center;justify-content:center;background-color:var(--f-primary-50, rgba(59, 130, 246, .08))}:host-context(.app-dark) .empty-state-icon{background-color:#60a5fa1f}.validation-warning{background-color:var(--f-yellow-50, #fefce8);border-left:4px solid var(--f-yellow-500, #eab308);color:var(--f-yellow-700, #a16207)}:host-context(.app-dark) .validation-warning{background-color:#eab3081a;color:var(--f-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--f-red-50, rgba(239, 68, 68, .1))!important}:host-context(.app-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
4755
+ }], ctorParameters: () => [], propDecorators: { userSelectRef: [{ type: i0.ViewChild, args: ['userSelect', { isSignal: true }] }] } });
4536
4756
 
4537
4757
  // Logic Builder Component
4538
4758
 
@@ -4543,6 +4763,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
4543
4763
  * Allows ng-auth profile page to display user permissions without
4544
4764
  * depending on ng-iam directly.
4545
4765
  *
4766
+ * Uses the self-scoped `my-roles`/`my-actions` endpoints (not the admin
4767
+ * `get-user-roles`/`get-user-actions` endpoints): those require the
4768
+ * user-role/user-action READ permission and only exact-match the given
4769
+ * branchId, so they drop company-wide (branch-independent) assignments.
4770
+ * The self-scoped endpoints always resolve the caller's own company/branch
4771
+ * from the JWT and merge company-wide + branch-specific grants server-side,
4772
+ * so the `userId`/`branchId` args below are accepted for interface
4773
+ * compatibility but unused.
4774
+ *
4546
4775
  * @example
4547
4776
  * // In app.config.ts
4548
4777
  * providers: [
@@ -4554,10 +4783,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
4554
4783
  */
4555
4784
  class ProfilePermissionProviderAdapter {
4556
4785
  permissionApi = inject(PermissionApiService);
4557
- getUserRoles(userId, branchId) {
4558
- return this.permissionApi
4559
- .getUserRoles(userId, branchId ? { branchId } : undefined)
4560
- .pipe(map$1((response) => ({
4786
+ getUserRoles() {
4787
+ return this.permissionApi.getMyRoles().pipe(map$1((response) => ({
4561
4788
  success: response.success,
4562
4789
  message: response.message,
4563
4790
  data: (response.data ?? []).map((role) => ({
@@ -4567,10 +4794,8 @@ class ProfilePermissionProviderAdapter {
4567
4794
  })),
4568
4795
  })));
4569
4796
  }
4570
- getUserActions(userId, branchId) {
4571
- return this.permissionApi
4572
- .getUserActions(userId, branchId ? { branchId } : undefined)
4573
- .pipe(map$1((response) => ({
4797
+ getUserActions() {
4798
+ return this.permissionApi.getMyActions().pipe(map$1((response) => ({
4574
4799
  success: response.success,
4575
4800
  message: response.message,
4576
4801
  data: (response.data ?? []).map((action) => ({
@@ -4641,15 +4866,15 @@ const IAM_ROUTES = [
4641
4866
  children: [
4642
4867
  {
4643
4868
  path: '',
4644
- loadComponent: () => import('./flusys-ng-iam-action-list-page.component-P7YcQOur.mjs').then((m) => m.ActionListPageComponent),
4869
+ loadComponent: () => import('./flusys-ng-iam-action-list-page.component-D-Lhs8gW.mjs').then((m) => m.ActionListPageComponent),
4645
4870
  },
4646
4871
  {
4647
4872
  path: 'new',
4648
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-CqrxA8bY.mjs').then((m) => m.ActionFormPageComponent),
4873
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-Cz7DY6JM.mjs').then((m) => m.ActionFormPageComponent),
4649
4874
  },
4650
4875
  {
4651
4876
  path: ':id',
4652
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-CqrxA8bY.mjs').then((m) => m.ActionFormPageComponent),
4877
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-Cz7DY6JM.mjs').then((m) => m.ActionFormPageComponent),
4653
4878
  },
4654
4879
  ],
4655
4880
  },
@@ -4660,15 +4885,15 @@ const IAM_ROUTES = [
4660
4885
  children: [
4661
4886
  {
4662
4887
  path: '',
4663
- loadComponent: () => import('./flusys-ng-iam-role-list-page.component-Cu3NxRWX.mjs').then((m) => m.RoleListPageComponent),
4888
+ loadComponent: () => import('./flusys-ng-iam-role-list-page.component-CUzEIuzS.mjs').then((m) => m.RoleListPageComponent),
4664
4889
  },
4665
4890
  {
4666
4891
  path: 'new',
4667
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-Cw958yl1.mjs').then((m) => m.RoleFormPageComponent),
4892
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-DMDQjumX.mjs').then((m) => m.RoleFormPageComponent),
4668
4893
  },
4669
4894
  {
4670
4895
  path: ':id',
4671
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-Cw958yl1.mjs').then((m) => m.RoleFormPageComponent),
4896
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-DMDQjumX.mjs').then((m) => m.RoleFormPageComponent),
4672
4897
  },
4673
4898
  ],
4674
4899
  },
@@ -4683,7 +4908,7 @@ const IAM_ROUTES = [
4683
4908
  COMPANY_ACTION_PERMISSIONS.READ,
4684
4909
  ]),
4685
4910
  ],
4686
- loadComponent: () => import('./flusys-ng-iam-permission-page.component-DhwjuKxl.mjs').then((m) => m.PermissionPageComponent),
4911
+ loadComponent: () => import('./flusys-ng-iam-permission-page.component-C1XE1Zes.mjs').then((m) => m.PermissionPageComponent),
4687
4912
  },
4688
4913
  // Default redirect to the first tab the user has permission to see
4689
4914
  {
@@ -4701,5 +4926,5 @@ const IAM_ROUTES = [
4701
4926
  * Generated bundle index. Do not edit.
4702
4927
  */
4703
4928
 
4704
- export { ActionApiService as A, CompanyActionSelectorComponent as C, IAM_MESSAGES as I, LogicBuilderComponent as L, MAX_DROPDOWN_ITEMS as M, PermissionApiService as P, RoleApiService as R, UserRoleSelectorComponent as U, ActionType as a, RoleActionSelectorComponent as b, convertActionToTreeNode as c, UserActionSelectorComponent as d, ActionPermissionLogicService as e, IAM_ROUTES as f, MyPermissionsApiService as g, PermissionStateService as h, ProfilePermissionProviderAdapter as i, provideIamProviders as p };
4705
- //# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-D397Jg30.mjs.map
4929
+ export { ActionApiService as A, CompanyActionSelectorComponent as C, IAM_MESSAGES as I, LogicBuilderComponent as L, MAX_DROPDOWN_ITEMS as M, PermissionApiService as P, RoleApiService as R, UserRoleSelectorComponent as U, ActionType as a, buildActionParentTree as b, convertActionToTreeNode as c, RoleActionSelectorComponent as d, UserActionSelectorComponent as e, ActionPermissionLogicService as f, IAM_ROUTES as g, MyPermissionsApiService as h, PermissionStateService as i, ProfilePermissionProviderAdapter as j, provideIamProviders as p };
4930
+ //# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-DW60PO5q.mjs.map