Files
Tasks/frontend/src/app/features/admin/admin-users/admin-users.component.html
2025-12-26 00:18:28 +02:00

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>