diff --git a/APP/src/app/features/users/pages/users-page/users-page.component.html b/APP/src/app/features/users/pages/users-page/users-page.component.html
new file mode 100644
index 0000000..e0d24d1
--- /dev/null
+++ b/APP/src/app/features/users/pages/users-page/users-page.component.html
@@ -0,0 +1,78 @@
+
+
+
User Management
+
+
+
+
+ New temporary password: {{ resetPasswordResult.tempPassword }}
+
+
+ Warning: This will not be shown again.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.memberDisplayName ?? '--' }}
+
+
+
+
+
+ {{ role }}
+
+
+
+
+
+
+ {{ row.isActive ? 'Active' : 'Inactive' }}
+
+
+
+
+
+
+ {{ row.lastLoginAt ? (row.lastLoginAt | date:'MM/dd/yyyy HH:mm') : '--' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/APP/src/app/features/users/pages/users-page/users-page.component.scss b/APP/src/app/features/users/pages/users-page/users-page.component.scss
new file mode 100644
index 0000000..b59aa55
--- /dev/null
+++ b/APP/src/app/features/users/pages/users-page/users-page.component.scss
@@ -0,0 +1,19 @@
+:host { display: block; height: 100%; }
+
+.reset-password-banner {
+ background: #d4edda;
+ border: 1px solid #c3e6cb;
+ border-radius: 4px;
+}
+
+.role-badge {
+ display: inline-block;
+ padding: 1px 6px;
+ background: #cce5ff;
+ color: #004085;
+ border-radius: 10px;
+ font-size: 0.75rem;
+}
+
+.status-active { color: #155724; font-weight: 500; }
+.status-inactive { color: #721c24; font-weight: 500; }
diff --git a/APP/src/app/features/users/pages/users-page/users-page.component.ts b/APP/src/app/features/users/pages/users-page/users-page.component.ts
index 16127ba..c6ac20b 100644
--- a/APP/src/app/features/users/pages/users-page/users-page.component.ts
+++ b/APP/src/app/features/users/pages/users-page/users-page.component.ts
@@ -1,10 +1,103 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { GridModule, PageChangeEvent } from '@progress/kendo-angular-grid';
+import { InputsModule } from '@progress/kendo-angular-inputs';
+import { ButtonsModule } from '@progress/kendo-angular-buttons';
+import { IndicatorsModule } from '@progress/kendo-angular-indicators';
+import { UserApiService } from '../../services/user-api.service';
+import { EditUserDialogComponent } from '../../components/edit-user-dialog/edit-user-dialog.component';
+import {
+ UserListItemDto, UserDto, UpdateUserRequest, PagedResult
+} from '../../models/user.model';
@Component({
selector: 'app-users-page',
standalone: true,
- imports: [CommonModule],
- template: 'User Management
Loading...
',
+ imports: [
+ CommonModule, FormsModule, GridModule, InputsModule,
+ ButtonsModule, IndicatorsModule, EditUserDialogComponent,
+ ],
+ templateUrl: './users-page.component.html',
+ styleUrls: ['./users-page.component.scss'],
})
-export class UsersPageComponent {}
+export class UsersPageComponent implements OnInit {
+ data: UserListItemDto[] = [];
+ totalCount = 0;
+ page = 1;
+ pageSize = 20;
+ isLoading = false;
+ searchText = '';
+
+ // Edit dialog
+ showEditDialog = false;
+ editingUser: UserDto | null = null;
+
+ // Reset password result
+ resetPasswordResult: { userId: string; tempPassword: string } | null = null;
+
+ constructor(private userApi: UserApiService) {}
+
+ ngOnInit(): void { this.loadData(); }
+
+ loadData(): void {
+ this.isLoading = true;
+ this.userApi.getPaged({ page: this.page, pageSize: this.pageSize, search: this.searchText || undefined })
+ .subscribe({
+ next: (result: PagedResult) => {
+ this.data = result.items;
+ this.totalCount = result.totalCount;
+ this.isLoading = false;
+ },
+ error: () => { this.isLoading = false; }
+ });
+ }
+
+ onPageChange(event: PageChangeEvent): void {
+ this.page = event.skip / this.pageSize + 1;
+ this.pageSize = event.take;
+ this.loadData();
+ }
+
+ onSearch(): void { this.page = 1; this.loadData(); }
+
+ openEditDialog(user: UserListItemDto): void {
+ this.userApi.getById(user.id).subscribe(dto => {
+ this.editingUser = dto;
+ this.showEditDialog = true;
+ });
+ }
+
+ closeEditDialog(): void {
+ this.showEditDialog = false;
+ this.editingUser = null;
+ }
+
+ onUserSaved(request: UpdateUserRequest): void {
+ if (!this.editingUser) return;
+ this.userApi.update(this.editingUser.id, request).subscribe(() => {
+ this.closeEditDialog();
+ this.loadData();
+ });
+ }
+
+ deactivateUser(user: UserListItemDto): void {
+ if (!confirm(`Deactivate ${user.email}? They will lose access immediately.`)) return;
+ this.userApi.deactivate(user.id).subscribe(() => this.loadData());
+ }
+
+ resetPassword(user: UserListItemDto): void {
+ if (!confirm(`Reset password for ${user.email}? A new temporary password will be generated.`)) return;
+ this.userApi.resetPassword(user.id).subscribe(result => {
+ this.resetPasswordResult = { userId: user.id, tempPassword: result.tempPassword };
+ });
+ }
+
+ copyResetPassword(): void {
+ if (this.resetPasswordResult) {
+ navigator.clipboard.writeText(this.resetPasswordResult.tempPassword);
+ }
+ }
+
+ dismissResetResult(): void { this.resetPasswordResult = null; }
+}