@flusys/ng-iam 1.0.0-rc → 1.1.1-beta

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 (28) hide show
  1. package/README.md +24 -175
  2. package/fesm2022/{flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs → flusys-ng-iam-action-form-page.component-C1j10Qhw.mjs} +202 -174
  3. package/fesm2022/flusys-ng-iam-action-form-page.component-C1j10Qhw.mjs.map +1 -0
  4. package/fesm2022/flusys-ng-iam-action-list-page.component-BCzSardO.mjs +281 -0
  5. package/fesm2022/flusys-ng-iam-action-list-page.component-BCzSardO.mjs.map +1 -0
  6. package/fesm2022/{flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs → flusys-ng-iam-flusys-ng-iam-DISrddPh.mjs} +829 -822
  7. package/fesm2022/flusys-ng-iam-flusys-ng-iam-DISrddPh.mjs.map +1 -0
  8. package/fesm2022/flusys-ng-iam-iam-container.component-BkhqmzLi.mjs +97 -0
  9. package/fesm2022/flusys-ng-iam-iam-container.component-BkhqmzLi.mjs.map +1 -0
  10. package/fesm2022/{flusys-ng-iam-permission-page.component-CmxOBJPu.mjs → flusys-ng-iam-permission-page.component-BSQFPt_N.mjs} +12 -41
  11. package/fesm2022/flusys-ng-iam-permission-page.component-BSQFPt_N.mjs.map +1 -0
  12. package/fesm2022/{flusys-ng-iam-role-form-page.component-ByNueI1a.mjs → flusys-ng-iam-role-form-page.component-Cqziu_BM.mjs} +134 -106
  13. package/fesm2022/flusys-ng-iam-role-form-page.component-Cqziu_BM.mjs.map +1 -0
  14. package/fesm2022/flusys-ng-iam-role-list-page.component-BObCxHiB.mjs +266 -0
  15. package/fesm2022/flusys-ng-iam-role-list-page.component-BObCxHiB.mjs.map +1 -0
  16. package/fesm2022/flusys-ng-iam.mjs +1 -1
  17. package/package.json +2 -2
  18. package/types/flusys-ng-iam.d.ts +21 -40
  19. package/fesm2022/flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs.map +0 -1
  20. package/fesm2022/flusys-ng-iam-action-list-page.component-Daf93zpS.mjs +0 -289
  21. package/fesm2022/flusys-ng-iam-action-list-page.component-Daf93zpS.mjs.map +0 -1
  22. package/fesm2022/flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs.map +0 -1
  23. package/fesm2022/flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs +0 -92
  24. package/fesm2022/flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs.map +0 -1
  25. package/fesm2022/flusys-ng-iam-permission-page.component-CmxOBJPu.mjs.map +0 -1
  26. package/fesm2022/flusys-ng-iam-role-form-page.component-ByNueI1a.mjs.map +0 -1
  27. package/fesm2022/flusys-ng-iam-role-list-page.component-CFly5KnH.mjs +0 -316
  28. package/fesm2022/flusys-ng-iam-role-list-page.component-CFly5KnH.mjs.map +0 -1
@@ -1,8 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, signal, input, output, computed, effect, ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
3
- import { HttpClient } from '@angular/common/http';
4
- import { ApiResourceService, PermissionValidatorService, AngularModule, PrimeModule, ROLE_ACTION_PERMISSIONS, HasPermissionDirective, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, USER_ROLE_PERMISSIONS, USER_PERMISSION_PROVIDER, UserSelectComponent, USER_ACTION_PERMISSIONS, PROFILE_PERMISSION_PROVIDER } from '@flusys/ng-shared';
2
+ import { inject, Injectable, signal, input, output, computed, ChangeDetectionStrategy, Component, effect } from '@angular/core';
3
+ import { HttpClient, HttpParams } from '@angular/common/http';
4
+ import { ApiResourceService, PermissionValidatorService, AngularModule, PrimeModule, COMPANY_API_PROVIDER, USER_PERMISSION_PROVIDER, UserSelectComponent, PROFILE_PERMISSION_PROVIDER } from '@flusys/ng-shared';
5
5
  import { APP_CONFIG, BaseApiService, isCompanyFeatureEnabled } from '@flusys/ng-core';
6
+ import * as i2$1 from 'primeng/api';
6
7
  import { ConfirmationService, MessageService } from 'primeng/api';
7
8
  import { of, firstValueFrom, map as map$1 } from 'rxjs';
8
9
  import { tap, catchError, map } from 'rxjs/operators';
@@ -11,11 +12,12 @@ import { CommonModule } from '@angular/common';
11
12
  import * as i1$1 from '@angular/forms';
12
13
  import { FormsModule } from '@angular/forms';
13
14
  import * as i2 from 'primeng/button';
14
- import * as i6 from 'primeng/tooltip';
15
- import * as i3 from 'primeng/checkbox';
16
- import * as i4 from 'primeng/select';
17
- import * as i5 from 'primeng/tag';
18
- import * as i7 from 'primeng/treetable';
15
+ import { ButtonModule } from 'primeng/button';
16
+ import * as i7 from 'primeng/tooltip';
17
+ import * as i4 from 'primeng/checkbox';
18
+ import * as i5 from 'primeng/select';
19
+ import * as i6 from 'primeng/tag';
20
+ import * as i8 from 'primeng/treetable';
19
21
  import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
20
22
  import * as i5$1 from 'primeng/table';
21
23
 
@@ -81,11 +83,11 @@ class ActionApiService extends ApiResourceService {
81
83
  }
82
84
  /**
83
85
  * Get actions for permission assignment
84
- * POST /iam/actions/tree-for-permission
86
+ * GET /iam/actions/tree-for-permission
85
87
  * Returns actions filtered by company whitelist if enabled
86
88
  */
87
89
  getActionsForPermission() {
88
- return this.http.post(`${this.appConfig.apiBaseUrl}/iam/actions/tree-for-permission`, {});
90
+ return this.http.get(`${this.appConfig.apiBaseUrl}/iam/actions/tree-for-permission`);
89
91
  }
90
92
  /**
91
93
  * Get actions in hierarchical tree structure
@@ -833,10 +835,14 @@ class PermissionApiService extends BaseApiService {
833
835
  }
834
836
  /**
835
837
  * Get user's direct action permissions
836
- * POST /iam/permissions/get-user-actions
838
+ * GET /permissions/user-actions/:userId
837
839
  */
838
840
  getUserActions(userId, query) {
839
- return this.http.post(this.getUrl('permissions/get-user-actions'), { userId, branchId: query?.branchId });
841
+ let params = new HttpParams();
842
+ if (query?.branchId) {
843
+ params = params.set('branchId', query.branchId);
844
+ }
845
+ return this.http.get(this.getUrl(`permissions/user-actions/${userId}`), { params });
840
846
  }
841
847
  // =============================================================================
842
848
  // User → Role (Role Assignments)
@@ -850,10 +856,14 @@ class PermissionApiService extends BaseApiService {
850
856
  }
851
857
  /**
852
858
  * Get user's role assignments
853
- * POST /iam/permissions/get-user-roles
859
+ * GET /permissions/user-roles/:userId
854
860
  */
855
861
  getUserRoles(userId, query) {
856
- return this.http.post(this.getUrl('permissions/get-user-roles'), { userId, branchId: query?.branchId });
862
+ let params = new HttpParams();
863
+ if (query?.branchId) {
864
+ params = params.set('branchId', query.branchId);
865
+ }
866
+ return this.http.get(this.getUrl(`permissions/user-roles/${userId}`), { params });
857
867
  }
858
868
  // =============================================================================
859
869
  // Role → Action (Role Permissions)
@@ -867,10 +877,10 @@ class PermissionApiService extends BaseApiService {
867
877
  }
868
878
  /**
869
879
  * Get role's action permissions
870
- * POST /iam/permissions/get-role-actions
880
+ * GET /permissions/role-actions/:roleId
871
881
  */
872
882
  getRoleActions(roleId, query) {
873
- return this.http.post(this.getUrl('permissions/get-role-actions'), { roleId });
883
+ return this.http.get(this.getUrl(`permissions/role-actions/${roleId}`));
874
884
  }
875
885
  // =============================================================================
876
886
  // Company → Action (Company Whitelisting)
@@ -884,10 +894,10 @@ class PermissionApiService extends BaseApiService {
884
894
  }
885
895
  /**
886
896
  * Get company's whitelisted actions
887
- * POST /iam/permissions/get-company-actions
897
+ * GET /permissions/company-actions/:companyId
888
898
  */
889
899
  getCompanyActions(companyId) {
890
- return this.http.post(this.getUrl('permissions/get-company-actions'), { companyId });
900
+ return this.http.get(this.getUrl(`permissions/company-actions/${companyId}`));
891
901
  }
892
902
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
893
903
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, providedIn: 'root' });
@@ -1027,85 +1037,77 @@ class LogicBuilderComponent {
1027
1037
  actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : []));
1028
1038
  logicChange = output();
1029
1039
  availableActions = computed(() => this.actions(), ...(ngDevMode ? [{ debugName: "availableActions" }] : []));
1030
- /** Internal builder tree state (private writable + public readonly pattern) */
1031
- _builderTree = signal(null, ...(ngDevMode ? [{ debugName: "_builderTree" }] : []));
1032
- builderLogic = this._builderTree.asReadonly();
1033
- constructor() {
1034
- // Sync builderTree with logic input changes
1035
- effect(() => {
1036
- const logic = this.logic();
1037
- if (!logic) {
1038
- this._builderTree.set(null);
1039
- }
1040
- else if (!this._builderTree()) {
1041
- this._builderTree.set(toBuilderNode(logic));
1042
- }
1043
- }, { allowSignalWrites: true });
1044
- }
1040
+ builderTree = null;
1041
+ builderLogic = computed(() => {
1042
+ const logic = this.logic();
1043
+ if (!logic) {
1044
+ this.builderTree = null;
1045
+ return null;
1046
+ }
1047
+ if (!this.builderTree) {
1048
+ this.builderTree = toBuilderNode(logic);
1049
+ }
1050
+ return this.builderTree;
1051
+ }, ...(ngDevMode ? [{ debugName: "builderLogic" }] : []));
1045
1052
  initializeLogic() {
1046
- this._builderTree.set({
1053
+ this.builderTree = {
1047
1054
  id: crypto.randomUUID(),
1048
1055
  type: 'group',
1049
1056
  operator: 'AND',
1050
1057
  children: [],
1051
- });
1058
+ };
1052
1059
  this.emitChange();
1053
1060
  }
1054
1061
  clearLogic() {
1055
- this._builderTree.set(null);
1062
+ this.builderTree = null;
1056
1063
  this.logicChange.emit(null);
1057
1064
  }
1058
1065
  toggleOperator(nodeId) {
1059
- const tree = this._builderTree();
1060
- if (!tree)
1066
+ if (!this.builderTree)
1061
1067
  return;
1062
- this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
1068
+ this.builderTree = this.updateNodeInTree(this.builderTree, nodeId, (node) => ({
1063
1069
  ...node,
1064
1070
  operator: node.operator === 'AND' ? 'OR' : 'AND',
1065
- })));
1071
+ }));
1066
1072
  this.emitChange();
1067
1073
  }
1068
1074
  addChildNode(parentId, type) {
1069
- const tree = this._builderTree();
1070
- if (!tree)
1075
+ if (!this.builderTree)
1071
1076
  return;
1072
1077
  const newNode = type === 'group'
1073
1078
  ? { id: crypto.randomUUID(), type: 'group', operator: 'AND', children: [] }
1074
1079
  : { id: crypto.randomUUID(), type: 'action', actionId: '' };
1075
- this._builderTree.set(this.updateNodeInTree(tree, parentId, (node) => ({
1080
+ this.builderTree = this.updateNodeInTree(this.builderTree, parentId, (node) => ({
1076
1081
  ...node,
1077
1082
  children: [...(node.children || []), newNode],
1078
- })));
1083
+ }));
1079
1084
  this.emitChange();
1080
1085
  }
1081
1086
  removeNode(nodeId) {
1082
- const tree = this._builderTree();
1083
- if (!tree)
1087
+ if (!this.builderTree)
1084
1088
  return;
1085
- if (tree.id === nodeId) {
1089
+ if (this.builderTree.id === nodeId) {
1086
1090
  this.clearLogic();
1087
1091
  return;
1088
1092
  }
1089
- this._builderTree.set(this.removeNodeFromTree(tree, nodeId));
1093
+ this.builderTree = this.removeNodeFromTree(this.builderTree, nodeId);
1090
1094
  this.emitChange();
1091
1095
  }
1092
1096
  updateActionId(nodeId, actionId) {
1093
- const tree = this._builderTree();
1094
- if (!tree)
1097
+ if (!this.builderTree)
1095
1098
  return;
1096
- this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
1099
+ this.builderTree = this.updateNodeInTree(this.builderTree, nodeId, (node) => ({
1097
1100
  ...node,
1098
1101
  actionId,
1099
- })));
1102
+ }));
1100
1103
  this.emitChange();
1101
1104
  }
1102
1105
  emitChange() {
1103
- const tree = this._builderTree();
1104
- if (!tree) {
1106
+ if (!this.builderTree) {
1105
1107
  this.logicChange.emit(null);
1106
1108
  return;
1107
1109
  }
1108
- this.logicChange.emit(toLogicNode(tree));
1110
+ this.logicChange.emit(toLogicNode(this.builderTree));
1109
1111
  }
1110
1112
  updateNodeInTree(node, targetId, updater) {
1111
1113
  if (node.id === targetId)
@@ -1133,7 +1135,7 @@ class LogicBuilderComponent {
1133
1135
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: LogicBuilderComponent, isStandalone: true, selector: "lib-logic-builder", inputs: { logic: { classPropertyName: "logic", publicName: "logic", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { logicChange: "logicChange" }, ngImport: i0, template: `
1134
1136
  <div class="logic-builder">
1135
1137
  <div class="flex justify-between items-center mb-3">
1136
- <h4 class="text-sm font-semibold m-0">Permission Logic</h4>
1138
+ <h4 class="text-sm font-semibold">Permission Logic</h4>
1137
1139
  @if (!builderLogic()) {
1138
1140
  <p-button
1139
1141
  label="Add Logic"
@@ -1152,13 +1154,13 @@ class LogicBuilderComponent {
1152
1154
  </div>
1153
1155
 
1154
1156
  @if (builderLogic()) {
1155
- <div class="border border-surface rounded p-3 bg-surface-50">
1157
+ <div class="border rounded p-3 bg-surface-50">
1156
1158
  <div class="mb-3 text-sm text-muted-color">
1157
1159
  Define permission requirements using AND/OR logic with actions
1158
1160
  </div>
1159
1161
 
1160
1162
  <!-- Root Node -->
1161
- <div class="text-sm bg-surface-0 rounded border border-surface">
1163
+ <div class="logic-tree bg-surface-0 rounded border">
1162
1164
  <ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
1163
1165
  </div>
1164
1166
  </div>
@@ -1167,11 +1169,11 @@ class LogicBuilderComponent {
1167
1169
 
1168
1170
  <!-- Recursive Node Template -->
1169
1171
  <ng-template #nodeTemplate let-node let-depth="depth">
1170
- <div class="p-3 border-b border-surface" [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
1171
- <div class="flex items-center gap-3 mb-2">
1172
+ <div class="node" [class]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
1173
+ <div class="node-header">
1172
1174
  <span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
1173
1175
 
1174
- <div class="flex-1 flex items-center gap-2">
1176
+ <div class="node-content">
1175
1177
  @if (node.type === 'group') {
1176
1178
  <!-- Group: show operator toggle -->
1177
1179
  <span
@@ -1187,7 +1189,7 @@ class LogicBuilderComponent {
1187
1189
  @if (node.type === 'action') {
1188
1190
  <!-- Action: show action selector -->
1189
1191
  <select
1190
- class="action-select flex-1 p-2 border border-surface rounded text-sm"
1192
+ class="flex-1"
1191
1193
  [ngModel]="node.actionId"
1192
1194
  (ngModelChange)="updateActionId(node.id, $event)">
1193
1195
  <option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
@@ -1198,7 +1200,7 @@ class LogicBuilderComponent {
1198
1200
  }
1199
1201
  </div>
1200
1202
 
1201
- <div class="flex gap-1">
1203
+ <div class="node-actions">
1202
1204
  <p-button
1203
1205
  icon="pi pi-trash"
1204
1206
  [text]="true"
@@ -1211,7 +1213,7 @@ class LogicBuilderComponent {
1211
1213
 
1212
1214
  <!-- Children for group nodes -->
1213
1215
  @if (node.type === 'group' && node.children && node.children.length > 0) {
1214
- <div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
1216
+ <div class="node-children">
1215
1217
  @for (child of node.children; track child.id) {
1216
1218
  <ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
1217
1219
  }
@@ -1220,9 +1222,9 @@ class LogicBuilderComponent {
1220
1222
 
1221
1223
  <!-- Add child buttons for group nodes -->
1222
1224
  @if (node.type === 'group') {
1223
- <div class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface">
1225
+ <div class="add-child-section">
1224
1226
  <div class="text-xs font-semibold text-muted-color mb-2">Add Condition:</div>
1225
- <div class="flex gap-2 flex-wrap">
1227
+ <div class="add-child-buttons">
1226
1228
  <p-button
1227
1229
  label="Group"
1228
1230
  icon="pi pi-sitemap"
@@ -1241,14 +1243,14 @@ class LogicBuilderComponent {
1241
1243
  }
1242
1244
  </div>
1243
1245
  </ng-template>
1244
- `, isInline: true, styles: [":host{display:block}.node-type{display:inline-flex;align-items:center;padding:.25rem .5rem;border-radius:.25rem;font-weight:600;font-size:.75rem;text-transform:uppercase}.node-type.group{background-color:var(--p-blue-100, #dbeafe);color:var(--p-blue-700, #1e40af)}.node-type.action{background-color:var(--p-green-100, #d1fae5);color:var(--p-green-700, #065f46)}:host-context(.p-dark) .node-type.group{background-color:#3b82f633;color:var(--p-blue-400, #60a5fa)}:host-context(.p-dark) .node-type.action{background-color:#22c55e33;color:var(--p-green-400, #4ade80)}.operator-badge{display:inline-flex;align-items:center;padding:.125rem .5rem;border-radius:9999px;font-weight:600;font-size:.75rem;cursor:pointer;transition:opacity .2s}.operator-badge.and{background-color:var(--p-yellow-100, #fef3c7);color:var(--p-yellow-700, #92400e)}.operator-badge.or{background-color:var(--p-indigo-100, #e0e7ff);color:var(--p-indigo-700, #3730a3)}:host-context(.p-dark) .operator-badge.and{background-color:#eab30833;color:var(--p-yellow-400, #facc15)}:host-context(.p-dark) .operator-badge.or{background-color:#6366f133;color:var(--p-indigo-400, #818cf8)}.operator-badge:hover{opacity:.8}.action-select{background-color:var(--p-surface-0, #ffffff);color:var(--p-text-color, #1f2937)}:host-context(.p-dark) .action-select{background-color:var(--p-surface-900, #1f2937);color:var(--p-text-color, #f9fafb)}.action-select:focus{outline:none;border-color:var(--p-primary-color, #3b82f6)}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { 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: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1246
+ `, isInline: true, styles: [":host{display:block}.logic-tree{font-size:.875rem}.node{padding:.75rem;border-bottom:1px solid var(--p-surface-200, #e5e7eb)}.node:last-child{border-bottom:none}.node-header{display:flex;align-items:center;gap:.75rem;margin-bottom:.5rem}.node-type{display:inline-flex;align-items:center;padding:.25rem .5rem;border-radius:.25rem;font-weight:600;font-size:.75rem;text-transform:uppercase}.node-type.group{background-color:var(--p-blue-100, #dbeafe);color:var(--p-blue-700, #1e40af)}.node-type.action{background-color:var(--p-green-100, #d1fae5);color:var(--p-green-700, #065f46)}.node-content{flex:1;display:flex;align-items:center;gap:.5rem}.node-actions{display:flex;gap:.25rem}.node-children{margin-left:1.5rem;margin-top:.5rem;padding-left:1rem;border-left:2px solid var(--p-surface-200, #e5e7eb)}.operator-badge{display:inline-flex;align-items:center;padding:.125rem .5rem;border-radius:9999px;font-weight:600;font-size:.75rem;cursor:pointer;transition:all .2s}.operator-badge.and{background-color:var(--p-yellow-100, #fef3c7);color:var(--p-yellow-700, #92400e)}.operator-badge.or{background-color:var(--p-indigo-100, #e0e7ff);color:var(--p-indigo-700, #3730a3)}.operator-badge:hover{opacity:.8}select{padding:.375rem .75rem;border:1px solid var(--p-surface-300, #d1d5db);border-radius:.375rem;font-size:.875rem;background-color:var(--p-surface-0, white);color:var(--p-text-color, inherit)}select:focus{outline:none;border-color:var(--p-primary-color, #3b82f6);box-shadow:0 0 0 3px rgba(var(--p-primary-color-rgb, 59, 130, 246),.1)}.add-child-section{margin-top:.75rem;padding:.75rem;background-color:var(--p-surface-50, #f9fafb);border-radius:.375rem;border:1px dashed var(--p-surface-300, #d1d5db)}.add-child-buttons{display:flex;gap:.5rem;flex-wrap:wrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { 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: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: ButtonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1245
1247
  }
1246
1248
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LogicBuilderComponent, decorators: [{
1247
1249
  type: Component,
1248
- args: [{ selector: 'lib-logic-builder', standalone: true, imports: [AngularModule, PrimeModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1250
+ args: [{ selector: 'lib-logic-builder', standalone: true, imports: [CommonModule, FormsModule, AngularModule, PrimeModule, ButtonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1249
1251
  <div class="logic-builder">
1250
1252
  <div class="flex justify-between items-center mb-3">
1251
- <h4 class="text-sm font-semibold m-0">Permission Logic</h4>
1253
+ <h4 class="text-sm font-semibold">Permission Logic</h4>
1252
1254
  @if (!builderLogic()) {
1253
1255
  <p-button
1254
1256
  label="Add Logic"
@@ -1267,13 +1269,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1267
1269
  </div>
1268
1270
 
1269
1271
  @if (builderLogic()) {
1270
- <div class="border border-surface rounded p-3 bg-surface-50">
1272
+ <div class="border rounded p-3 bg-surface-50">
1271
1273
  <div class="mb-3 text-sm text-muted-color">
1272
1274
  Define permission requirements using AND/OR logic with actions
1273
1275
  </div>
1274
1276
 
1275
1277
  <!-- Root Node -->
1276
- <div class="text-sm bg-surface-0 rounded border border-surface">
1278
+ <div class="logic-tree bg-surface-0 rounded border">
1277
1279
  <ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
1278
1280
  </div>
1279
1281
  </div>
@@ -1282,11 +1284,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1282
1284
 
1283
1285
  <!-- Recursive Node Template -->
1284
1286
  <ng-template #nodeTemplate let-node let-depth="depth">
1285
- <div class="p-3 border-b border-surface" [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
1286
- <div class="flex items-center gap-3 mb-2">
1287
+ <div class="node" [class]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
1288
+ <div class="node-header">
1287
1289
  <span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
1288
1290
 
1289
- <div class="flex-1 flex items-center gap-2">
1291
+ <div class="node-content">
1290
1292
  @if (node.type === 'group') {
1291
1293
  <!-- Group: show operator toggle -->
1292
1294
  <span
@@ -1302,7 +1304,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1302
1304
  @if (node.type === 'action') {
1303
1305
  <!-- Action: show action selector -->
1304
1306
  <select
1305
- class="action-select flex-1 p-2 border border-surface rounded text-sm"
1307
+ class="flex-1"
1306
1308
  [ngModel]="node.actionId"
1307
1309
  (ngModelChange)="updateActionId(node.id, $event)">
1308
1310
  <option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
@@ -1313,7 +1315,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1313
1315
  }
1314
1316
  </div>
1315
1317
 
1316
- <div class="flex gap-1">
1318
+ <div class="node-actions">
1317
1319
  <p-button
1318
1320
  icon="pi pi-trash"
1319
1321
  [text]="true"
@@ -1326,7 +1328,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1326
1328
 
1327
1329
  <!-- Children for group nodes -->
1328
1330
  @if (node.type === 'group' && node.children && node.children.length > 0) {
1329
- <div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
1331
+ <div class="node-children">
1330
1332
  @for (child of node.children; track child.id) {
1331
1333
  <ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
1332
1334
  }
@@ -1335,9 +1337,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1335
1337
 
1336
1338
  <!-- Add child buttons for group nodes -->
1337
1339
  @if (node.type === 'group') {
1338
- <div class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface">
1340
+ <div class="add-child-section">
1339
1341
  <div class="text-xs font-semibold text-muted-color mb-2">Add Condition:</div>
1340
- <div class="flex gap-2 flex-wrap">
1342
+ <div class="add-child-buttons">
1341
1343
  <p-button
1342
1344
  label="Group"
1343
1345
  icon="pi pi-sitemap"
@@ -1356,8 +1358,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1356
1358
  }
1357
1359
  </div>
1358
1360
  </ng-template>
1359
- `, styles: [":host{display:block}.node-type{display:inline-flex;align-items:center;padding:.25rem .5rem;border-radius:.25rem;font-weight:600;font-size:.75rem;text-transform:uppercase}.node-type.group{background-color:var(--p-blue-100, #dbeafe);color:var(--p-blue-700, #1e40af)}.node-type.action{background-color:var(--p-green-100, #d1fae5);color:var(--p-green-700, #065f46)}:host-context(.p-dark) .node-type.group{background-color:#3b82f633;color:var(--p-blue-400, #60a5fa)}:host-context(.p-dark) .node-type.action{background-color:#22c55e33;color:var(--p-green-400, #4ade80)}.operator-badge{display:inline-flex;align-items:center;padding:.125rem .5rem;border-radius:9999px;font-weight:600;font-size:.75rem;cursor:pointer;transition:opacity .2s}.operator-badge.and{background-color:var(--p-yellow-100, #fef3c7);color:var(--p-yellow-700, #92400e)}.operator-badge.or{background-color:var(--p-indigo-100, #e0e7ff);color:var(--p-indigo-700, #3730a3)}:host-context(.p-dark) .operator-badge.and{background-color:#eab30833;color:var(--p-yellow-400, #facc15)}:host-context(.p-dark) .operator-badge.or{background-color:#6366f133;color:var(--p-indigo-400, #818cf8)}.operator-badge:hover{opacity:.8}.action-select{background-color:var(--p-surface-0, #ffffff);color:var(--p-text-color, #1f2937)}:host-context(.p-dark) .action-select{background-color:var(--p-surface-900, #1f2937);color:var(--p-text-color, #f9fafb)}.action-select:focus{outline:none;border-color:var(--p-primary-color, #3b82f6)}\n"] }]
1360
- }], ctorParameters: () => [], propDecorators: { logic: [{ type: i0.Input, args: [{ isSignal: true, alias: "logic", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], logicChange: [{ type: i0.Output, args: ["logicChange"] }] } });
1361
+ `, styles: [":host{display:block}.logic-tree{font-size:.875rem}.node{padding:.75rem;border-bottom:1px solid var(--p-surface-200, #e5e7eb)}.node:last-child{border-bottom:none}.node-header{display:flex;align-items:center;gap:.75rem;margin-bottom:.5rem}.node-type{display:inline-flex;align-items:center;padding:.25rem .5rem;border-radius:.25rem;font-weight:600;font-size:.75rem;text-transform:uppercase}.node-type.group{background-color:var(--p-blue-100, #dbeafe);color:var(--p-blue-700, #1e40af)}.node-type.action{background-color:var(--p-green-100, #d1fae5);color:var(--p-green-700, #065f46)}.node-content{flex:1;display:flex;align-items:center;gap:.5rem}.node-actions{display:flex;gap:.25rem}.node-children{margin-left:1.5rem;margin-top:.5rem;padding-left:1rem;border-left:2px solid var(--p-surface-200, #e5e7eb)}.operator-badge{display:inline-flex;align-items:center;padding:.125rem .5rem;border-radius:9999px;font-weight:600;font-size:.75rem;cursor:pointer;transition:all .2s}.operator-badge.and{background-color:var(--p-yellow-100, #fef3c7);color:var(--p-yellow-700, #92400e)}.operator-badge.or{background-color:var(--p-indigo-100, #e0e7ff);color:var(--p-indigo-700, #3730a3)}.operator-badge:hover{opacity:.8}select{padding:.375rem .75rem;border:1px solid var(--p-surface-300, #d1d5db);border-radius:.375rem;font-size:.875rem;background-color:var(--p-surface-0, white);color:var(--p-text-color, inherit)}select:focus{outline:none;border-color:var(--p-primary-color, #3b82f6);box-shadow:0 0 0 3px rgba(var(--p-primary-color-rgb, 59, 130, 246),.1)}.add-child-section{margin-top:.75rem;padding:.75rem;background-color:var(--p-surface-50, #f9fafb);border-radius:.375rem;border:1px dashed var(--p-surface-300, #d1d5db)}.add-child-buttons{display:flex;gap:.5rem;flex-wrap:wrap}\n"] }]
1362
+ }], propDecorators: { logic: [{ type: i0.Input, args: [{ isSignal: true, alias: "logic", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], logicChange: [{ type: i0.Output, args: ["logicChange"] }] } });
1361
1363
 
1362
1364
  /**
1363
1365
  * Tree Utility Functions
@@ -1554,10 +1556,7 @@ function convertActionToTreeNode(actions) {
1554
1556
  * ```
1555
1557
  */
1556
1558
  class RoleActionSelectorComponent {
1557
- // Permission constants for template
1558
- ROLE_ACTION_PERMISSIONS = ROLE_ACTION_PERMISSIONS;
1559
1559
  // Dependencies
1560
- destroyRef = inject(DestroyRef);
1561
1560
  roleApi = inject(RoleApiService);
1562
1561
  actionApi = inject(ActionApiService);
1563
1562
  permissionApi = inject(PermissionApiService);
@@ -1638,10 +1637,6 @@ class RoleActionSelectorComponent {
1638
1637
  loadDataAbortController = null;
1639
1638
  constructor() {
1640
1639
  this.loadRoles();
1641
- // Cleanup on destroy
1642
- this.destroyRef.onDestroy(() => {
1643
- this.loadDataAbortController?.abort();
1644
- });
1645
1640
  // Effect: Load data when role selection changes
1646
1641
  effect(() => {
1647
1642
  const roleId = this.selectedRoleId();
@@ -1651,7 +1646,11 @@ class RoleActionSelectorComponent {
1651
1646
  this.loadDataAbortController = new AbortController();
1652
1647
  this.onRoleChange(this.loadDataAbortController.signal).catch((err) => {
1653
1648
  if (err.name !== 'AbortError') {
1654
- // Error toast handled by global interceptor
1649
+ this.messageService.add({
1650
+ severity: 'error',
1651
+ summary: 'Error',
1652
+ detail: 'Failed to load role permissions. Please refresh.',
1653
+ });
1655
1654
  this.loading.set(false);
1656
1655
  }
1657
1656
  });
@@ -1661,6 +1660,9 @@ class RoleActionSelectorComponent {
1661
1660
  }
1662
1661
  });
1663
1662
  }
1663
+ ngOnDestroy() {
1664
+ this.loadDataAbortController?.abort();
1665
+ }
1664
1666
  /**
1665
1667
  * Load roles from API
1666
1668
  */
@@ -1672,7 +1674,11 @@ class RoleActionSelectorComponent {
1672
1674
  this.roles.set(response?.data ?? []);
1673
1675
  }
1674
1676
  catch {
1675
- // Error toast handled by global interceptor
1677
+ this.messageService.add({
1678
+ severity: 'error',
1679
+ summary: 'Error',
1680
+ detail: 'Failed to load roles',
1681
+ });
1676
1682
  }
1677
1683
  }
1678
1684
  /**
@@ -1830,8 +1836,13 @@ class RoleActionSelectorComponent {
1830
1836
  // Update baseline
1831
1837
  this._initialSelection.set({ ...this.selectionMap() });
1832
1838
  }
1833
- catch {
1834
- // Error toast handled by global interceptor
1839
+ catch (err) {
1840
+ const error = err;
1841
+ this.messageService.add({
1842
+ severity: 'error',
1843
+ summary: 'Error',
1844
+ detail: error?.error?.message || 'Failed to update role permissions',
1845
+ });
1835
1846
  }
1836
1847
  finally {
1837
1848
  this.saving.set(false);
@@ -1851,12 +1862,11 @@ class RoleActionSelectorComponent {
1851
1862
  <div class="role-action-selector">
1852
1863
  <!-- Role Selector -->
1853
1864
  <div class="mb-4">
1854
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
1855
- <div>
1865
+ <div class="grid">
1866
+ <div class="col-12 md:col-6 lg:col-4">
1856
1867
  <label class="block font-semibold mb-2">Select Role</label>
1857
1868
  <p-select
1858
- [ngModel]="selectedRoleId()"
1859
- (ngModelChange)="selectedRoleId.set($event)"
1869
+ [(ngModel)]="selectedRoleId"
1860
1870
  [options]="roles()"
1861
1871
  optionLabel="name"
1862
1872
  optionValue="id"
@@ -1873,21 +1883,24 @@ class RoleActionSelectorComponent {
1873
1883
  <!-- Loading State -->
1874
1884
  @if (loading()) {
1875
1885
  <div
1876
- class="surface-card p-5 rounded-border shadow-sm flex justify-center"
1886
+ class="surface-card p-5 border-round shadow-1 flex justify-content-center"
1877
1887
  >
1878
- <i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
1888
+ <i
1889
+ class="pi pi-spin pi-spinner text-primary"
1890
+ style="font-size: 3rem"
1891
+ ></i>
1879
1892
  </div>
1880
1893
  }
1881
1894
 
1882
1895
  <!-- Action List -->
1883
1896
  @if (!loading() && actions().length > 0) {
1884
- <div class="surface-card p-4 rounded-border shadow-sm">
1897
+ <div class="surface-card p-4 border-round shadow-1">
1885
1898
  <div
1886
- class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
1899
+ class="flex flex-column md:flex-row justify-content-between align-items-start md:align-items-center gap-3 mb-4"
1887
1900
  >
1888
1901
  <div>
1889
1902
  <h5 class="m-0 mb-1">Action Permissions</h5>
1890
- <p class="text-sm text-muted-color m-0">
1903
+ <p class="text-sm text-color-secondary m-0">
1891
1904
  {{ actions().length }} actions available
1892
1905
  </p>
1893
1906
  </div>
@@ -1909,7 +1922,6 @@ class RoleActionSelectorComponent {
1909
1922
  (onClick)="deselectAll()"
1910
1923
  />
1911
1924
  <p-button
1912
- *hasPermission="ROLE_ACTION_PERMISSIONS.ASSIGN"
1913
1925
  label="Save Changes"
1914
1926
  icon="pi pi-save"
1915
1927
  [disabled]="!canSave()"
@@ -1923,12 +1935,14 @@ class RoleActionSelectorComponent {
1923
1935
 
1924
1936
  <!-- Validation Warning -->
1925
1937
  @if (invalidActionsCount() > 0) {
1926
- <div class="validation-warning rounded-border p-3 mb-4">
1927
- <div class="flex items-start gap-2">
1928
- <i class="pi pi-exclamation-triangle text-xl"></i>
1929
- <div class="flex-1">
1930
- <span class="font-semibold">Validation Warning:</span>
1931
- <p class="text-sm mt-1 mb-0">
1938
+ <div class="bg-warn/10 border-l-4 border-warn p-4 mb-4">
1939
+ <div class="flex items-center">
1940
+ <i
1941
+ class="pi pi-exclamation-triangle text-warn mr-2"
1942
+ ></i>
1943
+ <div>
1944
+ <strong class="text-warn">Validation Warning:</strong>
1945
+ <p class="text-sm text-warn mt-1">
1932
1946
  {{ invalidActionsCount() }} selected
1933
1947
  action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
1934
1948
  unmet prerequisites. Fix before saving or use auto-fix on
@@ -1940,33 +1954,33 @@ class RoleActionSelectorComponent {
1940
1954
  }
1941
1955
 
1942
1956
  <!-- Action Tree Table -->
1943
- <div class="overflow-x-auto -mx-4 sm:mx-0">
1944
- <p-treeTable
1945
- [value]="treeNodes()"
1946
- dataKey="id"
1947
- styleClass="p-treetable-sm"
1948
- [tableStyle]="{ 'min-width': '50rem' }"
1949
- >
1950
- <ng-template #header>
1951
- <tr>
1952
- <th class="w-12">
1953
- <p-checkbox
1954
- [ngModel]="allSelected()"
1955
- [binary]="true"
1956
- (ngModelChange)="toggleAll()"
1957
- pTooltip="Select/Deselect All"
1958
- tooltipPosition="top"
1959
- />
1960
- </th>
1961
- <th>Name</th>
1962
- <th class="hidden md:table-cell">Code</th>
1963
- <th>Type</th>
1964
- <th class="hidden lg:table-cell">Requirements</th>
1965
- </tr>
1966
- </ng-template>
1967
- <ng-template #body let-rowNode let-rowData="rowData">
1968
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
1969
- <td class="w-12">
1957
+ <p-treeTable
1958
+ [value]="treeNodes()"
1959
+ [scrollable]="true"
1960
+ scrollHeight="flex"
1961
+ dataKey="id"
1962
+ styleClass="p-treetable-sm"
1963
+ >
1964
+ <ng-template pTemplate="header">
1965
+ <tr>
1966
+ <th style="width: 3rem">
1967
+ <p-checkbox
1968
+ [ngModel]="allSelected()"
1969
+ [binary]="true"
1970
+ (ngModelChange)="toggleAll()"
1971
+ pTooltip="Select/Deselect All"
1972
+ tooltipPosition="top"
1973
+ />
1974
+ </th>
1975
+ <th>Name</th>
1976
+ <th>Code</th>
1977
+ <th>Type</th>
1978
+ <th>Requirements</th>
1979
+ </tr>
1980
+ </ng-template>
1981
+ <ng-template pTemplate="body" let-rowNode let-rowData="rowData">
1982
+ <tr [class.bg-danger/10]="hasUnmetPrerequisites(rowData)">
1983
+ <td style="width: 3rem">
1970
1984
  <p-checkbox
1971
1985
  [ngModel]="selectionMap()[rowData.id]"
1972
1986
  [binary]="true"
@@ -1977,18 +1991,18 @@ class RoleActionSelectorComponent {
1977
1991
  </td>
1978
1992
  <td>
1979
1993
  <p-treeTableToggler [rowNode]="rowNode" />
1980
- <span class="inline-flex items-center gap-2">
1994
+ <span class="inline-flex align-items-center gap-2">
1981
1995
  {{ rowData.name }}
1982
1996
  @if (hasUnmetPrerequisites(rowData)) {
1983
1997
  <i
1984
- class="pi pi-exclamation-triangle text-orange-500"
1998
+ class="pi pi-exclamation-triangle text-warn"
1985
1999
  pTooltip="This action has unmet prerequisites and will fail validation on save"
1986
2000
  tooltipPosition="top"
1987
2001
  ></i>
1988
2002
  }
1989
2003
  </span>
1990
2004
  </td>
1991
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
2005
+ <td>{{ rowData.code || '-' }}</td>
1992
2006
  <td>
1993
2007
  <p-tag
1994
2008
  [value]="rowData.actionType"
@@ -1997,41 +2011,42 @@ class RoleActionSelectorComponent {
1997
2011
  "
1998
2012
  />
1999
2013
  </td>
2000
- <td class="hidden lg:table-cell">
2014
+ <td>
2001
2015
  @if (rowData.permissionLogic) {
2002
- <span class="text-sm text-muted-color">Has prerequisites</span>
2016
+ <span class="text-sm text-muted">Has prerequisites</span>
2003
2017
  } @else {
2004
- <span class="text-muted-color">-</span>
2018
+ <span class="text-muted">-</span>
2005
2019
  }
2006
2020
  </td>
2007
2021
  </tr>
2008
2022
  </ng-template>
2009
- <ng-template #emptymessage>
2023
+ <ng-template pTemplate="emptymessage">
2010
2024
  <tr>
2011
- <td colspan="5" class="text-center p-4 text-muted-color">
2025
+ <td colspan="5" class="text-center p-4">
2012
2026
  @if (loading()) {
2013
2027
  <i class="pi pi-spin pi-spinner"></i> Loading actions...
2014
2028
  } @else { No actions available. }
2015
2029
  </td>
2016
2030
  </tr>
2017
2031
  </ng-template>
2018
- </p-treeTable>
2019
- </div>
2032
+ </p-treeTable>
2020
2033
  </div>
2021
2034
 
2022
2035
  <!-- Change Summary -->
2023
2036
  @if (hasChanges()) {
2024
- <div class="border border-surface rounded-border p-3 mt-4">
2025
- <div class="flex items-center gap-2 mb-3">
2026
- <i class="pi pi-info-circle text-primary"></i>
2027
- <span class="font-bold">Pending Changes</span>
2037
+ <div class="surface-border border-1 border-round p-3 mt-4">
2038
+ <div class="flex align-items-center gap-2 mb-3">
2039
+ <i class="pi pi-info-circle text-primary content-center"></i>
2040
+ <p class="m-0 mb-1 font-bold">Pending Changes</p>
2028
2041
  </div>
2029
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2042
+ <div class="flex flex-col md:flex-row gap-4">
2030
2043
  @if (pendingAdd().length > 0) {
2031
- <div>
2032
- <div class="flex items-center gap-2 mb-2">
2033
- <i class="pi pi-plus-circle text-green-500"></i>
2034
- <strong class="text-sm">To Add ({{ pendingAdd().length }})</strong>
2044
+ <div class="w-full md:w-1/2">
2045
+ <div class="flex align-items-center gap-2 mb-2">
2046
+ <i class="pi pi-plus-circle text-success"></i>
2047
+ <strong class="text-sm"
2048
+ >To Add ({{ pendingAdd().length }})</strong
2049
+ >
2035
2050
  </div>
2036
2051
  <ul class="list-none p-0 m-0 pl-4">
2037
2052
  @for (action of pendingAdd(); track action.id) {
@@ -2041,10 +2056,12 @@ class RoleActionSelectorComponent {
2041
2056
  </div>
2042
2057
  }
2043
2058
  @if (pendingRemove().length > 0) {
2044
- <div>
2045
- <div class="flex items-center gap-2 mb-2">
2046
- <i class="pi pi-minus-circle text-red-500"></i>
2047
- <strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
2059
+ <div class="w-full md:w-1/2">
2060
+ <div class="flex align-items-center gap-2 mb-2">
2061
+ <i class="pi pi-minus-circle text-danger"></i>
2062
+ <strong class="text-sm"
2063
+ >To Remove ({{ pendingRemove().length }})</strong
2064
+ >
2048
2065
  </div>
2049
2066
  <ul class="list-none p-0 m-0 pl-4">
2050
2067
  @for (action of pendingRemove(); track action.id) {
@@ -2059,29 +2076,31 @@ class RoleActionSelectorComponent {
2059
2076
  }
2060
2077
 
2061
2078
  @if (!loading() && actions().length === 0) {
2062
- <div class="surface-card p-5 rounded-border shadow-sm text-center">
2063
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
2064
- <p class="text-muted-color m-0">
2079
+ <div class="surface-card p-5 border-round shadow-1 text-center">
2080
+ <i
2081
+ class="pi pi-info-circle text-color-secondary mb-3"
2082
+ style="font-size: 3rem; display: block;"
2083
+ ></i>
2084
+ <p class="text-color-secondary m-0">
2065
2085
  No actions available for this role.
2066
2086
  </p>
2067
2087
  </div>
2068
2088
  }
2069
2089
  }
2070
2090
  </div>
2071
- `, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\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: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2091
+ `, 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: PrimeModule }, { kind: "directive", type: i2$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i6.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i8.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i8.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2072
2092
  }
2073
2093
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
2074
2094
  type: Component,
2075
- args: [{ selector: 'flusys-role-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
2095
+ args: [{ selector: 'flusys-role-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule], template: `
2076
2096
  <div class="role-action-selector">
2077
2097
  <!-- Role Selector -->
2078
2098
  <div class="mb-4">
2079
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
2080
- <div>
2099
+ <div class="grid">
2100
+ <div class="col-12 md:col-6 lg:col-4">
2081
2101
  <label class="block font-semibold mb-2">Select Role</label>
2082
2102
  <p-select
2083
- [ngModel]="selectedRoleId()"
2084
- (ngModelChange)="selectedRoleId.set($event)"
2103
+ [(ngModel)]="selectedRoleId"
2085
2104
  [options]="roles()"
2086
2105
  optionLabel="name"
2087
2106
  optionValue="id"
@@ -2098,21 +2117,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2098
2117
  <!-- Loading State -->
2099
2118
  @if (loading()) {
2100
2119
  <div
2101
- class="surface-card p-5 rounded-border shadow-sm flex justify-center"
2120
+ class="surface-card p-5 border-round shadow-1 flex justify-content-center"
2102
2121
  >
2103
- <i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
2122
+ <i
2123
+ class="pi pi-spin pi-spinner text-primary"
2124
+ style="font-size: 3rem"
2125
+ ></i>
2104
2126
  </div>
2105
2127
  }
2106
2128
 
2107
2129
  <!-- Action List -->
2108
2130
  @if (!loading() && actions().length > 0) {
2109
- <div class="surface-card p-4 rounded-border shadow-sm">
2131
+ <div class="surface-card p-4 border-round shadow-1">
2110
2132
  <div
2111
- class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
2133
+ class="flex flex-column md:flex-row justify-content-between align-items-start md:align-items-center gap-3 mb-4"
2112
2134
  >
2113
2135
  <div>
2114
2136
  <h5 class="m-0 mb-1">Action Permissions</h5>
2115
- <p class="text-sm text-muted-color m-0">
2137
+ <p class="text-sm text-color-secondary m-0">
2116
2138
  {{ actions().length }} actions available
2117
2139
  </p>
2118
2140
  </div>
@@ -2134,7 +2156,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2134
2156
  (onClick)="deselectAll()"
2135
2157
  />
2136
2158
  <p-button
2137
- *hasPermission="ROLE_ACTION_PERMISSIONS.ASSIGN"
2138
2159
  label="Save Changes"
2139
2160
  icon="pi pi-save"
2140
2161
  [disabled]="!canSave()"
@@ -2148,12 +2169,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2148
2169
 
2149
2170
  <!-- Validation Warning -->
2150
2171
  @if (invalidActionsCount() > 0) {
2151
- <div class="validation-warning rounded-border p-3 mb-4">
2152
- <div class="flex items-start gap-2">
2153
- <i class="pi pi-exclamation-triangle text-xl"></i>
2154
- <div class="flex-1">
2155
- <span class="font-semibold">Validation Warning:</span>
2156
- <p class="text-sm mt-1 mb-0">
2172
+ <div class="bg-warn/10 border-l-4 border-warn p-4 mb-4">
2173
+ <div class="flex items-center">
2174
+ <i
2175
+ class="pi pi-exclamation-triangle text-warn mr-2"
2176
+ ></i>
2177
+ <div>
2178
+ <strong class="text-warn">Validation Warning:</strong>
2179
+ <p class="text-sm text-warn mt-1">
2157
2180
  {{ invalidActionsCount() }} selected
2158
2181
  action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
2159
2182
  unmet prerequisites. Fix before saving or use auto-fix on
@@ -2165,33 +2188,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2165
2188
  }
2166
2189
 
2167
2190
  <!-- Action Tree Table -->
2168
- <div class="overflow-x-auto -mx-4 sm:mx-0">
2169
- <p-treeTable
2170
- [value]="treeNodes()"
2171
- dataKey="id"
2172
- styleClass="p-treetable-sm"
2173
- [tableStyle]="{ 'min-width': '50rem' }"
2174
- >
2175
- <ng-template #header>
2176
- <tr>
2177
- <th class="w-12">
2178
- <p-checkbox
2179
- [ngModel]="allSelected()"
2180
- [binary]="true"
2181
- (ngModelChange)="toggleAll()"
2182
- pTooltip="Select/Deselect All"
2183
- tooltipPosition="top"
2184
- />
2185
- </th>
2186
- <th>Name</th>
2187
- <th class="hidden md:table-cell">Code</th>
2188
- <th>Type</th>
2189
- <th class="hidden lg:table-cell">Requirements</th>
2190
- </tr>
2191
- </ng-template>
2192
- <ng-template #body let-rowNode let-rowData="rowData">
2193
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
2194
- <td class="w-12">
2191
+ <p-treeTable
2192
+ [value]="treeNodes()"
2193
+ [scrollable]="true"
2194
+ scrollHeight="flex"
2195
+ dataKey="id"
2196
+ styleClass="p-treetable-sm"
2197
+ >
2198
+ <ng-template pTemplate="header">
2199
+ <tr>
2200
+ <th style="width: 3rem">
2201
+ <p-checkbox
2202
+ [ngModel]="allSelected()"
2203
+ [binary]="true"
2204
+ (ngModelChange)="toggleAll()"
2205
+ pTooltip="Select/Deselect All"
2206
+ tooltipPosition="top"
2207
+ />
2208
+ </th>
2209
+ <th>Name</th>
2210
+ <th>Code</th>
2211
+ <th>Type</th>
2212
+ <th>Requirements</th>
2213
+ </tr>
2214
+ </ng-template>
2215
+ <ng-template pTemplate="body" let-rowNode let-rowData="rowData">
2216
+ <tr [class.bg-danger/10]="hasUnmetPrerequisites(rowData)">
2217
+ <td style="width: 3rem">
2195
2218
  <p-checkbox
2196
2219
  [ngModel]="selectionMap()[rowData.id]"
2197
2220
  [binary]="true"
@@ -2202,18 +2225,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2202
2225
  </td>
2203
2226
  <td>
2204
2227
  <p-treeTableToggler [rowNode]="rowNode" />
2205
- <span class="inline-flex items-center gap-2">
2228
+ <span class="inline-flex align-items-center gap-2">
2206
2229
  {{ rowData.name }}
2207
2230
  @if (hasUnmetPrerequisites(rowData)) {
2208
2231
  <i
2209
- class="pi pi-exclamation-triangle text-orange-500"
2232
+ class="pi pi-exclamation-triangle text-warn"
2210
2233
  pTooltip="This action has unmet prerequisites and will fail validation on save"
2211
2234
  tooltipPosition="top"
2212
2235
  ></i>
2213
2236
  }
2214
2237
  </span>
2215
2238
  </td>
2216
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
2239
+ <td>{{ rowData.code || '-' }}</td>
2217
2240
  <td>
2218
2241
  <p-tag
2219
2242
  [value]="rowData.actionType"
@@ -2222,41 +2245,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2222
2245
  "
2223
2246
  />
2224
2247
  </td>
2225
- <td class="hidden lg:table-cell">
2248
+ <td>
2226
2249
  @if (rowData.permissionLogic) {
2227
- <span class="text-sm text-muted-color">Has prerequisites</span>
2250
+ <span class="text-sm text-muted">Has prerequisites</span>
2228
2251
  } @else {
2229
- <span class="text-muted-color">-</span>
2252
+ <span class="text-muted">-</span>
2230
2253
  }
2231
2254
  </td>
2232
2255
  </tr>
2233
2256
  </ng-template>
2234
- <ng-template #emptymessage>
2257
+ <ng-template pTemplate="emptymessage">
2235
2258
  <tr>
2236
- <td colspan="5" class="text-center p-4 text-muted-color">
2259
+ <td colspan="5" class="text-center p-4">
2237
2260
  @if (loading()) {
2238
2261
  <i class="pi pi-spin pi-spinner"></i> Loading actions...
2239
2262
  } @else { No actions available. }
2240
2263
  </td>
2241
2264
  </tr>
2242
2265
  </ng-template>
2243
- </p-treeTable>
2244
- </div>
2266
+ </p-treeTable>
2245
2267
  </div>
2246
2268
 
2247
2269
  <!-- Change Summary -->
2248
2270
  @if (hasChanges()) {
2249
- <div class="border border-surface rounded-border p-3 mt-4">
2250
- <div class="flex items-center gap-2 mb-3">
2251
- <i class="pi pi-info-circle text-primary"></i>
2252
- <span class="font-bold">Pending Changes</span>
2271
+ <div class="surface-border border-1 border-round p-3 mt-4">
2272
+ <div class="flex align-items-center gap-2 mb-3">
2273
+ <i class="pi pi-info-circle text-primary content-center"></i>
2274
+ <p class="m-0 mb-1 font-bold">Pending Changes</p>
2253
2275
  </div>
2254
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2276
+ <div class="flex flex-col md:flex-row gap-4">
2255
2277
  @if (pendingAdd().length > 0) {
2256
- <div>
2257
- <div class="flex items-center gap-2 mb-2">
2258
- <i class="pi pi-plus-circle text-green-500"></i>
2259
- <strong class="text-sm">To Add ({{ pendingAdd().length }})</strong>
2278
+ <div class="w-full md:w-1/2">
2279
+ <div class="flex align-items-center gap-2 mb-2">
2280
+ <i class="pi pi-plus-circle text-success"></i>
2281
+ <strong class="text-sm"
2282
+ >To Add ({{ pendingAdd().length }})</strong
2283
+ >
2260
2284
  </div>
2261
2285
  <ul class="list-none p-0 m-0 pl-4">
2262
2286
  @for (action of pendingAdd(); track action.id) {
@@ -2266,10 +2290,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2266
2290
  </div>
2267
2291
  }
2268
2292
  @if (pendingRemove().length > 0) {
2269
- <div>
2270
- <div class="flex items-center gap-2 mb-2">
2271
- <i class="pi pi-minus-circle text-red-500"></i>
2272
- <strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
2293
+ <div class="w-full md:w-1/2">
2294
+ <div class="flex align-items-center gap-2 mb-2">
2295
+ <i class="pi pi-minus-circle text-danger"></i>
2296
+ <strong class="text-sm"
2297
+ >To Remove ({{ pendingRemove().length }})</strong
2298
+ >
2273
2299
  </div>
2274
2300
  <ul class="list-none p-0 m-0 pl-4">
2275
2301
  @for (action of pendingRemove(); track action.id) {
@@ -2284,16 +2310,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2284
2310
  }
2285
2311
 
2286
2312
  @if (!loading() && actions().length === 0) {
2287
- <div class="surface-card p-5 rounded-border shadow-sm text-center">
2288
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
2289
- <p class="text-muted-color m-0">
2313
+ <div class="surface-card p-5 border-round shadow-1 text-center">
2314
+ <i
2315
+ class="pi pi-info-circle text-color-secondary mb-3"
2316
+ style="font-size: 3rem; display: block;"
2317
+ ></i>
2318
+ <p class="text-color-secondary m-0">
2290
2319
  No actions available for this role.
2291
2320
  </p>
2292
2321
  </div>
2293
2322
  }
2294
2323
  }
2295
2324
  </div>
2296
- `, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
2325
+ `, styles: [":host{display:block}\n"] }]
2297
2326
  }], ctorParameters: () => [] });
2298
2327
 
2299
2328
  /**
@@ -2331,8 +2360,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2331
2360
  * ```
2332
2361
  */
2333
2362
  class CompanyActionSelectorComponent {
2334
- // Permission constants for template
2335
- COMPANY_ACTION_PERMISSIONS = COMPANY_ACTION_PERMISSIONS;
2336
2363
  // Dependencies
2337
2364
  companyApiProvider = inject(COMPANY_API_PROVIDER);
2338
2365
  actionApi = inject(ActionApiService);
@@ -2340,7 +2367,6 @@ class CompanyActionSelectorComponent {
2340
2367
  messageService = inject(MessageService);
2341
2368
  confirmationService = inject(ConfirmationService);
2342
2369
  permissionLogic = inject(ActionPermissionLogicService);
2343
- destroyRef = inject(DestroyRef);
2344
2370
  // State - Company Selection
2345
2371
  selectedCompanyId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedCompanyId" }] : []));
2346
2372
  companies = signal([], ...(ngDevMode ? [{ debugName: "companies" }] : []));
@@ -2415,10 +2441,6 @@ class CompanyActionSelectorComponent {
2415
2441
  // AbortController for data loading
2416
2442
  loadDataAbortController = null;
2417
2443
  constructor() {
2418
- // Cleanup on destroy
2419
- this.destroyRef.onDestroy(() => {
2420
- this.loadDataAbortController?.abort();
2421
- });
2422
2444
  this.loadCompanies();
2423
2445
  // Effect: Load data when company selection changes
2424
2446
  effect(() => {
@@ -2429,7 +2451,11 @@ class CompanyActionSelectorComponent {
2429
2451
  this.loadDataAbortController = new AbortController();
2430
2452
  this.loadData(this.loadDataAbortController.signal).catch((err) => {
2431
2453
  if (err.name !== 'AbortError') {
2432
- // Error toast handled by global interceptor
2454
+ this.messageService.add({
2455
+ severity: 'error',
2456
+ summary: 'Error',
2457
+ detail: 'Failed to load company actions. Please refresh.',
2458
+ });
2433
2459
  this.loading.set(false);
2434
2460
  }
2435
2461
  });
@@ -2439,6 +2465,9 @@ class CompanyActionSelectorComponent {
2439
2465
  }
2440
2466
  });
2441
2467
  }
2468
+ ngOnDestroy() {
2469
+ this.loadDataAbortController?.abort();
2470
+ }
2442
2471
  /**
2443
2472
  * Load companies from API
2444
2473
  */
@@ -2451,7 +2480,11 @@ class CompanyActionSelectorComponent {
2451
2480
  this.companies.set(response?.data ?? []);
2452
2481
  }
2453
2482
  catch {
2454
- // Error toast handled by global interceptor
2483
+ this.messageService.add({
2484
+ severity: 'error',
2485
+ summary: 'Error',
2486
+ detail: 'Failed to load companies',
2487
+ });
2455
2488
  }
2456
2489
  }
2457
2490
  /**
@@ -2616,8 +2649,13 @@ class CompanyActionSelectorComponent {
2616
2649
  // Update baseline
2617
2650
  this._initialSelection.set({ ...this.selectionMap() });
2618
2651
  }
2619
- catch {
2620
- // Error toast handled by global interceptor
2652
+ catch (err) {
2653
+ const error = err;
2654
+ this.messageService.add({
2655
+ severity: 'error',
2656
+ summary: 'Error',
2657
+ detail: error?.error?.message || 'Failed to update company action whitelist',
2658
+ });
2621
2659
  }
2622
2660
  finally {
2623
2661
  this.saving.set(false);
@@ -2684,12 +2722,11 @@ class CompanyActionSelectorComponent {
2684
2722
  <div class="company-action-selector">
2685
2723
  <!-- Company Selector -->
2686
2724
  <div class="mb-4">
2687
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
2688
- <div>
2725
+ <div class="grid">
2726
+ <div class="col-12 md:col-6 lg:col-4">
2689
2727
  <label class="block font-semibold mb-2">Select Company</label>
2690
2728
  <p-select
2691
- [ngModel]="selectedCompanyId()"
2692
- (ngModelChange)="selectedCompanyId.set($event)"
2729
+ [(ngModel)]="selectedCompanyId"
2693
2730
  [options]="companies()"
2694
2731
  optionLabel="name"
2695
2732
  optionValue="id"
@@ -2706,21 +2743,24 @@ class CompanyActionSelectorComponent {
2706
2743
  <!-- Loading State -->
2707
2744
  @if (loading()) {
2708
2745
  <div
2709
- class="surface-card p-5 rounded-border shadow-sm flex justify-center"
2746
+ class="surface-card p-5 border-round shadow-1 flex justify-content-center"
2710
2747
  >
2711
- <i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
2748
+ <i
2749
+ class="pi pi-spin pi-spinner text-primary"
2750
+ style="font-size: 3rem"
2751
+ ></i>
2712
2752
  </div>
2713
2753
  }
2714
2754
 
2715
2755
  <!-- Action List -->
2716
2756
  @if (!loading() && actions().length > 0) {
2717
- <div class="surface-card p-4 rounded-border shadow-sm">
2757
+ <div class="surface-card p-4 border-round shadow-1">
2718
2758
  <div
2719
- class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
2759
+ class="flex flex-column md:flex-row justify-content-between align-items-start md:align-items-center gap-3 mb-4"
2720
2760
  >
2721
2761
  <div>
2722
2762
  <h5 class="m-0 mb-1">Action Whitelist</h5>
2723
- <p class="text-sm text-muted-color m-0">
2763
+ <p class="text-sm text-color-secondary m-0">
2724
2764
  {{ actions().length }} actions available
2725
2765
  </p>
2726
2766
  </div>
@@ -2742,7 +2782,6 @@ class CompanyActionSelectorComponent {
2742
2782
  (onClick)="deselectAll()"
2743
2783
  />
2744
2784
  <p-button
2745
- *hasPermission="COMPANY_ACTION_PERMISSIONS.ASSIGN"
2746
2785
  label="Save Changes"
2747
2786
  icon="pi pi-save"
2748
2787
  [disabled]="!canSave()"
@@ -2756,12 +2795,14 @@ class CompanyActionSelectorComponent {
2756
2795
 
2757
2796
  <!-- Validation Warning -->
2758
2797
  @if (invalidActionsCount() > 0) {
2759
- <div class="validation-warning rounded-border p-3 mb-4">
2760
- <div class="flex items-start gap-2">
2761
- <i class="pi pi-exclamation-triangle text-xl"></i>
2762
- <div class="flex-1">
2763
- <span class="font-semibold">Validation Warning:</span>
2764
- <p class="text-sm mt-1 mb-0">
2798
+ <div class="bg-warn/10 border-l-4 border-warn p-4 mb-4">
2799
+ <div class="flex items-center">
2800
+ <i
2801
+ class="pi pi-exclamation-triangle text-warn mr-2"
2802
+ ></i>
2803
+ <div>
2804
+ <strong class="text-warn">Validation Warning:</strong>
2805
+ <p class="text-sm text-warn mt-1">
2765
2806
  {{ invalidActionsCount() }} selected
2766
2807
  action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
2767
2808
  unmet prerequisites. Fix before saving or use auto-fix on
@@ -2773,33 +2814,33 @@ class CompanyActionSelectorComponent {
2773
2814
  }
2774
2815
 
2775
2816
  <!-- Action Tree Table -->
2776
- <div class="overflow-x-auto -mx-4 sm:mx-0">
2777
- <p-treeTable
2778
- [value]="treeNodes()"
2779
- dataKey="id"
2780
- styleClass="p-treetable-sm"
2781
- [tableStyle]="{ 'min-width': '50rem' }"
2782
- >
2783
- <ng-template #header>
2784
- <tr>
2785
- <th class="w-12">
2786
- <p-checkbox
2787
- [ngModel]="allSelected()"
2788
- [binary]="true"
2789
- (ngModelChange)="toggleAll()"
2790
- pTooltip="Select/Deselect All"
2791
- tooltipPosition="top"
2792
- />
2793
- </th>
2794
- <th>Name</th>
2795
- <th class="hidden md:table-cell">Code</th>
2796
- <th>Type</th>
2797
- <th class="hidden lg:table-cell">Description</th>
2798
- </tr>
2799
- </ng-template>
2800
- <ng-template #body let-rowNode let-rowData="rowData">
2801
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
2802
- <td class="w-12">
2817
+ <p-treeTable
2818
+ [value]="treeNodes()"
2819
+ [scrollable]="true"
2820
+ scrollHeight="flex"
2821
+ dataKey="id"
2822
+ styleClass="p-treetable-sm"
2823
+ >
2824
+ <ng-template pTemplate="header">
2825
+ <tr>
2826
+ <th style="width: 3rem">
2827
+ <p-checkbox
2828
+ [ngModel]="allSelected()"
2829
+ [binary]="true"
2830
+ (ngModelChange)="toggleAll()"
2831
+ pTooltip="Select/Deselect All"
2832
+ tooltipPosition="top"
2833
+ />
2834
+ </th>
2835
+ <th>Name</th>
2836
+ <th>Code</th>
2837
+ <th>Type</th>
2838
+ <th>Description</th>
2839
+ </tr>
2840
+ </ng-template>
2841
+ <ng-template pTemplate="body" let-rowNode let-rowData="rowData">
2842
+ <tr [class.bg-danger/10]="hasUnmetPrerequisites(rowData)">
2843
+ <td style="width: 3rem">
2803
2844
  <p-checkbox
2804
2845
  [ngModel]="selectionMap()[rowData.id]"
2805
2846
  [binary]="true"
@@ -2810,18 +2851,18 @@ class CompanyActionSelectorComponent {
2810
2851
  </td>
2811
2852
  <td>
2812
2853
  <p-treeTableToggler [rowNode]="rowNode" />
2813
- <span class="inline-flex items-center gap-2">
2854
+ <span class="inline-flex align-items-center gap-2">
2814
2855
  {{ rowData.name }}
2815
2856
  @if (hasUnmetPrerequisites(rowData)) {
2816
2857
  <i
2817
- class="pi pi-exclamation-triangle text-orange-500"
2858
+ class="pi pi-exclamation-triangle text-warn"
2818
2859
  pTooltip="This action has unmet prerequisites and will fail validation on save"
2819
2860
  tooltipPosition="top"
2820
2861
  ></i>
2821
2862
  }
2822
2863
  </span>
2823
2864
  </td>
2824
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
2865
+ <td>{{ rowData.code || '-' }}</td>
2825
2866
  <td>
2826
2867
  <p-tag
2827
2868
  [value]="rowData.actionType"
@@ -2830,35 +2871,36 @@ class CompanyActionSelectorComponent {
2830
2871
  "
2831
2872
  />
2832
2873
  </td>
2833
- <td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
2874
+ <td>{{ rowData.description || '-' }}</td>
2834
2875
  </tr>
2835
2876
  </ng-template>
2836
- <ng-template #emptymessage>
2877
+ <ng-template pTemplate="emptymessage">
2837
2878
  <tr>
2838
- <td colspan="5" class="text-center p-4 text-muted-color">
2879
+ <td colspan="5" class="text-center p-4">
2839
2880
  @if (loading()) {
2840
2881
  <i class="pi pi-spin pi-spinner"></i> Loading actions...
2841
2882
  } @else { No actions available. }
2842
2883
  </td>
2843
2884
  </tr>
2844
2885
  </ng-template>
2845
- </p-treeTable>
2846
- </div>
2886
+ </p-treeTable>
2847
2887
  </div>
2848
2888
 
2849
2889
  <!-- Change Summary -->
2850
2890
  @if (hasChanges()) {
2851
- <div class="border border-surface rounded-border p-3 mt-4">
2852
- <div class="flex items-center gap-2 mb-3">
2853
- <i class="pi pi-info-circle text-primary"></i>
2854
- <span class="font-bold">Pending Changes</span>
2891
+ <div class="surface-border border-1 border-round p-3 mt-4">
2892
+ <div class="flex align-items-center gap-2 mb-3">
2893
+ <i class="pi pi-info-circle text-primary content-center"></i>
2894
+ <p class="m-0 mb-1 font-bold">Pending Changes</p>
2855
2895
  </div>
2856
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2896
+ <div class="flex flex-col md:flex-row gap-4">
2857
2897
  @if (pendingAdd().length > 0) {
2858
- <div>
2898
+ <div class="w-full md:w-1/2">
2859
2899
  <div class="flex items-center gap-2 mb-2">
2860
- <i class="pi pi-plus-circle text-green-500"></i>
2861
- <strong class="text-sm">To Whitelist ({{ pendingAdd().length }})</strong>
2900
+ <i class="pi pi-plus-circle text-success"></i>
2901
+ <strong class="text-sm">
2902
+ To Whitelist ({{ pendingAdd().length }})
2903
+ </strong>
2862
2904
  </div>
2863
2905
  <ul class="list-none p-0 m-0 pl-4">
2864
2906
  @for (action of pendingAdd(); track action.id) {
@@ -2867,11 +2909,14 @@ class CompanyActionSelectorComponent {
2867
2909
  </ul>
2868
2910
  </div>
2869
2911
  }
2912
+
2870
2913
  @if (pendingRemove().length > 0) {
2871
- <div>
2914
+ <div class="w-full md:w-1/2">
2872
2915
  <div class="flex items-center gap-2 mb-2">
2873
- <i class="pi pi-minus-circle text-red-500"></i>
2874
- <strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
2916
+ <i class="pi pi-minus-circle text-danger"></i>
2917
+ <strong class="text-sm">
2918
+ To Remove ({{ pendingRemove().length }})
2919
+ </strong>
2875
2920
  </div>
2876
2921
  <ul class="list-none p-0 m-0 pl-4">
2877
2922
  @for (action of pendingRemove(); track action.id) {
@@ -2886,29 +2931,31 @@ class CompanyActionSelectorComponent {
2886
2931
  }
2887
2932
 
2888
2933
  @if (!loading() && actions().length === 0) {
2889
- <div class="surface-card p-5 rounded-border shadow-sm text-center">
2890
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
2891
- <p class="text-muted-color m-0">
2934
+ <div class="surface-card p-5 border-round shadow-1 text-center">
2935
+ <i
2936
+ class="pi pi-info-circle text-color-secondary mb-3"
2937
+ style="font-size: 3rem; display: block;"
2938
+ ></i>
2939
+ <p class="text-color-secondary m-0">
2892
2940
  No actions available for this company.
2893
2941
  </p>
2894
2942
  </div>
2895
2943
  }
2896
2944
  }
2897
2945
  </div>
2898
- `, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\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: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2946
+ `, 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: PrimeModule }, { kind: "directive", type: i2$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i6.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i8.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i8.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2899
2947
  }
2900
2948
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
2901
2949
  type: Component,
2902
- args: [{ selector: 'flusys-company-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
2950
+ args: [{ selector: 'flusys-company-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule], template: `
2903
2951
  <div class="company-action-selector">
2904
2952
  <!-- Company Selector -->
2905
2953
  <div class="mb-4">
2906
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
2907
- <div>
2954
+ <div class="grid">
2955
+ <div class="col-12 md:col-6 lg:col-4">
2908
2956
  <label class="block font-semibold mb-2">Select Company</label>
2909
2957
  <p-select
2910
- [ngModel]="selectedCompanyId()"
2911
- (ngModelChange)="selectedCompanyId.set($event)"
2958
+ [(ngModel)]="selectedCompanyId"
2912
2959
  [options]="companies()"
2913
2960
  optionLabel="name"
2914
2961
  optionValue="id"
@@ -2925,21 +2972,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2925
2972
  <!-- Loading State -->
2926
2973
  @if (loading()) {
2927
2974
  <div
2928
- class="surface-card p-5 rounded-border shadow-sm flex justify-center"
2975
+ class="surface-card p-5 border-round shadow-1 flex justify-content-center"
2929
2976
  >
2930
- <i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
2977
+ <i
2978
+ class="pi pi-spin pi-spinner text-primary"
2979
+ style="font-size: 3rem"
2980
+ ></i>
2931
2981
  </div>
2932
2982
  }
2933
2983
 
2934
2984
  <!-- Action List -->
2935
2985
  @if (!loading() && actions().length > 0) {
2936
- <div class="surface-card p-4 rounded-border shadow-sm">
2986
+ <div class="surface-card p-4 border-round shadow-1">
2937
2987
  <div
2938
- class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
2988
+ class="flex flex-column md:flex-row justify-content-between align-items-start md:align-items-center gap-3 mb-4"
2939
2989
  >
2940
2990
  <div>
2941
2991
  <h5 class="m-0 mb-1">Action Whitelist</h5>
2942
- <p class="text-sm text-muted-color m-0">
2992
+ <p class="text-sm text-color-secondary m-0">
2943
2993
  {{ actions().length }} actions available
2944
2994
  </p>
2945
2995
  </div>
@@ -2961,7 +3011,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2961
3011
  (onClick)="deselectAll()"
2962
3012
  />
2963
3013
  <p-button
2964
- *hasPermission="COMPANY_ACTION_PERMISSIONS.ASSIGN"
2965
3014
  label="Save Changes"
2966
3015
  icon="pi pi-save"
2967
3016
  [disabled]="!canSave()"
@@ -2975,12 +3024,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2975
3024
 
2976
3025
  <!-- Validation Warning -->
2977
3026
  @if (invalidActionsCount() > 0) {
2978
- <div class="validation-warning rounded-border p-3 mb-4">
2979
- <div class="flex items-start gap-2">
2980
- <i class="pi pi-exclamation-triangle text-xl"></i>
2981
- <div class="flex-1">
2982
- <span class="font-semibold">Validation Warning:</span>
2983
- <p class="text-sm mt-1 mb-0">
3027
+ <div class="bg-warn/10 border-l-4 border-warn p-4 mb-4">
3028
+ <div class="flex items-center">
3029
+ <i
3030
+ class="pi pi-exclamation-triangle text-warn mr-2"
3031
+ ></i>
3032
+ <div>
3033
+ <strong class="text-warn">Validation Warning:</strong>
3034
+ <p class="text-sm text-warn mt-1">
2984
3035
  {{ invalidActionsCount() }} selected
2985
3036
  action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
2986
3037
  unmet prerequisites. Fix before saving or use auto-fix on
@@ -2992,33 +3043,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2992
3043
  }
2993
3044
 
2994
3045
  <!-- Action Tree Table -->
2995
- <div class="overflow-x-auto -mx-4 sm:mx-0">
2996
- <p-treeTable
2997
- [value]="treeNodes()"
2998
- dataKey="id"
2999
- styleClass="p-treetable-sm"
3000
- [tableStyle]="{ 'min-width': '50rem' }"
3001
- >
3002
- <ng-template #header>
3003
- <tr>
3004
- <th class="w-12">
3005
- <p-checkbox
3006
- [ngModel]="allSelected()"
3007
- [binary]="true"
3008
- (ngModelChange)="toggleAll()"
3009
- pTooltip="Select/Deselect All"
3010
- tooltipPosition="top"
3011
- />
3012
- </th>
3013
- <th>Name</th>
3014
- <th class="hidden md:table-cell">Code</th>
3015
- <th>Type</th>
3016
- <th class="hidden lg:table-cell">Description</th>
3017
- </tr>
3018
- </ng-template>
3019
- <ng-template #body let-rowNode let-rowData="rowData">
3020
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
3021
- <td class="w-12">
3046
+ <p-treeTable
3047
+ [value]="treeNodes()"
3048
+ [scrollable]="true"
3049
+ scrollHeight="flex"
3050
+ dataKey="id"
3051
+ styleClass="p-treetable-sm"
3052
+ >
3053
+ <ng-template pTemplate="header">
3054
+ <tr>
3055
+ <th style="width: 3rem">
3056
+ <p-checkbox
3057
+ [ngModel]="allSelected()"
3058
+ [binary]="true"
3059
+ (ngModelChange)="toggleAll()"
3060
+ pTooltip="Select/Deselect All"
3061
+ tooltipPosition="top"
3062
+ />
3063
+ </th>
3064
+ <th>Name</th>
3065
+ <th>Code</th>
3066
+ <th>Type</th>
3067
+ <th>Description</th>
3068
+ </tr>
3069
+ </ng-template>
3070
+ <ng-template pTemplate="body" let-rowNode let-rowData="rowData">
3071
+ <tr [class.bg-danger/10]="hasUnmetPrerequisites(rowData)">
3072
+ <td style="width: 3rem">
3022
3073
  <p-checkbox
3023
3074
  [ngModel]="selectionMap()[rowData.id]"
3024
3075
  [binary]="true"
@@ -3029,18 +3080,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3029
3080
  </td>
3030
3081
  <td>
3031
3082
  <p-treeTableToggler [rowNode]="rowNode" />
3032
- <span class="inline-flex items-center gap-2">
3083
+ <span class="inline-flex align-items-center gap-2">
3033
3084
  {{ rowData.name }}
3034
3085
  @if (hasUnmetPrerequisites(rowData)) {
3035
3086
  <i
3036
- class="pi pi-exclamation-triangle text-orange-500"
3087
+ class="pi pi-exclamation-triangle text-warn"
3037
3088
  pTooltip="This action has unmet prerequisites and will fail validation on save"
3038
3089
  tooltipPosition="top"
3039
3090
  ></i>
3040
3091
  }
3041
3092
  </span>
3042
3093
  </td>
3043
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
3094
+ <td>{{ rowData.code || '-' }}</td>
3044
3095
  <td>
3045
3096
  <p-tag
3046
3097
  [value]="rowData.actionType"
@@ -3049,35 +3100,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3049
3100
  "
3050
3101
  />
3051
3102
  </td>
3052
- <td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
3103
+ <td>{{ rowData.description || '-' }}</td>
3053
3104
  </tr>
3054
3105
  </ng-template>
3055
- <ng-template #emptymessage>
3106
+ <ng-template pTemplate="emptymessage">
3056
3107
  <tr>
3057
- <td colspan="5" class="text-center p-4 text-muted-color">
3108
+ <td colspan="5" class="text-center p-4">
3058
3109
  @if (loading()) {
3059
3110
  <i class="pi pi-spin pi-spinner"></i> Loading actions...
3060
3111
  } @else { No actions available. }
3061
3112
  </td>
3062
3113
  </tr>
3063
3114
  </ng-template>
3064
- </p-treeTable>
3065
- </div>
3115
+ </p-treeTable>
3066
3116
  </div>
3067
3117
 
3068
3118
  <!-- Change Summary -->
3069
3119
  @if (hasChanges()) {
3070
- <div class="border border-surface rounded-border p-3 mt-4">
3071
- <div class="flex items-center gap-2 mb-3">
3072
- <i class="pi pi-info-circle text-primary"></i>
3073
- <span class="font-bold">Pending Changes</span>
3120
+ <div class="surface-border border-1 border-round p-3 mt-4">
3121
+ <div class="flex align-items-center gap-2 mb-3">
3122
+ <i class="pi pi-info-circle text-primary content-center"></i>
3123
+ <p class="m-0 mb-1 font-bold">Pending Changes</p>
3074
3124
  </div>
3075
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
3125
+ <div class="flex flex-col md:flex-row gap-4">
3076
3126
  @if (pendingAdd().length > 0) {
3077
- <div>
3127
+ <div class="w-full md:w-1/2">
3078
3128
  <div class="flex items-center gap-2 mb-2">
3079
- <i class="pi pi-plus-circle text-green-500"></i>
3080
- <strong class="text-sm">To Whitelist ({{ pendingAdd().length }})</strong>
3129
+ <i class="pi pi-plus-circle text-success"></i>
3130
+ <strong class="text-sm">
3131
+ To Whitelist ({{ pendingAdd().length }})
3132
+ </strong>
3081
3133
  </div>
3082
3134
  <ul class="list-none p-0 m-0 pl-4">
3083
3135
  @for (action of pendingAdd(); track action.id) {
@@ -3086,11 +3138,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3086
3138
  </ul>
3087
3139
  </div>
3088
3140
  }
3141
+
3089
3142
  @if (pendingRemove().length > 0) {
3090
- <div>
3143
+ <div class="w-full md:w-1/2">
3091
3144
  <div class="flex items-center gap-2 mb-2">
3092
- <i class="pi pi-minus-circle text-red-500"></i>
3093
- <strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
3145
+ <i class="pi pi-minus-circle text-danger"></i>
3146
+ <strong class="text-sm">
3147
+ To Remove ({{ pendingRemove().length }})
3148
+ </strong>
3094
3149
  </div>
3095
3150
  <ul class="list-none p-0 m-0 pl-4">
3096
3151
  @for (action of pendingRemove(); track action.id) {
@@ -3105,16 +3160,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3105
3160
  }
3106
3161
 
3107
3162
  @if (!loading() && actions().length === 0) {
3108
- <div class="surface-card p-5 rounded-border shadow-sm text-center">
3109
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
3110
- <p class="text-muted-color m-0">
3163
+ <div class="surface-card p-5 border-round shadow-1 text-center">
3164
+ <i
3165
+ class="pi pi-info-circle text-color-secondary mb-3"
3166
+ style="font-size: 3rem; display: block;"
3167
+ ></i>
3168
+ <p class="text-color-secondary m-0">
3111
3169
  No actions available for this company.
3112
3170
  </p>
3113
3171
  </div>
3114
3172
  }
3115
3173
  }
3116
3174
  </div>
3117
- `, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
3175
+ `, styles: [":host{display:block}\n"] }]
3118
3176
  }], ctorParameters: () => [] });
3119
3177
 
3120
3178
  /**
@@ -3148,8 +3206,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3148
3206
  * ```
3149
3207
  */
3150
3208
  class UserRoleSelectorComponent {
3151
- // Permission constants for template
3152
- USER_ROLE_PERMISSIONS = USER_ROLE_PERMISSIONS;
3153
3209
  // Dependencies
3154
3210
  appConfig = inject(APP_CONFIG);
3155
3211
  companyContext = inject(LAYOUT_AUTH_STATE);
@@ -3157,9 +3213,6 @@ class UserRoleSelectorComponent {
3157
3213
  roleApi = inject(RoleApiService);
3158
3214
  permissionApi = inject(PermissionApiService);
3159
3215
  messageService = inject(MessageService);
3160
- destroyRef = inject(DestroyRef);
3161
- // AbortController for data loading
3162
- loadDataAbortController = null;
3163
3216
  // State - User/Branch Selection
3164
3217
  selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
3165
3218
  selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
@@ -3196,11 +3249,7 @@ class UserRoleSelectorComponent {
3196
3249
  hasChanges = computed(() => {
3197
3250
  const current = this.selectionMap();
3198
3251
  const initial = this.initialSelection();
3199
- const currentKeys = Object.keys(current);
3200
- const initialKeys = Object.keys(initial);
3201
- if (currentKeys.length !== initialKeys.length)
3202
- return true;
3203
- return currentKeys.some((key) => current[key] !== initial[key]);
3252
+ return JSON.stringify(current) !== JSON.stringify(initial);
3204
3253
  }, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
3205
3254
  // Computed - Pending Changes
3206
3255
  pendingAdd = computed(() => {
@@ -3220,10 +3269,6 @@ class UserRoleSelectorComponent {
3220
3269
  return this.hasChanges() && !this.saving();
3221
3270
  }, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
3222
3271
  constructor() {
3223
- // Cleanup on destroy
3224
- this.destroyRef.onDestroy(() => {
3225
- this.loadDataAbortController?.abort();
3226
- });
3227
3272
  // Effect: Load user branches and data when user changes
3228
3273
  effect(() => {
3229
3274
  const userId = this.selectedUserId();
@@ -3271,7 +3316,11 @@ class UserRoleSelectorComponent {
3271
3316
  this.branches.set(userBranches);
3272
3317
  }
3273
3318
  catch {
3274
- // Error toast handled by global interceptor
3319
+ this.messageService.add({
3320
+ severity: 'error',
3321
+ summary: 'Error',
3322
+ detail: 'Failed to load user permitted branches',
3323
+ });
3275
3324
  }
3276
3325
  }
3277
3326
  /**
@@ -3309,7 +3358,11 @@ class UserRoleSelectorComponent {
3309
3358
  this._initialSelection.set({ ...selMap });
3310
3359
  }
3311
3360
  catch {
3312
- // Error toast handled by global interceptor
3361
+ this.messageService.add({
3362
+ severity: 'error',
3363
+ summary: 'Error',
3364
+ detail: 'Failed to load user role assignments',
3365
+ });
3313
3366
  }
3314
3367
  finally {
3315
3368
  this.loading.set(false);
@@ -3416,8 +3469,13 @@ class UserRoleSelectorComponent {
3416
3469
  // Update baseline
3417
3470
  this._initialSelection.set({ ...this.selectionMap() });
3418
3471
  }
3419
- catch {
3420
- // Error toast handled by global interceptor
3472
+ catch (err) {
3473
+ const error = err;
3474
+ this.messageService.add({
3475
+ severity: 'error',
3476
+ summary: 'Error',
3477
+ detail: error?.error?.message || 'Failed to update user role assignments',
3478
+ });
3421
3479
  }
3422
3480
  finally {
3423
3481
  this.saving.set(false);
@@ -3435,30 +3493,28 @@ class UserRoleSelectorComponent {
3435
3493
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UserRoleSelectorComponent, isStandalone: true, selector: "flusys-user-role-selector", ngImport: i0, template: `
3436
3494
  <div class="user-role-selector">
3437
3495
  <!-- User and Branch Selectors -->
3438
- <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
3439
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
3440
- <div>
3441
- <label class="block font-semibold mb-2">
3496
+ <div class="surface-card p-4 border-round mb-4 shadow-1">
3497
+ <div class="formgrid grid gap-5">
3498
+ <div class="field col-12 sm:col-6 mb-0">
3499
+ <label class="block font-semibold mb-2 text-900">
3442
3500
  <i class="pi pi-user mr-2 text-primary"></i>
3443
3501
  Select User
3444
3502
  </label>
3445
3503
  <lib-user-select
3446
- [value]="selectedUserId()"
3447
- (valueChange)="selectedUserId.set($event)"
3504
+ [(value)]="selectedUserId"
3448
3505
  [isEditMode]="true"
3449
3506
  placeHolder="Select a user"
3450
3507
  />
3451
3508
  </div>
3452
3509
 
3453
3510
  @if (showBranchSelector()) {
3454
- <div>
3455
- <label class="block font-semibold mb-2">
3511
+ <div class="field col-12 sm:col-6 mb-0">
3512
+ <label class="block font-semibold mb-2 text-900">
3456
3513
  <i class="pi pi-building mr-2 text-primary"></i>
3457
3514
  Select Branch
3458
3515
  </label>
3459
3516
  <p-select
3460
- [ngModel]="selectedBranchId()"
3461
- (ngModelChange)="selectedBranchId.set($event)"
3517
+ [(ngModel)]="selectedBranchId"
3462
3518
  [options]="filteredBranches()"
3463
3519
  optionLabel="name"
3464
3520
  optionValue="id"
@@ -3469,20 +3525,20 @@ class UserRoleSelectorComponent {
3469
3525
  >
3470
3526
  <ng-template #selectedItem let-branch>
3471
3527
  @if (branch) {
3472
- <div class="flex items-center gap-2">
3528
+ <div class="flex align-items-center gap-2">
3473
3529
  <i class="pi pi-building text-primary"></i>
3474
3530
  <span class="font-semibold">{{ branch.name }}</span>
3475
3531
  </div>
3476
3532
  }
3477
3533
  </ng-template>
3478
3534
  <ng-template #item let-branch>
3479
- <div class="flex items-center gap-2">
3480
- <i class="pi pi-building text-muted-color"></i>
3535
+ <div class="flex align-items-center gap-2">
3536
+ <i class="pi pi-building text-color-secondary"></i>
3481
3537
  <span>{{ branch.name }}</span>
3482
3538
  </div>
3483
3539
  </ng-template>
3484
3540
  </p-select>
3485
- <small class="text-muted-color block mt-1">
3541
+ <small class="text-color-secondary block mt-1">
3486
3542
  {{ filteredBranches().length }} permitted branch{{
3487
3543
  filteredBranches().length !== 1 ? 'es' : ''
3488
3544
  }}
@@ -3497,21 +3553,24 @@ class UserRoleSelectorComponent {
3497
3553
  <!-- Loading State -->
3498
3554
  @if (loading()) {
3499
3555
  <div
3500
- class="surface-card p-5 rounded-border shadow-sm flex justify-center"
3556
+ class="surface-card p-5 border-round shadow-1 flex justify-content-center"
3501
3557
  >
3502
- <i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
3558
+ <i
3559
+ class="pi pi-spin pi-spinner text-primary"
3560
+ style="font-size: 3rem"
3561
+ ></i>
3503
3562
  </div>
3504
3563
  }
3505
3564
 
3506
3565
  <!-- Role List -->
3507
3566
  @if (!loading() && roles().length > 0) {
3508
- <div class="surface-card p-4 rounded-border shadow-sm">
3567
+ <div class="surface-card p-4 border-round shadow-1">
3509
3568
  <div
3510
- class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
3569
+ class="flex flex-column md:flex-row justify-content-between align-items-start md:align-items-center gap-3 mb-4"
3511
3570
  >
3512
3571
  <div>
3513
3572
  <h5 class="m-0 mb-1">Role Assignments</h5>
3514
- <p class="text-sm text-muted-color m-0">
3573
+ <p class="text-sm text-color-secondary m-0">
3515
3574
  {{ roles().length }} roles available
3516
3575
  </p>
3517
3576
  </div>
@@ -3533,7 +3592,6 @@ class UserRoleSelectorComponent {
3533
3592
  (onClick)="deselectAll()"
3534
3593
  />
3535
3594
  <p-button
3536
- *hasPermission="USER_ROLE_PERMISSIONS.ASSIGN"
3537
3595
  label="Save Changes"
3538
3596
  icon="pi pi-save"
3539
3597
  [disabled]="!canSave()"
@@ -3546,78 +3604,77 @@ class UserRoleSelectorComponent {
3546
3604
  </div>
3547
3605
 
3548
3606
  <!-- Role Table -->
3549
- <div class="overflow-x-auto -mx-4 sm:mx-0">
3550
- <p-table
3551
- [value]="roles()"
3552
- [rows]="10"
3553
- [paginator]="roles().length > 10"
3554
- [rowsPerPageOptions]="[10, 20, 50]"
3555
- [globalFilterFields]="['name', 'code', 'description']"
3556
- [showCurrentPageReport]="true"
3557
- currentPageReportTemplate="Showing {first} to {last} of {totalRecords} roles"
3558
- styleClass="p-datatable-sm"
3559
- [tableStyle]="{ 'min-width': '35rem' }"
3560
- >
3561
- <ng-template #header>
3562
- <tr>
3563
- <th style="width: 3rem">
3564
- <p-checkbox
3565
- [ngModel]="allSelected()"
3566
- [binary]="true"
3567
- (ngModelChange)="toggleAll()"
3568
- pTooltip="Select/Deselect All"
3569
- tooltipPosition="top"
3570
- />
3571
- </th>
3572
- <th>Name</th>
3573
- <th class="hidden sm:table-cell">Code</th>
3574
- <th class="hidden md:table-cell">Description</th>
3575
- </tr>
3576
- </ng-template>
3577
- <ng-template #body let-role>
3578
- <tr>
3579
- <td>
3580
- <p-checkbox
3581
- [ngModel]="selectionMap()[role.id]"
3582
- [binary]="true"
3583
- (ngModelChange)="onRoleToggle(role, $event)"
3584
- [pTooltip]="getTooltip(role)"
3585
- tooltipPosition="top"
3586
- />
3587
- </td>
3588
- <td>{{ role.name }}</td>
3589
- <td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
3590
- <td class="hidden md:table-cell">{{ role.description || '-' }}</td>
3591
- </tr>
3592
- </ng-template>
3593
- <ng-template #emptymessage>
3594
- <tr>
3595
- <td colspan="4" class="text-center p-4 text-muted-color">
3596
- @if (loading()) {
3597
- <i class="pi pi-spin pi-spinner"></i> Loading roles...
3598
- } @else {
3599
- No roles available.
3600
- }
3601
- </td>
3602
- </tr>
3603
- </ng-template>
3604
- </p-table>
3605
- </div>
3607
+ <p-table
3608
+ [value]="roles()"
3609
+ [rows]="10"
3610
+ [paginator]="roles().length > 10"
3611
+ [rowsPerPageOptions]="[10, 20, 50]"
3612
+ [globalFilterFields]="['name', 'code', 'description']"
3613
+ [showCurrentPageReport]="true"
3614
+ currentPageReportTemplate="Showing {first} to {last} of {totalRecords} roles"
3615
+ styleClass="p-datatable-sm"
3616
+ >
3617
+ <ng-template #header>
3618
+ <tr>
3619
+ <th style="width: 3rem">
3620
+ <p-checkbox
3621
+ [ngModel]="allSelected()"
3622
+ [binary]="true"
3623
+ (ngModelChange)="toggleAll()"
3624
+ pTooltip="Select/Deselect All"
3625
+ tooltipPosition="top"
3626
+ />
3627
+ </th>
3628
+ <th>Name</th>
3629
+ <th>Code</th>
3630
+ <th>Description</th>
3631
+ </tr>
3632
+ </ng-template>
3633
+ <ng-template #body let-role>
3634
+ <tr>
3635
+ <td>
3636
+ <p-checkbox
3637
+ [ngModel]="selectionMap()[role.id]"
3638
+ [binary]="true"
3639
+ (ngModelChange)="onRoleToggle(role, $event)"
3640
+ [pTooltip]="getTooltip(role)"
3641
+ tooltipPosition="top"
3642
+ />
3643
+ </td>
3644
+ <td>{{ role.name }}</td>
3645
+ <td>{{ role.code || '-' }}</td>
3646
+ <td>{{ role.description || '-' }}</td>
3647
+ </tr>
3648
+ </ng-template>
3649
+ <ng-template #emptymessage>
3650
+ <tr>
3651
+ <td colspan="4" class="text-center p-4">
3652
+ @if (loading()) {
3653
+ <i class="pi pi-spin pi-spinner"></i> Loading roles...
3654
+ } @else {
3655
+ No roles available.
3656
+ }
3657
+ </td>
3658
+ </tr>
3659
+ </ng-template>
3660
+ </p-table>
3606
3661
  </div>
3607
3662
 
3608
3663
  <!-- Change Summary -->
3609
3664
  @if (hasChanges()) {
3610
- <div class="border border-surface rounded-border p-3 mt-4">
3611
- <div class="flex items-center gap-2 mb-3">
3612
- <i class="pi pi-info-circle text-primary"></i>
3613
- <span class="font-bold">Pending Changes</span>
3665
+ <div class="surface-border border-1 border-round p-3 mt-4">
3666
+ <div class="flex align-items-center gap-2 mb-3">
3667
+ <i class="pi pi-info-circle text-primary content-center"></i>
3668
+ <p class="m-0 mb-1 font-bold">Pending Changes</p>
3614
3669
  </div>
3615
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
3670
+ <div class="flex flex-col md:flex-row gap-4">
3616
3671
  @if (pendingAdd().length > 0) {
3617
- <div>
3618
- <div class="flex items-center gap-2 mb-2">
3619
- <i class="pi pi-plus-circle text-green-500"></i>
3620
- <strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
3672
+ <div class="w-full md:w-1/2">
3673
+ <div class="flex align-items-center gap-2 mb-2">
3674
+ <i class="pi pi-plus-circle text-success"></i>
3675
+ <strong class="text-sm"
3676
+ >To Assign ({{ pendingAdd().length }})</strong
3677
+ >
3621
3678
  </div>
3622
3679
  <ul class="list-none p-0 m-0 pl-4">
3623
3680
  @for (role of pendingAdd(); track role.id) {
@@ -3627,10 +3684,12 @@ class UserRoleSelectorComponent {
3627
3684
  </div>
3628
3685
  }
3629
3686
  @if (pendingRemove().length > 0) {
3630
- <div>
3631
- <div class="flex items-center gap-2 mb-2">
3632
- <i class="pi pi-minus-circle text-red-500"></i>
3633
- <strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
3687
+ <div class="w-full md:w-1/2">
3688
+ <div class="flex align-items-center gap-2 mb-2">
3689
+ <i class="pi pi-minus-circle text-danger"></i>
3690
+ <strong class="text-sm"
3691
+ >To Remove ({{ pendingRemove().length }})</strong
3692
+ >
3634
3693
  </div>
3635
3694
  <ul class="list-none p-0 m-0 pl-4">
3636
3695
  @for (role of pendingRemove(); track role.id) {
@@ -3645,46 +3704,47 @@ class UserRoleSelectorComponent {
3645
3704
  }
3646
3705
 
3647
3706
  @if (!loading() && roles().length === 0) {
3648
- <div class="surface-card p-5 rounded-border shadow-sm text-center">
3649
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
3650
- <p class="text-muted-color m-0">
3707
+ <div class="surface-card p-5 border-round shadow-1 text-center">
3708
+ <i
3709
+ class="pi pi-info-circle text-color-secondary mb-3"
3710
+ style="font-size: 3rem; display: block;"
3711
+ ></i>
3712
+ <p class="text-color-secondary m-0">
3651
3713
  No roles available for this user.
3652
3714
  </p>
3653
3715
  </div>
3654
3716
  }
3655
3717
  }
3656
3718
  </div>
3657
- `, 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: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["loadUsers", "placeHolder", "isEditMode", "filterActive", "additionalFilters", "pageSize", "value"], outputs: ["valueChange", "userSelected", "onError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3719
+ `, 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: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["loadUsers", "placeHolder", "isEditMode", "filterActive", "additionalFilters", "pageSize", "value"], outputs: ["valueChange", "userSelected", "onError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3658
3720
  }
3659
3721
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
3660
3722
  type: Component,
3661
- args: [{ selector: 'flusys-user-role-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
3723
+ args: [{ selector: 'flusys-user-role-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, UserSelectComponent], template: `
3662
3724
  <div class="user-role-selector">
3663
3725
  <!-- User and Branch Selectors -->
3664
- <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
3665
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
3666
- <div>
3667
- <label class="block font-semibold mb-2">
3726
+ <div class="surface-card p-4 border-round mb-4 shadow-1">
3727
+ <div class="formgrid grid gap-5">
3728
+ <div class="field col-12 sm:col-6 mb-0">
3729
+ <label class="block font-semibold mb-2 text-900">
3668
3730
  <i class="pi pi-user mr-2 text-primary"></i>
3669
3731
  Select User
3670
3732
  </label>
3671
3733
  <lib-user-select
3672
- [value]="selectedUserId()"
3673
- (valueChange)="selectedUserId.set($event)"
3734
+ [(value)]="selectedUserId"
3674
3735
  [isEditMode]="true"
3675
3736
  placeHolder="Select a user"
3676
3737
  />
3677
3738
  </div>
3678
3739
 
3679
3740
  @if (showBranchSelector()) {
3680
- <div>
3681
- <label class="block font-semibold mb-2">
3741
+ <div class="field col-12 sm:col-6 mb-0">
3742
+ <label class="block font-semibold mb-2 text-900">
3682
3743
  <i class="pi pi-building mr-2 text-primary"></i>
3683
3744
  Select Branch
3684
3745
  </label>
3685
3746
  <p-select
3686
- [ngModel]="selectedBranchId()"
3687
- (ngModelChange)="selectedBranchId.set($event)"
3747
+ [(ngModel)]="selectedBranchId"
3688
3748
  [options]="filteredBranches()"
3689
3749
  optionLabel="name"
3690
3750
  optionValue="id"
@@ -3695,20 +3755,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3695
3755
  >
3696
3756
  <ng-template #selectedItem let-branch>
3697
3757
  @if (branch) {
3698
- <div class="flex items-center gap-2">
3758
+ <div class="flex align-items-center gap-2">
3699
3759
  <i class="pi pi-building text-primary"></i>
3700
3760
  <span class="font-semibold">{{ branch.name }}</span>
3701
3761
  </div>
3702
3762
  }
3703
3763
  </ng-template>
3704
3764
  <ng-template #item let-branch>
3705
- <div class="flex items-center gap-2">
3706
- <i class="pi pi-building text-muted-color"></i>
3765
+ <div class="flex align-items-center gap-2">
3766
+ <i class="pi pi-building text-color-secondary"></i>
3707
3767
  <span>{{ branch.name }}</span>
3708
3768
  </div>
3709
3769
  </ng-template>
3710
3770
  </p-select>
3711
- <small class="text-muted-color block mt-1">
3771
+ <small class="text-color-secondary block mt-1">
3712
3772
  {{ filteredBranches().length }} permitted branch{{
3713
3773
  filteredBranches().length !== 1 ? 'es' : ''
3714
3774
  }}
@@ -3723,21 +3783,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3723
3783
  <!-- Loading State -->
3724
3784
  @if (loading()) {
3725
3785
  <div
3726
- class="surface-card p-5 rounded-border shadow-sm flex justify-center"
3786
+ class="surface-card p-5 border-round shadow-1 flex justify-content-center"
3727
3787
  >
3728
- <i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
3788
+ <i
3789
+ class="pi pi-spin pi-spinner text-primary"
3790
+ style="font-size: 3rem"
3791
+ ></i>
3729
3792
  </div>
3730
3793
  }
3731
3794
 
3732
3795
  <!-- Role List -->
3733
3796
  @if (!loading() && roles().length > 0) {
3734
- <div class="surface-card p-4 rounded-border shadow-sm">
3797
+ <div class="surface-card p-4 border-round shadow-1">
3735
3798
  <div
3736
- class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
3799
+ class="flex flex-column md:flex-row justify-content-between align-items-start md:align-items-center gap-3 mb-4"
3737
3800
  >
3738
3801
  <div>
3739
3802
  <h5 class="m-0 mb-1">Role Assignments</h5>
3740
- <p class="text-sm text-muted-color m-0">
3803
+ <p class="text-sm text-color-secondary m-0">
3741
3804
  {{ roles().length }} roles available
3742
3805
  </p>
3743
3806
  </div>
@@ -3759,7 +3822,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3759
3822
  (onClick)="deselectAll()"
3760
3823
  />
3761
3824
  <p-button
3762
- *hasPermission="USER_ROLE_PERMISSIONS.ASSIGN"
3763
3825
  label="Save Changes"
3764
3826
  icon="pi pi-save"
3765
3827
  [disabled]="!canSave()"
@@ -3772,78 +3834,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3772
3834
  </div>
3773
3835
 
3774
3836
  <!-- Role Table -->
3775
- <div class="overflow-x-auto -mx-4 sm:mx-0">
3776
- <p-table
3777
- [value]="roles()"
3778
- [rows]="10"
3779
- [paginator]="roles().length > 10"
3780
- [rowsPerPageOptions]="[10, 20, 50]"
3781
- [globalFilterFields]="['name', 'code', 'description']"
3782
- [showCurrentPageReport]="true"
3783
- currentPageReportTemplate="Showing {first} to {last} of {totalRecords} roles"
3784
- styleClass="p-datatable-sm"
3785
- [tableStyle]="{ 'min-width': '35rem' }"
3786
- >
3787
- <ng-template #header>
3788
- <tr>
3789
- <th style="width: 3rem">
3790
- <p-checkbox
3791
- [ngModel]="allSelected()"
3792
- [binary]="true"
3793
- (ngModelChange)="toggleAll()"
3794
- pTooltip="Select/Deselect All"
3795
- tooltipPosition="top"
3796
- />
3797
- </th>
3798
- <th>Name</th>
3799
- <th class="hidden sm:table-cell">Code</th>
3800
- <th class="hidden md:table-cell">Description</th>
3801
- </tr>
3802
- </ng-template>
3803
- <ng-template #body let-role>
3804
- <tr>
3805
- <td>
3806
- <p-checkbox
3807
- [ngModel]="selectionMap()[role.id]"
3808
- [binary]="true"
3809
- (ngModelChange)="onRoleToggle(role, $event)"
3810
- [pTooltip]="getTooltip(role)"
3811
- tooltipPosition="top"
3812
- />
3813
- </td>
3814
- <td>{{ role.name }}</td>
3815
- <td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
3816
- <td class="hidden md:table-cell">{{ role.description || '-' }}</td>
3817
- </tr>
3818
- </ng-template>
3819
- <ng-template #emptymessage>
3820
- <tr>
3821
- <td colspan="4" class="text-center p-4 text-muted-color">
3822
- @if (loading()) {
3823
- <i class="pi pi-spin pi-spinner"></i> Loading roles...
3824
- } @else {
3825
- No roles available.
3826
- }
3827
- </td>
3828
- </tr>
3829
- </ng-template>
3830
- </p-table>
3831
- </div>
3837
+ <p-table
3838
+ [value]="roles()"
3839
+ [rows]="10"
3840
+ [paginator]="roles().length > 10"
3841
+ [rowsPerPageOptions]="[10, 20, 50]"
3842
+ [globalFilterFields]="['name', 'code', 'description']"
3843
+ [showCurrentPageReport]="true"
3844
+ currentPageReportTemplate="Showing {first} to {last} of {totalRecords} roles"
3845
+ styleClass="p-datatable-sm"
3846
+ >
3847
+ <ng-template #header>
3848
+ <tr>
3849
+ <th style="width: 3rem">
3850
+ <p-checkbox
3851
+ [ngModel]="allSelected()"
3852
+ [binary]="true"
3853
+ (ngModelChange)="toggleAll()"
3854
+ pTooltip="Select/Deselect All"
3855
+ tooltipPosition="top"
3856
+ />
3857
+ </th>
3858
+ <th>Name</th>
3859
+ <th>Code</th>
3860
+ <th>Description</th>
3861
+ </tr>
3862
+ </ng-template>
3863
+ <ng-template #body let-role>
3864
+ <tr>
3865
+ <td>
3866
+ <p-checkbox
3867
+ [ngModel]="selectionMap()[role.id]"
3868
+ [binary]="true"
3869
+ (ngModelChange)="onRoleToggle(role, $event)"
3870
+ [pTooltip]="getTooltip(role)"
3871
+ tooltipPosition="top"
3872
+ />
3873
+ </td>
3874
+ <td>{{ role.name }}</td>
3875
+ <td>{{ role.code || '-' }}</td>
3876
+ <td>{{ role.description || '-' }}</td>
3877
+ </tr>
3878
+ </ng-template>
3879
+ <ng-template #emptymessage>
3880
+ <tr>
3881
+ <td colspan="4" class="text-center p-4">
3882
+ @if (loading()) {
3883
+ <i class="pi pi-spin pi-spinner"></i> Loading roles...
3884
+ } @else {
3885
+ No roles available.
3886
+ }
3887
+ </td>
3888
+ </tr>
3889
+ </ng-template>
3890
+ </p-table>
3832
3891
  </div>
3833
3892
 
3834
3893
  <!-- Change Summary -->
3835
3894
  @if (hasChanges()) {
3836
- <div class="border border-surface rounded-border p-3 mt-4">
3837
- <div class="flex items-center gap-2 mb-3">
3838
- <i class="pi pi-info-circle text-primary"></i>
3839
- <span class="font-bold">Pending Changes</span>
3895
+ <div class="surface-border border-1 border-round p-3 mt-4">
3896
+ <div class="flex align-items-center gap-2 mb-3">
3897
+ <i class="pi pi-info-circle text-primary content-center"></i>
3898
+ <p class="m-0 mb-1 font-bold">Pending Changes</p>
3840
3899
  </div>
3841
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
3900
+ <div class="flex flex-col md:flex-row gap-4">
3842
3901
  @if (pendingAdd().length > 0) {
3843
- <div>
3844
- <div class="flex items-center gap-2 mb-2">
3845
- <i class="pi pi-plus-circle text-green-500"></i>
3846
- <strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
3902
+ <div class="w-full md:w-1/2">
3903
+ <div class="flex align-items-center gap-2 mb-2">
3904
+ <i class="pi pi-plus-circle text-success"></i>
3905
+ <strong class="text-sm"
3906
+ >To Assign ({{ pendingAdd().length }})</strong
3907
+ >
3847
3908
  </div>
3848
3909
  <ul class="list-none p-0 m-0 pl-4">
3849
3910
  @for (role of pendingAdd(); track role.id) {
@@ -3853,10 +3914,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3853
3914
  </div>
3854
3915
  }
3855
3916
  @if (pendingRemove().length > 0) {
3856
- <div>
3857
- <div class="flex items-center gap-2 mb-2">
3858
- <i class="pi pi-minus-circle text-red-500"></i>
3859
- <strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
3917
+ <div class="w-full md:w-1/2">
3918
+ <div class="flex align-items-center gap-2 mb-2">
3919
+ <i class="pi pi-minus-circle text-danger"></i>
3920
+ <strong class="text-sm"
3921
+ >To Remove ({{ pendingRemove().length }})</strong
3922
+ >
3860
3923
  </div>
3861
3924
  <ul class="list-none p-0 m-0 pl-4">
3862
3925
  @for (role of pendingRemove(); track role.id) {
@@ -3871,9 +3934,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3871
3934
  }
3872
3935
 
3873
3936
  @if (!loading() && roles().length === 0) {
3874
- <div class="surface-card p-5 rounded-border shadow-sm text-center">
3875
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
3876
- <p class="text-muted-color m-0">
3937
+ <div class="surface-card p-5 border-round shadow-1 text-center">
3938
+ <i
3939
+ class="pi pi-info-circle text-color-secondary mb-3"
3940
+ style="font-size: 3rem; display: block;"
3941
+ ></i>
3942
+ <p class="text-color-secondary m-0">
3877
3943
  No roles available for this user.
3878
3944
  </p>
3879
3945
  </div>
@@ -3915,8 +3981,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3915
3981
  * ```
3916
3982
  */
3917
3983
  class UserActionSelectorComponent {
3918
- // Permission constants for template
3919
- USER_ACTION_PERMISSIONS = USER_ACTION_PERMISSIONS;
3920
3984
  // Dependencies
3921
3985
  appConfig = inject(APP_CONFIG);
3922
3986
  companyContext = inject(LAYOUT_AUTH_STATE);
@@ -3925,9 +3989,6 @@ class UserActionSelectorComponent {
3925
3989
  permissionApi = inject(PermissionApiService);
3926
3990
  permissionLogic = inject(ActionPermissionLogicService);
3927
3991
  messageService = inject(MessageService);
3928
- destroyRef = inject(DestroyRef);
3929
- // AbortController for data loading
3930
- loadDataAbortController = null;
3931
3992
  // State - User/Branch Selection
3932
3993
  selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
3933
3994
  selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
@@ -3969,11 +4030,7 @@ class UserActionSelectorComponent {
3969
4030
  hasChanges = computed(() => {
3970
4031
  const current = this.selectionMap();
3971
4032
  const initial = this.initialSelection();
3972
- const currentKeys = Object.keys(current);
3973
- const initialKeys = Object.keys(initial);
3974
- if (currentKeys.length !== initialKeys.length)
3975
- return true;
3976
- return currentKeys.some((key) => current[key] !== initial[key]);
4033
+ return JSON.stringify(current) !== JSON.stringify(initial);
3977
4034
  }, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
3978
4035
  // Computed - Validation
3979
4036
  actionsWithUnmetPrerequisites = computed(() => {
@@ -4009,10 +4066,6 @@ class UserActionSelectorComponent {
4009
4066
  return (this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0);
4010
4067
  }, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
4011
4068
  constructor() {
4012
- // Cleanup on destroy
4013
- this.destroyRef.onDestroy(() => {
4014
- this.loadDataAbortController?.abort();
4015
- });
4016
4069
  // Effect: Load user branches and data when user changes
4017
4070
  effect(() => {
4018
4071
  const userId = this.selectedUserId();
@@ -4060,7 +4113,11 @@ class UserActionSelectorComponent {
4060
4113
  this.branches.set(userBranches);
4061
4114
  }
4062
4115
  catch {
4063
- // Error toast handled by global interceptor
4116
+ this.messageService.add({
4117
+ severity: 'error',
4118
+ summary: 'Error',
4119
+ detail: 'Failed to load user permitted branches',
4120
+ });
4064
4121
  }
4065
4122
  }
4066
4123
  /**
@@ -4101,7 +4158,11 @@ class UserActionSelectorComponent {
4101
4158
  this._initialSelection.set({ ...selMap });
4102
4159
  }
4103
4160
  catch {
4104
- // Error toast handled by global interceptor
4161
+ this.messageService.add({
4162
+ severity: 'error',
4163
+ summary: 'Error',
4164
+ detail: 'Failed to load user action permissions',
4165
+ });
4105
4166
  }
4106
4167
  finally {
4107
4168
  this.loading.set(false);
@@ -4136,17 +4197,12 @@ class UserActionSelectorComponent {
4136
4197
  : false;
4137
4198
  }
4138
4199
  /**
4139
- * Handle action toggle with dependency management
4200
+ * Handle action checkbox toggle
4140
4201
  */
4141
4202
  onActionToggle(action, newValue) {
4142
- if (!newValue) {
4143
- // Unchecking - validate dependencies
4144
- this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
4145
- }
4146
- else {
4147
- // Checking - validate prerequisites
4148
- this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
4149
- }
4203
+ const selMap = { ...this.selectionMap() };
4204
+ selMap[action.id] = newValue;
4205
+ this._selectionMap.set(selMap);
4150
4206
  }
4151
4207
  /**
4152
4208
  * Toggle all actions
@@ -4197,12 +4253,6 @@ class UserActionSelectorComponent {
4197
4253
  });
4198
4254
  return;
4199
4255
  }
4200
- // Pre-save validation
4201
- const invalidActions = this.permissionLogic.getActionsWithUnmetPrerequisites(this.selectionMap(), this.actions());
4202
- if (invalidActions.length > 0) {
4203
- this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
4204
- return;
4205
- }
4206
4256
  // Build payload
4207
4257
  const items = [];
4208
4258
  this.pendingAdd().forEach((action) => {
@@ -4242,8 +4292,13 @@ class UserActionSelectorComponent {
4242
4292
  // Update baseline
4243
4293
  this._initialSelection.set({ ...this.selectionMap() });
4244
4294
  }
4245
- catch {
4246
- // Error toast handled by global interceptor
4295
+ catch (err) {
4296
+ const error = err;
4297
+ this.messageService.add({
4298
+ severity: 'error',
4299
+ summary: 'Error',
4300
+ detail: error?.error?.message || 'Failed to update user action permissions',
4301
+ });
4247
4302
  }
4248
4303
  finally {
4249
4304
  this.saving.set(false);
@@ -4262,30 +4317,28 @@ class UserActionSelectorComponent {
4262
4317
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", ngImport: i0, template: `
4263
4318
  <div class="user-action-selector">
4264
4319
  <!-- User and Branch Selectors -->
4265
- <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
4266
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
4267
- <div>
4268
- <label class="block font-semibold mb-2">
4320
+ <div class="surface-card p-4 border-round mb-4 shadow-1">
4321
+ <div class="formgrid grid gap-5">
4322
+ <div class="field col-12 sm:col-6 mb-0">
4323
+ <label class="block font-semibold mb-2 text-900">
4269
4324
  <i class="pi pi-user mr-2 text-primary"></i>
4270
4325
  Select User
4271
4326
  </label>
4272
4327
  <lib-user-select
4273
- [value]="selectedUserId()"
4274
- (valueChange)="selectedUserId.set($event)"
4328
+ [(value)]="selectedUserId"
4275
4329
  [isEditMode]="true"
4276
4330
  placeHolder="Select a user"
4277
4331
  />
4278
4332
  </div>
4279
4333
 
4280
4334
  @if (showBranchSelector()) {
4281
- <div>
4282
- <label class="block font-semibold mb-2">
4335
+ <div class="field col-12 sm:col-6 mb-0">
4336
+ <label class="block font-semibold mb-2 text-900">
4283
4337
  <i class="pi pi-building mr-2 text-primary"></i>
4284
4338
  Select Branch
4285
4339
  </label>
4286
4340
  <p-select
4287
- [ngModel]="selectedBranchId()"
4288
- (ngModelChange)="selectedBranchId.set($event)"
4341
+ [(ngModel)]="selectedBranchId"
4289
4342
  [options]="filteredBranches()"
4290
4343
  optionLabel="name"
4291
4344
  optionValue="id"
@@ -4296,20 +4349,20 @@ class UserActionSelectorComponent {
4296
4349
  >
4297
4350
  <ng-template #selectedItem let-branch>
4298
4351
  @if (branch) {
4299
- <div class="flex items-center gap-2">
4352
+ <div class="flex align-items-center gap-2">
4300
4353
  <i class="pi pi-building text-primary"></i>
4301
4354
  <span class="font-semibold">{{ branch.name }}</span>
4302
4355
  </div>
4303
4356
  }
4304
4357
  </ng-template>
4305
4358
  <ng-template #item let-branch>
4306
- <div class="flex items-center gap-2">
4307
- <i class="pi pi-building text-muted-color"></i>
4359
+ <div class="flex align-items-center gap-2">
4360
+ <i class="pi pi-building text-color-secondary"></i>
4308
4361
  <span>{{ branch.name }}</span>
4309
4362
  </div>
4310
4363
  </ng-template>
4311
4364
  </p-select>
4312
- <small class="text-muted-color block mt-1">
4365
+ <small class="text-color-secondary block mt-1">
4313
4366
  {{ filteredBranches().length }} permitted branch{{
4314
4367
  filteredBranches().length !== 1 ? 'es' : ''
4315
4368
  }}
@@ -4323,20 +4376,20 @@ class UserActionSelectorComponent {
4323
4376
  @if (selectedUserId()) {
4324
4377
  <!-- Loading State -->
4325
4378
  @if (loading()) {
4326
- <div class="flex justify-center p-5">
4327
- <i class="pi pi-spin pi-spinner text-4xl"></i>
4379
+ <div class="flex justify-content-center p-5">
4380
+ <i class="pi pi-spin pi-spinner" style="font-size: 2rem"></i>
4328
4381
  </div>
4329
4382
  }
4330
4383
 
4331
4384
  <!-- Action List -->
4332
4385
  @if (!loading() && actions().length > 0) {
4333
- <div class="surface-card p-4 rounded-border shadow-sm">
4386
+ <div class="surface-card p-4 border-round shadow-1">
4334
4387
  <div
4335
- class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
4388
+ class="flex flex-column md:flex-row justify-content-between align-items-start md:align-items-center gap-3 mb-4"
4336
4389
  >
4337
4390
  <div>
4338
4391
  <h5 class="m-0 mb-1">Direct Action Permissions</h5>
4339
- <p class="text-sm text-muted-color m-0">
4392
+ <p class="text-sm text-color-secondary m-0">
4340
4393
  {{ actions().length }} actions available
4341
4394
  </p>
4342
4395
  </div>
@@ -4358,7 +4411,6 @@ class UserActionSelectorComponent {
4358
4411
  (onClick)="deselectAll()"
4359
4412
  />
4360
4413
  <p-button
4361
- *hasPermission="USER_ACTION_PERMISSIONS.ASSIGN"
4362
4414
  label="Save Changes"
4363
4415
  icon="pi pi-save"
4364
4416
  [disabled]="!canSave()"
@@ -4370,52 +4422,34 @@ class UserActionSelectorComponent {
4370
4422
  </div>
4371
4423
  </div>
4372
4424
 
4373
- <!-- Validation Warning -->
4374
- @if (invalidActionsCount() > 0) {
4375
- <div class="validation-warning rounded-border p-3 mb-4">
4376
- <div class="flex items-start gap-2">
4377
- <i class="pi pi-exclamation-triangle text-xl"></i>
4378
- <div class="flex-1">
4379
- <span class="font-semibold">Validation Warning:</span>
4380
- <p class="text-sm mt-1 mb-0">
4381
- {{ invalidActionsCount() }} selected
4382
- action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
4383
- unmet prerequisites. Fix before saving or use auto-fix on
4384
- save.
4385
- </p>
4386
- </div>
4387
- </div>
4388
- </div>
4389
- }
4390
-
4391
4425
  <!-- Action Tree Table -->
4392
- <div class="overflow-x-auto -mx-4 sm:mx-0">
4393
- <p-treeTable
4394
- [value]="treeNodes()"
4395
- dataKey="id"
4396
- styleClass="p-treetable-sm"
4397
- [tableStyle]="{ 'min-width': '50rem' }"
4398
- >
4399
- <ng-template #header>
4400
- <tr>
4401
- <th class="w-12">
4402
- <p-checkbox
4403
- [ngModel]="allSelected()"
4404
- [binary]="true"
4405
- (ngModelChange)="toggleAll()"
4406
- pTooltip="Select/Deselect All"
4407
- tooltipPosition="top"
4408
- />
4409
- </th>
4410
- <th>Name</th>
4411
- <th class="hidden md:table-cell">Code</th>
4412
- <th>Type</th>
4413
- <th class="hidden lg:table-cell">Description</th>
4414
- </tr>
4415
- </ng-template>
4416
- <ng-template #body let-rowNode let-rowData="rowData">
4417
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
4418
- <td class="w-12">
4426
+ <p-treeTable
4427
+ [value]="treeNodes()"
4428
+ [scrollable]="true"
4429
+ scrollHeight="flex"
4430
+ dataKey="id"
4431
+ styleClass="p-treetable-sm"
4432
+ >
4433
+ <ng-template pTemplate="header">
4434
+ <tr>
4435
+ <th style="width: 3rem">
4436
+ <p-checkbox
4437
+ [ngModel]="allSelected()"
4438
+ [binary]="true"
4439
+ (ngModelChange)="toggleAll()"
4440
+ pTooltip="Select/Deselect All"
4441
+ tooltipPosition="top"
4442
+ />
4443
+ </th>
4444
+ <th>Name</th>
4445
+ <th>Code</th>
4446
+ <th>Type</th>
4447
+ <th>Description</th>
4448
+ </tr>
4449
+ </ng-template>
4450
+ <ng-template pTemplate="body" let-rowNode let-rowData="rowData">
4451
+ <tr>
4452
+ <td style="width: 3rem">
4419
4453
  <p-checkbox
4420
4454
  [ngModel]="selectionMap()[rowData.id]"
4421
4455
  [binary]="true"
@@ -4426,18 +4460,9 @@ class UserActionSelectorComponent {
4426
4460
  </td>
4427
4461
  <td>
4428
4462
  <p-treeTableToggler [rowNode]="rowNode" />
4429
- <span class="inline-flex items-center gap-2">
4430
- {{ rowData.name }}
4431
- @if (hasUnmetPrerequisites(rowData)) {
4432
- <i
4433
- class="pi pi-exclamation-triangle text-orange-500"
4434
- pTooltip="This action has unmet prerequisites and will fail validation on save"
4435
- tooltipPosition="top"
4436
- ></i>
4437
- }
4438
- </span>
4463
+ <span>{{ rowData.name }}</span>
4439
4464
  </td>
4440
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
4465
+ <td>{{ rowData.code || '-' }}</td>
4441
4466
  <td>
4442
4467
  <p-tag
4443
4468
  [value]="rowData.actionType"
@@ -4446,12 +4471,12 @@ class UserActionSelectorComponent {
4446
4471
  "
4447
4472
  />
4448
4473
  </td>
4449
- <td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
4474
+ <td>{{ rowData.description || '-' }}</td>
4450
4475
  </tr>
4451
4476
  </ng-template>
4452
- <ng-template #emptymessage>
4477
+ <ng-template pTemplate="emptymessage">
4453
4478
  <tr>
4454
- <td colspan="5" class="text-center p-4 text-muted-color">
4479
+ <td colspan="5" class="text-center p-4">
4455
4480
  @if (loading()) {
4456
4481
  <i class="pi pi-spin pi-spinner"></i> Loading actions...
4457
4482
  } @else {
@@ -4460,23 +4485,24 @@ class UserActionSelectorComponent {
4460
4485
  </td>
4461
4486
  </tr>
4462
4487
  </ng-template>
4463
- </p-treeTable>
4464
- </div>
4488
+ </p-treeTable>
4465
4489
  </div>
4466
4490
 
4467
4491
  <!-- Change Summary -->
4468
4492
  @if (hasChanges()) {
4469
- <div class="border border-surface rounded-border p-3 mt-4">
4470
- <div class="flex items-center gap-2 mb-3">
4471
- <i class="pi pi-info-circle text-primary"></i>
4472
- <span class="font-bold">Pending Changes</span>
4493
+ <div class="surface-border border-1 border-round p-3 mt-4">
4494
+ <div class="flex align-items-center gap-2 mb-3">
4495
+ <i class="pi pi-info-circle text-primary content-center"></i>
4496
+ <p class="m-0 mb-1 font-bold">Pending Changes</p>
4473
4497
  </div>
4474
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
4498
+ <div class="flex flex-col md:flex-row gap-4">
4475
4499
  @if (pendingAdd().length > 0) {
4476
- <div>
4477
- <div class="flex items-center gap-2 mb-2">
4478
- <i class="pi pi-plus-circle text-green-500"></i>
4479
- <strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
4500
+ <div class="w-full md:w-1/2">
4501
+ <div class="flex align-items-center gap-2 mb-2">
4502
+ <i class="pi pi-plus-circle text-success"></i>
4503
+ <strong class="text-sm"
4504
+ >To Assign ({{ pendingAdd().length }})</strong
4505
+ >
4480
4506
  </div>
4481
4507
  <ul class="list-none p-0 m-0 pl-4">
4482
4508
  @for (action of pendingAdd(); track action.id) {
@@ -4486,10 +4512,12 @@ class UserActionSelectorComponent {
4486
4512
  </div>
4487
4513
  }
4488
4514
  @if (pendingRemove().length > 0) {
4489
- <div>
4490
- <div class="flex items-center gap-2 mb-2">
4491
- <i class="pi pi-minus-circle text-red-500"></i>
4492
- <strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
4515
+ <div class="w-full md:w-1/2">
4516
+ <div class="flex align-items-center gap-2 mb-2">
4517
+ <i class="pi pi-minus-circle text-danger"></i>
4518
+ <strong class="text-sm"
4519
+ >To Remove ({{ pendingRemove().length }})</strong
4520
+ >
4493
4521
  </div>
4494
4522
  <ul class="list-none p-0 m-0 pl-4">
4495
4523
  @for (action of pendingRemove(); track action.id) {
@@ -4504,46 +4532,47 @@ class UserActionSelectorComponent {
4504
4532
  }
4505
4533
 
4506
4534
  @if (!loading() && actions().length === 0) {
4507
- <div class="surface-card p-5 rounded-border shadow-sm text-center">
4508
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
4509
- <p class="text-muted-color m-0">
4535
+ <div class="surface-card p-5 border-round shadow-1 text-center">
4536
+ <i
4537
+ class="pi pi-info-circle text-color-secondary mb-3"
4538
+ style="font-size: 3rem; display: block;"
4539
+ ></i>
4540
+ <p class="text-color-secondary m-0">
4510
4541
  No actions available for this user.
4511
4542
  </p>
4512
4543
  </div>
4513
4544
  }
4514
4545
  }
4515
4546
  </div>
4516
- `, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\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: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["loadUsers", "placeHolder", "isEditMode", "filterActive", "additionalFilters", "pageSize", "value"], outputs: ["valueChange", "userSelected", "onError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4547
+ `, 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: PrimeModule }, { kind: "directive", type: i2$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i6.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i8.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i8.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["loadUsers", "placeHolder", "isEditMode", "filterActive", "additionalFilters", "pageSize", "value"], outputs: ["valueChange", "userSelected", "onError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4517
4548
  }
4518
4549
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
4519
4550
  type: Component,
4520
- args: [{ selector: 'flusys-user-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
4551
+ args: [{ selector: 'flusys-user-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, UserSelectComponent], template: `
4521
4552
  <div class="user-action-selector">
4522
4553
  <!-- User and Branch Selectors -->
4523
- <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
4524
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
4525
- <div>
4526
- <label class="block font-semibold mb-2">
4554
+ <div class="surface-card p-4 border-round mb-4 shadow-1">
4555
+ <div class="formgrid grid gap-5">
4556
+ <div class="field col-12 sm:col-6 mb-0">
4557
+ <label class="block font-semibold mb-2 text-900">
4527
4558
  <i class="pi pi-user mr-2 text-primary"></i>
4528
4559
  Select User
4529
4560
  </label>
4530
4561
  <lib-user-select
4531
- [value]="selectedUserId()"
4532
- (valueChange)="selectedUserId.set($event)"
4562
+ [(value)]="selectedUserId"
4533
4563
  [isEditMode]="true"
4534
4564
  placeHolder="Select a user"
4535
4565
  />
4536
4566
  </div>
4537
4567
 
4538
4568
  @if (showBranchSelector()) {
4539
- <div>
4540
- <label class="block font-semibold mb-2">
4569
+ <div class="field col-12 sm:col-6 mb-0">
4570
+ <label class="block font-semibold mb-2 text-900">
4541
4571
  <i class="pi pi-building mr-2 text-primary"></i>
4542
4572
  Select Branch
4543
4573
  </label>
4544
4574
  <p-select
4545
- [ngModel]="selectedBranchId()"
4546
- (ngModelChange)="selectedBranchId.set($event)"
4575
+ [(ngModel)]="selectedBranchId"
4547
4576
  [options]="filteredBranches()"
4548
4577
  optionLabel="name"
4549
4578
  optionValue="id"
@@ -4554,20 +4583,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4554
4583
  >
4555
4584
  <ng-template #selectedItem let-branch>
4556
4585
  @if (branch) {
4557
- <div class="flex items-center gap-2">
4586
+ <div class="flex align-items-center gap-2">
4558
4587
  <i class="pi pi-building text-primary"></i>
4559
4588
  <span class="font-semibold">{{ branch.name }}</span>
4560
4589
  </div>
4561
4590
  }
4562
4591
  </ng-template>
4563
4592
  <ng-template #item let-branch>
4564
- <div class="flex items-center gap-2">
4565
- <i class="pi pi-building text-muted-color"></i>
4593
+ <div class="flex align-items-center gap-2">
4594
+ <i class="pi pi-building text-color-secondary"></i>
4566
4595
  <span>{{ branch.name }}</span>
4567
4596
  </div>
4568
4597
  </ng-template>
4569
4598
  </p-select>
4570
- <small class="text-muted-color block mt-1">
4599
+ <small class="text-color-secondary block mt-1">
4571
4600
  {{ filteredBranches().length }} permitted branch{{
4572
4601
  filteredBranches().length !== 1 ? 'es' : ''
4573
4602
  }}
@@ -4581,20 +4610,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4581
4610
  @if (selectedUserId()) {
4582
4611
  <!-- Loading State -->
4583
4612
  @if (loading()) {
4584
- <div class="flex justify-center p-5">
4585
- <i class="pi pi-spin pi-spinner text-4xl"></i>
4613
+ <div class="flex justify-content-center p-5">
4614
+ <i class="pi pi-spin pi-spinner" style="font-size: 2rem"></i>
4586
4615
  </div>
4587
4616
  }
4588
4617
 
4589
4618
  <!-- Action List -->
4590
4619
  @if (!loading() && actions().length > 0) {
4591
- <div class="surface-card p-4 rounded-border shadow-sm">
4620
+ <div class="surface-card p-4 border-round shadow-1">
4592
4621
  <div
4593
- class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
4622
+ class="flex flex-column md:flex-row justify-content-between align-items-start md:align-items-center gap-3 mb-4"
4594
4623
  >
4595
4624
  <div>
4596
4625
  <h5 class="m-0 mb-1">Direct Action Permissions</h5>
4597
- <p class="text-sm text-muted-color m-0">
4626
+ <p class="text-sm text-color-secondary m-0">
4598
4627
  {{ actions().length }} actions available
4599
4628
  </p>
4600
4629
  </div>
@@ -4616,7 +4645,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4616
4645
  (onClick)="deselectAll()"
4617
4646
  />
4618
4647
  <p-button
4619
- *hasPermission="USER_ACTION_PERMISSIONS.ASSIGN"
4620
4648
  label="Save Changes"
4621
4649
  icon="pi pi-save"
4622
4650
  [disabled]="!canSave()"
@@ -4628,52 +4656,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4628
4656
  </div>
4629
4657
  </div>
4630
4658
 
4631
- <!-- Validation Warning -->
4632
- @if (invalidActionsCount() > 0) {
4633
- <div class="validation-warning rounded-border p-3 mb-4">
4634
- <div class="flex items-start gap-2">
4635
- <i class="pi pi-exclamation-triangle text-xl"></i>
4636
- <div class="flex-1">
4637
- <span class="font-semibold">Validation Warning:</span>
4638
- <p class="text-sm mt-1 mb-0">
4639
- {{ invalidActionsCount() }} selected
4640
- action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
4641
- unmet prerequisites. Fix before saving or use auto-fix on
4642
- save.
4643
- </p>
4644
- </div>
4645
- </div>
4646
- </div>
4647
- }
4648
-
4649
4659
  <!-- Action Tree Table -->
4650
- <div class="overflow-x-auto -mx-4 sm:mx-0">
4651
- <p-treeTable
4652
- [value]="treeNodes()"
4653
- dataKey="id"
4654
- styleClass="p-treetable-sm"
4655
- [tableStyle]="{ 'min-width': '50rem' }"
4656
- >
4657
- <ng-template #header>
4658
- <tr>
4659
- <th class="w-12">
4660
- <p-checkbox
4661
- [ngModel]="allSelected()"
4662
- [binary]="true"
4663
- (ngModelChange)="toggleAll()"
4664
- pTooltip="Select/Deselect All"
4665
- tooltipPosition="top"
4666
- />
4667
- </th>
4668
- <th>Name</th>
4669
- <th class="hidden md:table-cell">Code</th>
4670
- <th>Type</th>
4671
- <th class="hidden lg:table-cell">Description</th>
4672
- </tr>
4673
- </ng-template>
4674
- <ng-template #body let-rowNode let-rowData="rowData">
4675
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
4676
- <td class="w-12">
4660
+ <p-treeTable
4661
+ [value]="treeNodes()"
4662
+ [scrollable]="true"
4663
+ scrollHeight="flex"
4664
+ dataKey="id"
4665
+ styleClass="p-treetable-sm"
4666
+ >
4667
+ <ng-template pTemplate="header">
4668
+ <tr>
4669
+ <th style="width: 3rem">
4670
+ <p-checkbox
4671
+ [ngModel]="allSelected()"
4672
+ [binary]="true"
4673
+ (ngModelChange)="toggleAll()"
4674
+ pTooltip="Select/Deselect All"
4675
+ tooltipPosition="top"
4676
+ />
4677
+ </th>
4678
+ <th>Name</th>
4679
+ <th>Code</th>
4680
+ <th>Type</th>
4681
+ <th>Description</th>
4682
+ </tr>
4683
+ </ng-template>
4684
+ <ng-template pTemplate="body" let-rowNode let-rowData="rowData">
4685
+ <tr>
4686
+ <td style="width: 3rem">
4677
4687
  <p-checkbox
4678
4688
  [ngModel]="selectionMap()[rowData.id]"
4679
4689
  [binary]="true"
@@ -4684,18 +4694,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4684
4694
  </td>
4685
4695
  <td>
4686
4696
  <p-treeTableToggler [rowNode]="rowNode" />
4687
- <span class="inline-flex items-center gap-2">
4688
- {{ rowData.name }}
4689
- @if (hasUnmetPrerequisites(rowData)) {
4690
- <i
4691
- class="pi pi-exclamation-triangle text-orange-500"
4692
- pTooltip="This action has unmet prerequisites and will fail validation on save"
4693
- tooltipPosition="top"
4694
- ></i>
4695
- }
4696
- </span>
4697
+ <span>{{ rowData.name }}</span>
4697
4698
  </td>
4698
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
4699
+ <td>{{ rowData.code || '-' }}</td>
4699
4700
  <td>
4700
4701
  <p-tag
4701
4702
  [value]="rowData.actionType"
@@ -4704,12 +4705,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4704
4705
  "
4705
4706
  />
4706
4707
  </td>
4707
- <td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
4708
+ <td>{{ rowData.description || '-' }}</td>
4708
4709
  </tr>
4709
4710
  </ng-template>
4710
- <ng-template #emptymessage>
4711
+ <ng-template pTemplate="emptymessage">
4711
4712
  <tr>
4712
- <td colspan="5" class="text-center p-4 text-muted-color">
4713
+ <td colspan="5" class="text-center p-4">
4713
4714
  @if (loading()) {
4714
4715
  <i class="pi pi-spin pi-spinner"></i> Loading actions...
4715
4716
  } @else {
@@ -4718,23 +4719,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4718
4719
  </td>
4719
4720
  </tr>
4720
4721
  </ng-template>
4721
- </p-treeTable>
4722
- </div>
4722
+ </p-treeTable>
4723
4723
  </div>
4724
4724
 
4725
4725
  <!-- Change Summary -->
4726
4726
  @if (hasChanges()) {
4727
- <div class="border border-surface rounded-border p-3 mt-4">
4728
- <div class="flex items-center gap-2 mb-3">
4729
- <i class="pi pi-info-circle text-primary"></i>
4730
- <span class="font-bold">Pending Changes</span>
4727
+ <div class="surface-border border-1 border-round p-3 mt-4">
4728
+ <div class="flex align-items-center gap-2 mb-3">
4729
+ <i class="pi pi-info-circle text-primary content-center"></i>
4730
+ <p class="m-0 mb-1 font-bold">Pending Changes</p>
4731
4731
  </div>
4732
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
4732
+ <div class="flex flex-col md:flex-row gap-4">
4733
4733
  @if (pendingAdd().length > 0) {
4734
- <div>
4735
- <div class="flex items-center gap-2 mb-2">
4736
- <i class="pi pi-plus-circle text-green-500"></i>
4737
- <strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
4734
+ <div class="w-full md:w-1/2">
4735
+ <div class="flex align-items-center gap-2 mb-2">
4736
+ <i class="pi pi-plus-circle text-success"></i>
4737
+ <strong class="text-sm"
4738
+ >To Assign ({{ pendingAdd().length }})</strong
4739
+ >
4738
4740
  </div>
4739
4741
  <ul class="list-none p-0 m-0 pl-4">
4740
4742
  @for (action of pendingAdd(); track action.id) {
@@ -4744,10 +4746,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4744
4746
  </div>
4745
4747
  }
4746
4748
  @if (pendingRemove().length > 0) {
4747
- <div>
4748
- <div class="flex items-center gap-2 mb-2">
4749
- <i class="pi pi-minus-circle text-red-500"></i>
4750
- <strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
4749
+ <div class="w-full md:w-1/2">
4750
+ <div class="flex align-items-center gap-2 mb-2">
4751
+ <i class="pi pi-minus-circle text-danger"></i>
4752
+ <strong class="text-sm"
4753
+ >To Remove ({{ pendingRemove().length }})</strong
4754
+ >
4751
4755
  </div>
4752
4756
  <ul class="list-none p-0 m-0 pl-4">
4753
4757
  @for (action of pendingRemove(); track action.id) {
@@ -4762,16 +4766,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4762
4766
  }
4763
4767
 
4764
4768
  @if (!loading() && actions().length === 0) {
4765
- <div class="surface-card p-5 rounded-border shadow-sm text-center">
4766
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
4767
- <p class="text-muted-color m-0">
4769
+ <div class="surface-card p-5 border-round shadow-1 text-center">
4770
+ <i
4771
+ class="pi pi-info-circle text-color-secondary mb-3"
4772
+ style="font-size: 3rem; display: block;"
4773
+ ></i>
4774
+ <p class="text-color-secondary m-0">
4768
4775
  No actions available for this user.
4769
4776
  </p>
4770
4777
  </div>
4771
4778
  }
4772
4779
  }
4773
4780
  </div>
4774
- `, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
4781
+ `, styles: [":host{display:block}\n"] }]
4775
4782
  }], ctorParameters: () => [] });
4776
4783
 
4777
4784
  // Logic Builder Component
@@ -4870,7 +4877,7 @@ function provideIamProviders() {
4870
4877
  const IAM_ROUTES = [
4871
4878
  {
4872
4879
  path: '',
4873
- loadComponent: () => import('./flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs').then((m) => m.IamContainerComponent),
4880
+ loadComponent: () => import('./flusys-ng-iam-iam-container.component-BkhqmzLi.mjs').then((m) => m.IamContainerComponent),
4874
4881
  children: [
4875
4882
  // Actions Management
4876
4883
  {
@@ -4878,15 +4885,15 @@ const IAM_ROUTES = [
4878
4885
  children: [
4879
4886
  {
4880
4887
  path: '',
4881
- loadComponent: () => import('./flusys-ng-iam-action-list-page.component-Daf93zpS.mjs').then((m) => m.ActionListPageComponent),
4888
+ loadComponent: () => import('./flusys-ng-iam-action-list-page.component-BCzSardO.mjs').then((m) => m.ActionListPageComponent),
4882
4889
  },
4883
4890
  {
4884
4891
  path: 'new',
4885
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs').then((m) => m.ActionFormPageComponent),
4892
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C1j10Qhw.mjs').then((m) => m.ActionFormPageComponent),
4886
4893
  },
4887
4894
  {
4888
4895
  path: ':id',
4889
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs').then((m) => m.ActionFormPageComponent),
4896
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C1j10Qhw.mjs').then((m) => m.ActionFormPageComponent),
4890
4897
  },
4891
4898
  ],
4892
4899
  },
@@ -4896,22 +4903,22 @@ const IAM_ROUTES = [
4896
4903
  children: [
4897
4904
  {
4898
4905
  path: '',
4899
- loadComponent: () => import('./flusys-ng-iam-role-list-page.component-CFly5KnH.mjs').then((m) => m.RoleListPageComponent),
4906
+ loadComponent: () => import('./flusys-ng-iam-role-list-page.component-BObCxHiB.mjs').then((m) => m.RoleListPageComponent),
4900
4907
  },
4901
4908
  {
4902
4909
  path: 'new',
4903
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-ByNueI1a.mjs').then((m) => m.RoleFormPageComponent),
4910
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-Cqziu_BM.mjs').then((m) => m.RoleFormPageComponent),
4904
4911
  },
4905
4912
  {
4906
4913
  path: ':id',
4907
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-ByNueI1a.mjs').then((m) => m.RoleFormPageComponent),
4914
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-Cqziu_BM.mjs').then((m) => m.RoleFormPageComponent),
4908
4915
  },
4909
4916
  ],
4910
4917
  },
4911
4918
  // Permissions Management (User permission assignment)
4912
4919
  {
4913
4920
  path: 'permissions',
4914
- loadComponent: () => import('./flusys-ng-iam-permission-page.component-CmxOBJPu.mjs').then((m) => m.PermissionPageComponent),
4921
+ loadComponent: () => import('./flusys-ng-iam-permission-page.component-BSQFPt_N.mjs').then((m) => m.PermissionPageComponent),
4915
4922
  },
4916
4923
  // Default redirect to actions
4917
4924
  {
@@ -4930,4 +4937,4 @@ const IAM_ROUTES = [
4930
4937
  */
4931
4938
 
4932
4939
  export { ActionApiService as A, CompanyActionSelectorComponent as C, IAM_ROUTES 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, MyPermissionsApiService as f, PermissionStateService as g, ProfilePermissionProviderAdapter as h, provideIamProviders as p };
4933
- //# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs.map
4940
+ //# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-DISrddPh.mjs.map