import { Component, OnInit, inject, DestroyRef, computed } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { Router, ActivatedRoute } from '@angular/router'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { MatCardModule } from '@angular/material/card'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTableModule } from '@angular/material/table'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatChipsModule } from '@angular/material/chips'; import { MatMenuModule } from '@angular/material/menu'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { AdminService } from '../../../core/services/admin.service'; import { AdminUser, UserListParams } from '../../../core/models/admin.model'; import { RoleUpdateDialogComponent } from '../role-update-dialog/role-update-dialog.component'; import { StatusUpdateDialogComponent } from '../status-update-dialog/status-update-dialog.component'; import { PaginationService, PaginationState } from '../../../core/services/pagination.service'; import { PaginationComponent } from '../../../shared/components/pagination/pagination.component'; /** * AdminUsersComponent * * Displays and manages all users with pagination, filtering, and sorting. * * Features: * - User table with key columns * - Search by username/email * - Filter by role and status * - Sort by username, email, or date * - Pagination controls * - Action buttons for each user * - Responsive design (cards on mobile) * - Loading and error states */ @Component({ selector: 'app-admin-users', standalone: true, imports: [ CommonModule, ReactiveFormsModule, MatCardModule, MatButtonModule, MatIconModule, MatTableModule, MatInputModule, MatFormFieldModule, MatSelectModule, MatProgressSpinnerModule, MatTooltipModule, MatChipsModule, MatMenuModule, MatDialogModule, PaginationComponent ], templateUrl: './admin-users.component.html', styleUrl: './admin-users.component.scss' }) export class AdminUsersComponent implements OnInit { private readonly adminService = inject(AdminService); private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); private readonly fb = inject(FormBuilder); private readonly destroyRef = inject(DestroyRef); private readonly dialog = inject(MatDialog); private readonly paginationService = inject(PaginationService); // Service signals readonly users = this.adminService.adminUsersState; readonly isLoading = this.adminService.isLoadingUsers; readonly error = this.adminService.usersError; readonly pagination = this.adminService.usersPagination; // Computed pagination state for reusable component readonly paginationState = computed(() => { const pag = this.pagination(); if (!pag) return null; return this.paginationService.calculatePaginationState({ currentPage: pag.currentPage, pageSize: pag.limit, totalItems: pag.totalUsers }); }); // Computed page numbers readonly pageNumbers = computed(() => { const state = this.paginationState(); if (!state) return []; return this.paginationService.calculatePageNumbers( state.currentPage, state.totalPages, 5 ); }); // Table configuration displayedColumns: string[] = ['username', 'email', 'role', 'status', 'joinedDate', 'lastLogin', 'actions']; // Filter form filterForm!: FormGroup; // Current params currentParams: UserListParams = { page: 1, limit: 10, role: 'all', isActive: 'all', sortBy: 'createdAt', sortOrder: 'desc', search: '' }; // Expose Math for template Math = Math; ngOnInit(): void { this.initializeFilterForm(); this.setupSearchDebounce(); this.loadUsersFromRoute(); } /** * Initialize filter form */ private initializeFilterForm(): void { this.filterForm = this.fb.group({ search: [''], role: ['all'], isActive: ['all'], sortBy: ['createdAt'], sortOrder: ['desc'] }); } /** * Setup search field debounce */ private setupSearchDebounce(): void { this.filterForm.get('search')?.valueChanges .pipe( debounceTime(500), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.applyFilters(); }); } /** * Load users based on route query params */ private loadUsersFromRoute(): void { this.route.queryParams .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(params => { this.currentParams = { page: +(params['page'] || 1), limit: +(params['limit'] || 10), role: params['role'] || 'all', isActive: params['isActive'] || 'all', sortBy: params['sortBy'] || 'createdAt', sortOrder: params['sortOrder'] || 'desc', search: params['search'] || '' }; // Update form with current params this.filterForm.patchValue({ search: this.currentParams.search, role: this.currentParams.role, isActive: this.currentParams.isActive, sortBy: this.currentParams.sortBy, sortOrder: this.currentParams.sortOrder }, { emitEvent: false }); this.loadUsers(); }); } /** * Load users from API */ private loadUsers(): void { this.adminService.getUsers(this.currentParams) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } /** * Apply filters and reset to page 1 */ applyFilters(): void { const formValue = this.filterForm.value; this.currentParams = { ...this.currentParams, page: 1, // Reset to first page search: formValue.search || '', role: formValue.role || 'all', isActive: formValue.isActive || 'all', sortBy: formValue.sortBy || 'createdAt', sortOrder: formValue.sortOrder || 'desc' }; this.updateRouteParams(); } /** * Change page */ goToPage(page: number): void { if (page < 1 || page > (this.pagination()?.totalPages ?? 1)) return; this.currentParams = { ...this.currentParams, page }; this.updateRouteParams(); } /** * Handle page size change */ onPageSizeChange(pageSize: number): void { this.currentParams = { ...this.currentParams, page: 1, limit: pageSize }; this.updateRouteParams(); } /** * Update route query parameters */ private updateRouteParams(): void { this.router.navigate([], { relativeTo: this.route, queryParams: this.currentParams, queryParamsHandling: 'merge' }); } /** * Refresh users list */ refreshUsers(): void { this.loadUsers(); } /** * Reset all filters */ resetFilters(): void { this.filterForm.reset({ search: '', role: 'all', isActive: 'all', sortBy: 'createdAt', sortOrder: 'desc' }); this.applyFilters(); } /** * View user details */ viewUserDetails(userId: string): void { this.router.navigate(['/admin/users', userId]); } /** * Edit user role - Opens role update dialog */ editUserRole(user: AdminUser): void { const dialogRef = this.dialog.open(RoleUpdateDialogComponent, { width: '600px', maxWidth: '95vw', data: { user }, disableClose: false }); dialogRef.afterClosed().subscribe(newRole => { if (newRole && newRole !== user.role) { this.adminService.updateUserRole(user.id, newRole).subscribe({ next: () => { // User list is automatically updated in the service }, error: () => { // Error is handled by service } }); } }); } /** * Toggle user active status */ toggleUserStatus(user: AdminUser): void { const action = user.isActive ? 'deactivate' : 'activate'; const dialogRef = this.dialog.open(StatusUpdateDialogComponent, { width: '500px', data: { user: user, action: action }, disableClose: false, autoFocus: true }); dialogRef.afterClosed() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((confirmed: boolean) => { if (!confirmed) return; // Call appropriate service method based on action const serviceCall = action === 'activate' ? this.adminService.activateUser(user.id) : this.adminService.deactivateUser(user.id); serviceCall .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { // Signal update happens automatically in service // No need to manually refresh the list }, error: (error) => { console.error('Error updating user status:', error); } }); }); } /** * Get role chip color */ getRoleColor(role: string): string { return role === 'admin' ? 'primary' : 'accent'; } /** * Get status chip color */ getStatusColor(isActive: boolean): string { return isActive ? 'primary' : 'warn'; } /** * Get status text */ getStatusText(isActive: boolean): string { return isActive ? 'Active' : 'Inactive'; } /** * Format date for display */ formatDate(date: string | undefined): string { if (!date) return 'Never'; const d = new Date(date); return d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } /** * Format date with time for display */ formatDateTime(date: string | undefined): string { if (!date) return 'Never'; const d = new Date(date); return d.toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } /** * Navigate back to admin dashboard */ goBack(): void { this.router.navigate(['/admin']); } }