Implement organization maker-checker workflows
This commit is contained in:
+28
-2
@@ -7,6 +7,26 @@
|
||||
-->
|
||||
|
||||
<div class="layout-row align-end gap-2px responsive-column container m-b-20">
|
||||
<mat-form-field class="mc-search-field">
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
placeholder="Search permissions..."
|
||||
[value]="searchTerm"
|
||||
(input)="onSearchTermChange($any($event.target).value)"
|
||||
/>
|
||||
@if (isSearchActive) {
|
||||
<button
|
||||
class="mc-clear-search"
|
||||
matSuffix
|
||||
type="button"
|
||||
(click)="clearSearch()"
|
||||
[attr.aria-label]="'Clear search'"
|
||||
>
|
||||
<fa-icon icon="times"></fa-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
<div #buttonEdit class="in-block">
|
||||
<button
|
||||
mat-raised-button
|
||||
@@ -41,14 +61,20 @@
|
||||
|
||||
<mat-divider [vertical]="true"></mat-divider>
|
||||
<div class="list-permission layout-column flex-70">
|
||||
@if (isSearchActive && displayedPermissions.length === 0) {
|
||||
<p class="no-results">No permissions found.</p>
|
||||
}
|
||||
<form [formGroup]="formGroup" (submit)="submit()">
|
||||
<div formArrayName="roster">
|
||||
@for (permission of permissions.permissions; track permission; let i = $index) {
|
||||
@for (permission of displayedPermissions; track permission.code) {
|
||||
<div class="display-permissions">
|
||||
<div [formGroupName]="i">
|
||||
<div [formGroupName]="permission.globalIndex">
|
||||
<mat-checkbox name="cp" id="{{ permission.code }}" formControlName="selected">
|
||||
{{ permissionName(permission.code) }}
|
||||
</mat-checkbox>
|
||||
@if (isSearchActive) {
|
||||
<span class="search-result-grouping">({{ formatName(permission.grouping || '') }})</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
+29
@@ -46,3 +46,32 @@ mat-list-item {
|
||||
.inactive:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.mc-search-field {
|
||||
width: 260px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.mc-clear-search {
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.mc-clear-search:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
padding-top: 15px;
|
||||
font-style: italic;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.search-result-grouping {
|
||||
padding-left: 6px;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
+54
-3
@@ -27,6 +27,11 @@ interface Permission {
|
||||
id: number;
|
||||
selected: boolean;
|
||||
grouping?: string;
|
||||
/** Index of this permission's control within the `roster` FormArray (which is built from the
|
||||
* full, globally-ordered permission list). Needed because the checkbox list on screen is
|
||||
* rendered from a per-grouping filtered subset, whose own local index does not match the
|
||||
* roster's global index. */
|
||||
globalIndex?: number;
|
||||
}
|
||||
|
||||
interface PermissionGroup {
|
||||
@@ -70,6 +75,12 @@ export class ConfigureMakerCheckerTasksComponent implements OnInit, AfterViewIni
|
||||
currentGrouping = '';
|
||||
tempPermissionUIData: PermissionData = {};
|
||||
permissions: PermissionGroup = { permissions: [] };
|
||||
/** Flat list of every permission across all groupings, built once alongside
|
||||
* tempPermissionUIData. Used only for global search - it is a read-only view over the same
|
||||
* Permission objects (each still carrying its correct globalIndex), never a separate copy
|
||||
* that could drift out of sync with the roster FormArray. */
|
||||
allPermissions: Permission[] = [];
|
||||
searchTerm = '';
|
||||
|
||||
formData = {};
|
||||
isDisabled = true;
|
||||
@@ -137,22 +148,30 @@ export class ConfigureMakerCheckerTasksComponent implements OnInit, AfterViewIni
|
||||
|
||||
setMakerCheckerTask(): void {
|
||||
this.tempPermissionUIData = {};
|
||||
this.allPermissions = [];
|
||||
|
||||
for (const permission of this.permissionsData) {
|
||||
this.permissionsData.forEach((permission: any, globalIndex: number) => {
|
||||
if (permission.grouping !== this.currentGrouping) {
|
||||
this.currentGrouping = permission.grouping || '';
|
||||
this.groupings.push(this.currentGrouping);
|
||||
this.tempPermissionUIData[this.currentGrouping] = { permissions: [] };
|
||||
}
|
||||
|
||||
// globalIndex matches this permission's position in `this.formGroup.controls.roster`,
|
||||
// since both this list and `roster` are built from the same `this.permissionsData` array
|
||||
// in the same order (see createForm()). The checkbox must address the roster control by
|
||||
// this global index, never by its local position within the current grouping's filtered view.
|
||||
const temp: Permission = {
|
||||
code: permission.code,
|
||||
id: permission.id,
|
||||
selected: permission.selected
|
||||
selected: permission.selected,
|
||||
grouping: this.currentGrouping,
|
||||
globalIndex
|
||||
};
|
||||
|
||||
this.tempPermissionUIData[this.currentGrouping].permissions.push(temp);
|
||||
}
|
||||
this.allPermissions.push(temp);
|
||||
});
|
||||
}
|
||||
|
||||
showPermissions(grouping: string): void {
|
||||
@@ -164,6 +183,38 @@ export class ConfigureMakerCheckerTasksComponent implements OnInit, AfterViewIni
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The permissions currently rendered as checkboxes. While a search term is active this is a
|
||||
* global, cross-grouping match over `allPermissions`; otherwise it is the normal per-grouping
|
||||
* view. Either way each item still carries its original globalIndex, so the checkbox template
|
||||
* always resolves the same roster control regardless of which list produced it.
|
||||
*/
|
||||
get displayedPermissions(): Permission[] {
|
||||
if (!this.isSearchActive) {
|
||||
return this.permissions.permissions;
|
||||
}
|
||||
const term = this.searchTerm.trim().toLowerCase();
|
||||
return this.allPermissions.filter((permission) => this.matchesSearch(permission, term));
|
||||
}
|
||||
|
||||
get isSearchActive(): boolean {
|
||||
return this.searchTerm.trim().length > 0;
|
||||
}
|
||||
|
||||
onSearchTermChange(value: string): void {
|
||||
this.searchTerm = value;
|
||||
}
|
||||
|
||||
clearSearch(): void {
|
||||
this.searchTerm = '';
|
||||
}
|
||||
|
||||
private matchesSearch(permission: Permission, term: string): boolean {
|
||||
const readableName = permission.code.replace(/_/g, ' ').toLowerCase();
|
||||
const haystack = `${permission.code.toLowerCase()} ${readableName}`;
|
||||
return haystack.includes(term);
|
||||
}
|
||||
|
||||
permissionName = function (name: any) {
|
||||
name = name || '';
|
||||
// replace '_' with ' '
|
||||
|
||||
Reference in New Issue
Block a user