284 lines
10 KiB
HTML
284 lines
10 KiB
HTML
<div class="admin-users-container">
|
|
<!-- Header -->
|
|
<div class="users-header">
|
|
<div class="header-left">
|
|
<button mat-icon-button (click)="goBack()" matTooltip="Back to Dashboard">
|
|
<mat-icon>arrow_back</mat-icon>
|
|
</button>
|
|
<div class="header-title">
|
|
<h1>User Management</h1>
|
|
<p class="subtitle">Manage all users and their permissions</p>
|
|
</div>
|
|
</div>
|
|
<div class="header-actions">
|
|
<button mat-stroked-button (click)="refreshUsers()" [disabled]="isLoading()">
|
|
<mat-icon>refresh</mat-icon>
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters Section -->
|
|
<mat-card class="filters-card">
|
|
<mat-card-content>
|
|
<form [formGroup]="filterForm" class="filters-form">
|
|
<!-- Search -->
|
|
<mat-form-field appearance="outline" class="search-field">
|
|
<mat-label>Search</mat-label>
|
|
<input matInput formControlName="search" placeholder="Search by username or email">
|
|
<mat-icon matPrefix>search</mat-icon>
|
|
</mat-form-field>
|
|
|
|
<!-- Role Filter -->
|
|
<mat-form-field appearance="outline">
|
|
<mat-label>Role</mat-label>
|
|
<mat-select formControlName="role" (selectionChange)="applyFilters()">
|
|
<mat-option value="all">All Roles</mat-option>
|
|
<mat-option value="user">User</mat-option>
|
|
<mat-option value="admin">Admin</mat-option>
|
|
</mat-select>
|
|
<mat-icon matPrefix>badge</mat-icon>
|
|
</mat-form-field>
|
|
|
|
<!-- Status Filter -->
|
|
<mat-form-field appearance="outline">
|
|
<mat-label>Status</mat-label>
|
|
<mat-select formControlName="isActive" (selectionChange)="applyFilters()">
|
|
<mat-option value="all">All Status</mat-option>
|
|
<mat-option value="active">Active</mat-option>
|
|
<mat-option value="inactive">Inactive</mat-option>
|
|
</mat-select>
|
|
<mat-icon matPrefix>toggle_on</mat-icon>
|
|
</mat-form-field>
|
|
|
|
<!-- Sort By -->
|
|
<mat-form-field appearance="outline">
|
|
<mat-label>Sort By</mat-label>
|
|
<mat-select formControlName="sortBy" (selectionChange)="applyFilters()">
|
|
<mat-option value="username">Username</mat-option>
|
|
<mat-option value="email">Email</mat-option>
|
|
<mat-option value="createdAt">Join Date</mat-option>
|
|
<mat-option value="lastLoginAt">Last Login</mat-option>
|
|
</mat-select>
|
|
<mat-icon matPrefix>sort</mat-icon>
|
|
</mat-form-field>
|
|
|
|
<!-- Sort Order -->
|
|
<mat-form-field appearance="outline">
|
|
<mat-label>Order</mat-label>
|
|
<mat-select formControlName="sortOrder" (selectionChange)="applyFilters()">
|
|
<mat-option value="asc">Ascending</mat-option>
|
|
<mat-option value="desc">Descending</mat-option>
|
|
</mat-select>
|
|
<mat-icon matPrefix>swap_vert</mat-icon>
|
|
</mat-form-field>
|
|
|
|
<!-- Reset Button -->
|
|
<button mat-stroked-button type="button" (click)="resetFilters()">
|
|
<mat-icon>clear</mat-icon>
|
|
Reset
|
|
</button>
|
|
</form>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<!-- Loading State -->
|
|
@if (isLoading() && users().length === 0) {
|
|
<div class="loading-container">
|
|
<mat-spinner diameter="60"></mat-spinner>
|
|
<p>Loading users...</p>
|
|
</div>
|
|
}
|
|
|
|
<!-- Error State -->
|
|
@if (error() && !isLoading() && users().length === 0) {
|
|
<mat-card class="error-card">
|
|
<mat-card-content>
|
|
<div class="error-content">
|
|
<mat-icon color="warn">error_outline</mat-icon>
|
|
<div class="error-text">
|
|
<h3>Failed to Load Users</h3>
|
|
<p>{{ error() }}</p>
|
|
</div>
|
|
</div>
|
|
<button mat-raised-button color="primary" (click)="refreshUsers()">
|
|
<mat-icon>refresh</mat-icon>
|
|
Try Again
|
|
</button>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
}
|
|
|
|
<!-- Users Table (Desktop) -->
|
|
@if (users().length > 0) {
|
|
<mat-card class="table-card desktop-table">
|
|
<div class="table-header">
|
|
<h2>Users</h2>
|
|
@if (pagination()) {
|
|
<span class="total-count">
|
|
Total: {{ pagination()?.totalItems }} user{{ pagination()?.totalItems !== 1 ? 's' : '' }}
|
|
</span>
|
|
}
|
|
</div>
|
|
<div class="table-container">
|
|
<table mat-table [dataSource]="users()" class="users-table">
|
|
<!-- Username Column -->
|
|
<ng-container matColumnDef="username">
|
|
<th mat-header-cell *matHeaderCellDef>Username</th>
|
|
<td mat-cell *matCellDef="let user">
|
|
<div class="username-cell">
|
|
<mat-icon class="user-icon">account_circle</mat-icon>
|
|
<span>{{ user.username }}</span>
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Email Column -->
|
|
<ng-container matColumnDef="email">
|
|
<th mat-header-cell *matHeaderCellDef>Email</th>
|
|
<td mat-cell *matCellDef="let user">{{ user.email }}</td>
|
|
</ng-container>
|
|
|
|
<!-- Role Column -->
|
|
<ng-container matColumnDef="role">
|
|
<th mat-header-cell *matHeaderCellDef>Role</th>
|
|
<td mat-cell *matCellDef="let user">
|
|
<mat-chip [color]="getRoleColor(user.role)" highlighted>
|
|
{{ user.role | uppercase }}
|
|
</mat-chip>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Status Column -->
|
|
<ng-container matColumnDef="status">
|
|
<th mat-header-cell *matHeaderCellDef>Status</th>
|
|
<td mat-cell *matCellDef="let user">
|
|
<mat-chip [color]="getStatusColor(user.isActive)" highlighted>
|
|
{{ getStatusText(user.isActive) }}
|
|
</mat-chip>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Joined Date Column -->
|
|
<ng-container matColumnDef="joinedDate">
|
|
<th mat-header-cell *matHeaderCellDef>Joined</th>
|
|
<td mat-cell *matCellDef="let user">{{ formatDate(user.createdAt) }}</td>
|
|
</ng-container>
|
|
|
|
<!-- Last Login Column -->
|
|
<ng-container matColumnDef="lastLogin">
|
|
<th mat-header-cell *matHeaderCellDef>Last Login</th>
|
|
<td mat-cell *matCellDef="let user">{{ formatDateTime(user.lastLoginAt) }}</td>
|
|
</ng-container>
|
|
|
|
<!-- Actions Column -->
|
|
<ng-container matColumnDef="actions">
|
|
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
|
<td mat-cell *matCellDef="let user">
|
|
<button mat-icon-button [matMenuTriggerFor]="actionMenu">
|
|
<mat-icon>more_vert</mat-icon>
|
|
</button>
|
|
<mat-menu #actionMenu="matMenu">
|
|
<button mat-menu-item (click)="viewUserDetails(user.id)">
|
|
<mat-icon>visibility</mat-icon>
|
|
<span>View Details</span>
|
|
</button>
|
|
<button mat-menu-item (click)="editUserRole(user)">
|
|
<mat-icon>edit</mat-icon>
|
|
<span>Edit Role</span>
|
|
</button>
|
|
<button mat-menu-item (click)="toggleUserStatus(user)">
|
|
<mat-icon>{{ user.isActive ? 'block' : 'check_circle' }}</mat-icon>
|
|
<span>{{ user.isActive ? 'Deactivate' : 'Activate' }}</span>
|
|
</button>
|
|
</mat-menu>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
|
</table>
|
|
</div>
|
|
</mat-card>
|
|
|
|
<!-- Users Cards (Mobile) -->
|
|
<div class="mobile-cards">
|
|
@for (user of users(); track user.id) {
|
|
<mat-card class="user-card">
|
|
<mat-card-header>
|
|
<mat-icon mat-card-avatar class="card-avatar">account_circle</mat-icon>
|
|
<mat-card-title>{{ user.username }}</mat-card-title>
|
|
<mat-card-subtitle>{{ user.email }}</mat-card-subtitle>
|
|
</mat-card-header>
|
|
<mat-card-content>
|
|
<div class="card-info">
|
|
<div class="info-row">
|
|
<span class="label">Role:</span>
|
|
<mat-chip [color]="getRoleColor(user.role)" highlighted>
|
|
{{ user.role | uppercase }}
|
|
</mat-chip>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="label">Status:</span>
|
|
<mat-chip [color]="getStatusColor(user.isActive)" highlighted>
|
|
{{ getStatusText(user.isActive) }}
|
|
</mat-chip>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="label">Joined:</span>
|
|
<span>{{ formatDate(user.createdAt) }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="label">Last Login:</span>
|
|
<span>{{ formatDateTime(user.lastLoginAt) }}</span>
|
|
</div>
|
|
</div>
|
|
</mat-card-content>
|
|
<mat-card-actions>
|
|
<button mat-button (click)="viewUserDetails(user.id)">
|
|
<mat-icon>visibility</mat-icon>
|
|
View
|
|
</button>
|
|
<button mat-button (click)="editUserRole(user)">
|
|
<mat-icon>edit</mat-icon>
|
|
Edit Role
|
|
</button>
|
|
<button mat-button [color]="user.isActive ? 'warn' : 'primary'" (click)="toggleUserStatus(user)">
|
|
<mat-icon>{{ user.isActive ? 'block' : 'check_circle' }}</mat-icon>
|
|
{{ user.isActive ? 'Deactivate' : 'Activate' }}
|
|
</button>
|
|
</mat-card-actions>
|
|
</mat-card>
|
|
}
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
@if (paginationState()) {
|
|
<app-pagination
|
|
[state]="paginationState()"
|
|
[pageNumbers]="pageNumbers()"
|
|
[pageSizeOptions]="[10, 25, 50, 100]"
|
|
[showFirstLast]="true"
|
|
[itemLabel]="'users'"
|
|
(pageChange)="goToPage($event)"
|
|
(pageSizeChange)="onPageSizeChange($event)">
|
|
</app-pagination>
|
|
}
|
|
}
|
|
|
|
<!-- Empty State -->
|
|
@if (!isLoading() && !error() && users().length === 0) {
|
|
<mat-card class="empty-card">
|
|
<mat-card-content>
|
|
<mat-icon>people_outline</mat-icon>
|
|
<h3>No Users Found</h3>
|
|
<p>No users match your current filters.</p>
|
|
<button mat-raised-button color="primary" (click)="resetFilters()">
|
|
<mat-icon>clear</mat-icon>
|
|
Clear Filters
|
|
</button>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
}
|
|
</div>
|