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; } +}