342 lines
13 KiB
HTML
342 lines
13 KiB
HTML
<app-ui-card title="Liste des Utilisateurs Hub">
|
|
<a
|
|
helper-text
|
|
href="javascript:void(0);"
|
|
class="icon-link icon-link-hover link-primary fw-semibold"
|
|
>Gérez les accès utilisateurs de votre plateforme DCB
|
|
</a>
|
|
|
|
<div card-body>
|
|
<!-- Barre d'actions supérieure -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<!-- Statistiques rapides -->
|
|
<div class="btn-group btn-group-sm">
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary"
|
|
[class.active]="roleFilter === 'all'"
|
|
(click)="filterByRole('all')"
|
|
>
|
|
Tous ({{ allUsers.length }})
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-danger"
|
|
[class.active]="roleFilter === UserRole.DCB_ADMIN"
|
|
(click)="filterByRole(UserRole.DCB_ADMIN)"
|
|
>
|
|
Admins ({{ getUsersCountByRole(UserRole.DCB_ADMIN) }})
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-info"
|
|
[class.active]="roleFilter === UserRole.DCB_SUPPORT"
|
|
(click)="filterByRole(UserRole.DCB_SUPPORT)"
|
|
>
|
|
Support ({{ getUsersCountByRole(UserRole.DCB_SUPPORT) }})
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-success"
|
|
[class.active]="roleFilter === UserRole.DCB_PARTNER"
|
|
(click)="filterByRole(UserRole.DCB_PARTNER)"
|
|
>
|
|
Partenaires ({{ getUsersCountByRole(UserRole.DCB_PARTNER) }})
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="d-flex justify-content-end gap-2">
|
|
@if (canCreateUsers) {
|
|
<button
|
|
class="btn btn-primary"
|
|
(click)="openCreateModal.emit()"
|
|
>
|
|
<ng-icon name="lucideUserPlus" class="me-1"></ng-icon>
|
|
Nouvel Utilisateur
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Barre de recherche et filtres -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-3">
|
|
<div class="input-group">
|
|
<span class="input-group-text">
|
|
<ng-icon name="lucideSearch"></ng-icon>
|
|
</span>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
placeholder="Nom, email, username..."
|
|
[(ngModel)]="searchTerm"
|
|
(keyup.enter)="onSearch()"
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" [(ngModel)]="statusFilter" (change)="onSearch()">
|
|
<option value="all">Tous les statuts</option>
|
|
<option value="enabled">Activés ({{ getEnabledUsersCount() }})</option>
|
|
<option value="disabled">Désactivés ({{ getDisabledUsersCount() }})</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" [(ngModel)]="emailVerifiedFilter" (change)="onSearch()">
|
|
<option value="all">Tous les emails</option>
|
|
<option value="verified">Email vérifié</option>
|
|
<option value="not-verified">Email non vérifié</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" [(ngModel)]="roleFilter" (change)="onSearch()">
|
|
@for (role of availableRoles; track role.value) {
|
|
<option [value]="role.value">{{ role.label }}</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="d-flex gap-2">
|
|
<button class="btn btn-outline-primary" (click)="onSearch()">
|
|
<ng-icon name="lucideFilter" class="me-1"></ng-icon>
|
|
Appliquer
|
|
</button>
|
|
<button class="btn btn-outline-secondary" (click)="onClearFilters()">
|
|
<ng-icon name="lucideX" class="me-1"></ng-icon>
|
|
Réinitialiser
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
@if (loading) {
|
|
<div class="text-center py-4">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Chargement...</span>
|
|
</div>
|
|
<p class="mt-2 text-muted">Chargement des utilisateurs...</p>
|
|
</div>
|
|
}
|
|
|
|
<!-- Error State -->
|
|
@if (error && !loading) {
|
|
<div class="alert alert-danger" role="alert">
|
|
<div class="d-flex align-items-center">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
<div>{{ error }}</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Users Table -->
|
|
@if (!loading && !error) {
|
|
<div class="table-responsive">
|
|
<table class="table table-hover table-striped">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th (click)="sort('username')" class="cursor-pointer">
|
|
<div class="d-flex align-items-center">
|
|
<span>Utilisateur</span>
|
|
<ng-icon [name]="getSortIcon('username')" class="ms-1 fs-12"></ng-icon>
|
|
</div>
|
|
</th>
|
|
<th (click)="sort('email')" class="cursor-pointer">
|
|
<div class="d-flex align-items-center">
|
|
<span>Email</span>
|
|
<ng-icon [name]="getSortIcon('email')" class="ms-1 fs-12"></ng-icon>
|
|
</div>
|
|
</th>
|
|
<th (click)="sort('role')" class="cursor-pointer">
|
|
<div class="d-flex align-items-center">
|
|
<span>Rôle</span>
|
|
<ng-icon [name]="getSortIcon('role')" class="ms-1 fs-12"></ng-icon>
|
|
</div>
|
|
</th>
|
|
<th (click)="sort('enabled')" class="cursor-pointer">
|
|
<div class="d-flex align-items-center">
|
|
<span>Statut</span>
|
|
<ng-icon [name]="getSortIcon('enabled')" class="ms-1 fs-12"></ng-icon>
|
|
</div>
|
|
</th>
|
|
<th (click)="sort('createdTimestamp')" class="cursor-pointer">
|
|
<div class="d-flex align-items-center">
|
|
<span>Créé le</span>
|
|
<ng-icon [name]="getSortIcon('createdTimestamp')" class="ms-1 fs-12"></ng-icon>
|
|
</div>
|
|
</th>
|
|
<th width="180">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (user of displayedUsers; track user.id) {
|
|
<tr>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar-sm bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-2">
|
|
<span class="text-primary fw-semibold small">
|
|
{{ getUserInitials(user) }}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<strong class="d-block">{{ getUserDisplayName(user) }}</strong>
|
|
<small class="text-muted">@{{ user.username }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
{{ user.email }}
|
|
@if (!user.emailVerified) {
|
|
<ng-icon
|
|
name="lucideAlertTriangle"
|
|
class="ms-1 text-warning"
|
|
size="16"
|
|
title="Email non vérifié"
|
|
></ng-icon>
|
|
}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge d-flex align-items-center" [ngClass]="getRoleBadgeClass(user.role)">
|
|
<ng-icon [name]="getRoleIcon(user.role)" class="me-1" size="14"></ng-icon>
|
|
{{ getRoleLabel(user.role) }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span [class]="getStatusBadgeClass(user)">
|
|
{{ getStatusText(user) }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<small class="text-muted">
|
|
{{ formatTimestamp(user.createdTimestamp) }}
|
|
</small>
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button
|
|
class="btn btn-outline-primary btn-sm"
|
|
(click)="viewUserProfile(user.id)"
|
|
title="Voir le profil"
|
|
>
|
|
<ng-icon name="lucideEye"></ng-icon>
|
|
</button>
|
|
<button
|
|
class="btn btn-outline-warning btn-sm"
|
|
(click)="resetPassword(user)"
|
|
title="Réinitialiser le mot de passe"
|
|
>
|
|
<ng-icon name="lucideKey"></ng-icon>
|
|
</button>
|
|
@if (user.enabled) {
|
|
<button
|
|
class="btn btn-outline-secondary btn-sm"
|
|
(click)="disableUser(user)"
|
|
title="Désactiver l'utilisateur"
|
|
>
|
|
<ng-icon name="lucideUserX"></ng-icon>
|
|
</button>
|
|
} @else {
|
|
<button
|
|
class="btn btn-outline-success btn-sm"
|
|
(click)="enableUser(user)"
|
|
title="Activer l'utilisateur"
|
|
>
|
|
<ng-icon name="lucideUserCheck"></ng-icon>
|
|
</button>
|
|
}
|
|
@if (canDeleteUsers) {
|
|
<button
|
|
class="btn btn-outline-danger btn-sm"
|
|
(click)="deleteUser(user)"
|
|
title="Supprimer l'utilisateur"
|
|
>
|
|
<ng-icon name="lucideTrash2"></ng-icon>
|
|
</button>
|
|
}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
}
|
|
@empty {
|
|
<tr>
|
|
<td colspan="6" class="text-center py-4">
|
|
<div class="text-muted">
|
|
<ng-icon name="lucideUsers" class="fs-1 mb-3 opacity-50"></ng-icon>
|
|
<h5 class="mb-2">Aucun utilisateur trouvé</h5>
|
|
<p class="mb-3">Aucun utilisateur ne correspond à vos critères de recherche.</p>
|
|
@if (canCreateUsers) {
|
|
<button class="btn btn-primary" (click)="openCreateModal.emit()">
|
|
<ng-icon name="lucideUserPlus" class="me-1"></ng-icon>
|
|
Créer le premier utilisateur
|
|
</button>
|
|
}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
@if (totalPages > 1) {
|
|
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
<div class="text-muted">
|
|
Affichage de {{ getStartIndex() }} à {{ getEndIndex() }} sur {{ totalItems }} utilisateurs
|
|
</div>
|
|
<nav>
|
|
<ngb-pagination
|
|
[collectionSize]="totalItems"
|
|
[page]="currentPage"
|
|
[pageSize]="itemsPerPage"
|
|
[maxSize]="5"
|
|
[rotate]="true"
|
|
[boundaryLinks]="true"
|
|
(pageChange)="onPageChange($event)"
|
|
/>
|
|
</nav>
|
|
</div>
|
|
}
|
|
|
|
<!-- Résumé des résultats -->
|
|
@if (displayedUsers.length > 0) {
|
|
<div class="mt-3 pt-3 border-top">
|
|
<div class="row text-center">
|
|
<div class="col">
|
|
<small class="text-muted">
|
|
<strong>Total :</strong> {{ allUsers.length }} utilisateurs
|
|
</small>
|
|
</div>
|
|
<div class="col">
|
|
<small class="text-muted">
|
|
<strong>Actifs :</strong> {{ getEnabledUsersCount() }}
|
|
</small>
|
|
</div>
|
|
<div class="col">
|
|
<small class="text-muted">
|
|
<strong>Admins :</strong> {{ getUsersCountByRole(UserRole.DCB_ADMIN) }}
|
|
</small>
|
|
</div>
|
|
<div class="col">
|
|
<small class="text-muted">
|
|
<strong>Support :</strong> {{ getUsersCountByRole(UserRole.DCB_SUPPORT) }}
|
|
</small>
|
|
</div>
|
|
<div class="col">
|
|
<small class="text-muted">
|
|
<strong>Partenaires :</strong> {{ getUsersCountByRole(UserRole.DCB_PARTNER) }}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
</app-ui-card> |