feat: pagination

This commit is contained in:
2026-02-04 20:17:04 -08:00
parent b9f33bc055
commit ca288eaac4
10 changed files with 696 additions and 30 deletions
@@ -4,17 +4,18 @@
* @author Naomi Carrigan
*/
import { Component, OnInit, inject, signal } from '@angular/core';
import { Component, OnInit, inject, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { SuggestionService } from '../../services/suggestion.service';
import { AuthService } from '../../services/auth.service';
import { PaginationComponent } from '../shared/pagination.component';
import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-types';
@Component({
selector: 'app-admin-suggestions',
standalone: true,
imports: [CommonModule, FormsModule],
imports: [CommonModule, FormsModule, PaginationComponent],
template: `
<div class="container">
<div class="header-section">
@@ -65,8 +66,16 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
<p>No suggestions found.</p>
</div>
} @else {
<app-pagination
[currentPage]="currentPage()"
[pageSize]="pageSize()"
[totalItems]="totalFilteredSuggestions()"
(pageChange)="onPageChange($event)"
(pageSizeChange)="onPageSizeChange($event)"
></app-pagination>
<div class="suggestions-list">
@for (suggestion of filteredSuggestions(); track suggestion.id) {
@for (suggestion of paginatedSuggestions(); track suggestion.id) {
<div class="suggestion-card" [class]="'status-' + suggestion.status.toLowerCase()">
<div class="suggestion-header">
<div class="badges">
@@ -185,6 +194,14 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
</div>
}
</div>
<app-pagination
[currentPage]="currentPage()"
[pageSize]="pageSize()"
[totalItems]="totalFilteredSuggestions()"
(pageChange)="onPageChange($event)"
(pageSizeChange)="onPageSizeChange($event)"
></app-pagination>
}
}
@@ -532,19 +549,32 @@ export class AdminSuggestionsComponent implements OnInit {
decliningsuggestion = signal<Suggestion | null>(null);
declineReason = '';
// Pagination state
currentPage = signal(1);
pageSize = signal(25);
SuggestionStatus = SuggestionStatus;
unreviewedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.UNREVIEWED).length;
acceptedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.ACCEPTED).length;
declinedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.DECLINED).length;
filteredSuggestions = () => {
filteredSuggestions = computed(() => {
const filter = this.statusFilter();
if (filter === 'all') {
return this.suggestions();
}
return this.suggestions().filter(s => s.status === filter);
};
});
paginatedSuggestions = computed(() => {
const suggestions = this.filteredSuggestions();
const start = (this.currentPage() - 1) * this.pageSize();
const end = start + this.pageSize();
return suggestions.slice(start, end);
});
totalFilteredSuggestions = computed(() => this.filteredSuggestions().length);
ngOnInit() {
if (this.authService.isAdmin()) {
@@ -568,6 +598,19 @@ export class AdminSuggestionsComponent implements OnInit {
setFilter(filter: 'all' | SuggestionStatus) {
this.statusFilter.set(filter);
this.currentPage.set(1); // Reset to first page when filter changes
}
onPageChange(page: number) {
this.currentPage.set(page);
}
onPageSizeChange(pageSize: number) {
this.pageSize.set(pageSize);
// Calculate new current page to stay on approximately the same content
const firstItemIndex = (this.currentPage() - 1) * this.pageSize();
const newPage = Math.floor(firstItemIndex / pageSize) + 1;
this.currentPage.set(newPage);
}
getStatusLabel(status: SuggestionStatus): string {
@@ -4,17 +4,18 @@
* @author Naomi Carrigan
*/
import { Component, OnInit, inject, signal } from '@angular/core';
import { Component, OnInit, inject, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Router } from '@angular/router';
import { UserService } from '../../services/user.service';
import { AuthService } from '../../services/auth.service';
import { PaginationComponent } from '../shared/pagination.component';
import { User } from '@library/shared-types';
@Component({
selector: 'app-admin-users',
standalone: true,
imports: [CommonModule],
imports: [CommonModule, PaginationComponent],
template: `
<div class="admin-container">
<h2>User Management</h2>
@@ -24,8 +25,16 @@ import { User } from '@library/shared-types';
} @else if (error()) {
<p class="error">{{ error() }}</p>
} @else {
<app-pagination
[currentPage]="currentPage()"
[pageSize]="pageSize()"
[totalItems]="totalUsers()"
(pageChange)="onPageChange($event)"
(pageSizeChange)="onPageSizeChange($event)"
></app-pagination>
<div class="users-list">
@for (user of users(); track user.id) {
@for (user of paginatedUsers(); track user.id) {
<div class="user-card" [class.banned]="user.isBanned">
<div class="user-info">
@if (user.avatar) {
@@ -70,6 +79,14 @@ import { User } from '@library/shared-types';
<p class="no-users">No users found.</p>
}
</div>
<app-pagination
[currentPage]="currentPage()"
[pageSize]="pageSize()"
[totalItems]="totalUsers()"
(pageChange)="onPageChange($event)"
(pageSizeChange)="onPageSizeChange($event)"
></app-pagination>
}
</div>
`,
@@ -270,6 +287,18 @@ export class AdminUsersComponent implements OnInit {
loading = signal(true);
error = signal<string | null>(null);
// Pagination state
currentPage = signal(1);
pageSize = signal(25);
paginatedUsers = computed(() => {
const start = (this.currentPage() - 1) * this.pageSize();
const end = start + this.pageSize();
return this.users().slice(start, end);
});
totalUsers = computed(() => this.users().length);
ngOnInit(): void {
if (!this.authService.isAdmin()) {
this.router.navigate(['/']);
@@ -320,4 +349,16 @@ export class AdminUsersComponent implements OnInit {
}
});
}
onPageChange(page: number): void {
this.currentPage.set(page);
}
onPageSizeChange(pageSize: number): void {
this.pageSize.set(pageSize);
// Calculate new current page to stay on approximately the same content
const firstItemIndex = (this.currentPage() - 1) * this.pageSize();
const newPage = Math.floor(firstItemIndex / pageSize) + 1;
this.currentPage.set(newPage);
}
}