src/app/core/user/user-list/user-list.component.ts
| changeDetection | ChangeDetectionStrategy.OnPush |
| selector | app-user-list |
| standalone | true |
| imports |
MatTableModule
MatButtonModule
MatPaginatorModule
|
| styleUrls | ./user-list.component.scss |
| templateUrl | ./user-list.component.html |
| styleUrl | ./user-list.component.scss |
No results matching.
ViewTitleComponent
MatTableModule
MatButtonModule
FaIconComponent
EntityBlockComponent
MatPaginatorModule
OnInit
Properties |
Methods |
| addNewAccount |
addNewAccount()
|
|
Returns :
void
|
| getRoleNames | ||||||
getRoleNames(userAccount: UserAccount)
|
||||||
|
Parameters :
Returns :
string
|
| loadUsers |
loadUsers()
|
|
Returns :
void
|
| onPageChange | ||||||
onPageChange(event: PageEvent)
|
||||||
|
Parameters :
Returns :
void
|
| openUserDetails | ||||||
openUserDetails(user: UserAccount | null)
|
||||||
|
Parameters :
Returns :
void
|
| displayedColumns |
Type : string[]
|
Default value : [
"email",
"userEntityId",
"enabled",
"emailVerified",
"roles",
]
|
| hasUserManagementRole |
Type : unknown
|
Default value : this.sessionInfo.value?.roles.includes(
UserAdminService.ACCOUNT_MANAGER_ROLE,
)
|
| pagedUsers |
Type : unknown
|
Default value : computed(() => {
const start = this.pageIndex() * this.pageSize();
const end = start + this.pageSize();
return this.users().slice(start, end);
})
|
| pageIndex |
Type : unknown
|
Default value : signal(0)
|
| pageSize |
Type : unknown
|
Default value : signal(10)
|
| Readonly pageSizeOptions |
Type : []
|
Default value : [10, 25, 50, 100]
|
| users |
Type : unknown
|
Default value : signal<UserAccount[]>([])
|
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
OnInit,
signal,
} from "@angular/core";
import { UserAdminService } from "../user-admin-service/user-admin.service";
import { MatDialog } from "@angular/material/dialog";
import { SessionSubject } from "../../session/auth/session-info";
import { UserAccount } from "../user-admin-service/user-account";
import { Logging } from "../../logging/logging.service";
import {
UserDetailsAction,
UserDetailsComponent,
UserDetailsDialogData,
} from "../user-details/user-details.component";
import { ViewTitleComponent } from "../../common-components/view-title/view-title.component";
import { MatTableModule } from "@angular/material/table";
import { MatButtonModule } from "@angular/material/button";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { EntityBlockComponent } from "../../basic-datatypes/entity/entity-block/entity-block.component";
import { AlertService } from "../../alerts/alert.service";
import { MatPaginatorModule, PageEvent } from "@angular/material/paginator";
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-user-list",
imports: [
ViewTitleComponent,
MatTableModule,
MatButtonModule,
FaIconComponent,
EntityBlockComponent,
MatPaginatorModule,
],
templateUrl: "./user-list.component.html",
styleUrl: "./user-list.component.scss",
})
export class UserListComponent implements OnInit {
private readonly userAdminService = inject(UserAdminService);
private readonly dialog = inject(MatDialog);
private readonly sessionInfo = inject(SessionSubject);
private readonly alertService = inject(AlertService);
hasUserManagementRole = this.sessionInfo.value?.roles.includes(
UserAdminService.ACCOUNT_MANAGER_ROLE,
);
users = signal<UserAccount[]>([]);
pageIndex = signal(0);
pageSize = signal(10);
readonly pageSizeOptions = [10, 25, 50, 100];
pagedUsers = computed(() => {
const start = this.pageIndex() * this.pageSize();
const end = start + this.pageSize();
return this.users().slice(start, end);
});
displayedColumns: string[] = [
"email",
"userEntityId",
"enabled",
"emailVerified",
"roles",
];
ngOnInit() {
const isOnline = typeof navigator !== "undefined" ? navigator.onLine : true;
if (this.hasUserManagementRole && isOnline) {
this.loadUsers();
} else if (!isOnline) {
this.alertService.addInfo(
$localize`User accounts cannot be loaded while offline.`,
);
}
}
loadUsers() {
this.userAdminService.getAllUsers().subscribe({
next: (users) => {
this.users.set(users);
this.pageIndex.set(0);
},
error: (err) => {
Logging.error("Failed to load users:", err);
this.alertService.addWarning(
$localize`Failed to load users. Please try again later or contact your server administrator.`,
);
},
});
}
onPageChange(event: PageEvent) {
this.pageIndex.set(event.pageIndex);
this.pageSize.set(event.pageSize);
}
getRoleNames(userAccount: UserAccount): string {
return userAccount.roles?.map((r) => r.name).join(", ") || "-";
}
addNewAccount() {
this.openUserDetails(null);
}
openUserDetails(user: UserAccount | null) {
const dialogData: UserDetailsDialogData = {
userAccount: user,
};
const dialogRef = this.dialog.open(UserDetailsComponent, {
data: dialogData,
width: "99%",
});
dialogRef.afterClosed().subscribe((action: UserDetailsAction) => {
if (!action) {
// Dialog was closed without an action (e.g., clicked outside or ESC)
return;
}
switch (action.type) {
case "accountUpdated": {
const updatedUsers = this.users().map((u) =>
u.id === action.data.user.id ? action.data.user : u,
);
this.users.set(updatedUsers);
break;
}
case "accountCreated":
this.loadUsers();
break;
}
});
}
}
<div class="flex-row flex-wrap">
<div class="flex-grow">
<app-view-title i18n>User Management</app-view-title>
</div>
@if (hasUserManagementRole) {
<button
mat-stroked-button
color="accent"
class="standard-add-button"
(click)="addNewAccount()"
>
<fa-icon icon="plus-circle" class="standard-icon-with-text"></fa-icon>
<span i18n>Add New Account</span>
</button>
}
</div>
@if (!hasUserManagementRole) {
<div i18n>
Your account does not have the required permissions to manage user accounts.
</div>
} @else {
<p i18n class="margin-bottom-regular">
This page displays all user accounts registered for your system.
</p>
<div class="table-container">
<table mat-table [dataSource]="pagedUsers()" class="full-width table">
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef i18n>Email</th>
<td mat-cell *matCellDef="let user">{{ user.email || "-" }}</td>
</ng-container>
<ng-container matColumnDef="userEntityId">
<th mat-header-cell *matHeaderCellDef i18n>Profile</th>
<td mat-cell *matCellDef="let user">
@if (user.userEntityId) {
<app-entity-block [entityId]="user.userEntityId"></app-entity-block>
} @else {
<span>-</span>
}
</td>
</ng-container>
<ng-container matColumnDef="enabled">
<th mat-header-cell *matHeaderCellDef i18n>Enabled</th>
<td mat-cell *matCellDef="let user">
@if (user.enabled) {
<span i18n>Yes</span>
} @else {
<span i18n>No</span>
}
</td>
</ng-container>
<ng-container matColumnDef="emailVerified">
<th mat-header-cell *matHeaderCellDef i18n>Email Verified</th>
<td mat-cell *matCellDef="let user">
@if (user.emailVerified) {
<span i18n>Yes</span>
} @else {
<span i18n>No</span>
}
</td>
</ng-container>
<ng-container matColumnDef="roles">
<th mat-header-cell *matHeaderCellDef i18n>Roles</th>
<td mat-cell *matCellDef="let user">{{ getRoleNames(user) }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="openUserDetails(row)"
class="clickable-row"
></tr>
</table>
<mat-paginator
class="table-footer"
[length]="users().length"
[pageIndex]="pageIndex()"
[pageSize]="pageSize()"
[pageSizeOptions]="pageSizeOptions"
[showFirstLastButtons]="true"
(page)="onPageChange($event)"
aria-label="User accounts pagination"
i18n-aria-label
></mat-paginator>
</div>
}
./user-list.component.scss
@use "mixins/list-table";
@include list-table.container-and-table;
@include list-table.footer;
@include list-table.clickable-row;
:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-container {
justify-content: flex-start;
}