Skip to content

Commit f6688cc

Browse files
committed
paging updated
1 parent 14d1e09 commit f6688cc

File tree

2 files changed

+203
-38
lines changed

2 files changed

+203
-38
lines changed

frontend/src/app/feature/admin/activity-log/activity-log.component.html

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ <h1 class="text-2xl font-bold">Activity Logs</h1>
1111
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
1212
<div>
1313
<label for="username" class="block text-sm font-medium text-gray-700">Username</label>
14-
<input type="text" id="username" formControlName="username" class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
14+
<input type="text" id="username" formControlName="username" placeholder="Enter username to filter" class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
1515
</div>
1616
<div>
1717
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
18-
<input type="email" id="email" formControlName="email" class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
18+
<input type="email" id="email" formControlName="email" placeholder="Enter email address to filter" class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
1919
</div>
2020
</div>
2121
<div class="mt-4">
@@ -27,28 +27,65 @@ <h1 class="text-2xl font-bold">Activity Logs</h1>
2727
</div>
2828
</div>
2929

30-
<div class="max-w-6xl mx-auto bg-white rounded-lg shadow-sm overflow-hidden transition-all duration-300">
30+
<div *ngIf="dataSource().data.length > 0" class="max-w-6xl mx-auto bg-white rounded-lg shadow-sm overflow-hidden transition-all duration-300">
3131
<div class="p-6">
32-
<table class="min-w-full divide-y divide-gray-200">
33-
<thead class="bg-gray-50">
34-
<tr>
35-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Username</th>
36-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
37-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Activity</th>
38-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Endpoint</th>
39-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Timestamp</th>
40-
</tr>
41-
</thead>
42-
<tbody class="bg-white divide-y divide-gray-200">
43-
<tr *ngFor="let log of activityLogs()">
44-
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.username }}</td>
45-
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.email }}</td>
46-
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.activity }}</td>
47-
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.endpoint }}</td>
48-
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.timestamp | date: 'short' }}</td>
32+
<div class="mb-4">
33+
<mat-form-field appearance="outline" class="w-full" floatLabel="always">
34+
<mat-label>Filter</mat-label>
35+
<input matInput (keyup)="applyFilter($event)" placeholder="Type to filter data" #input>
36+
<mat-hint>Search across all columns</mat-hint>
37+
</mat-form-field>
38+
</div>
39+
40+
<div class="mat-elevation-z1 overflow-hidden">
41+
<table mat-table [dataSource]="dataSource()" matSort class="w-full">
42+
<!-- Username Column -->
43+
<ng-container matColumnDef="username">
44+
<th mat-header-cell *matHeaderCellDef mat-sort-header>Username</th>
45+
<td mat-cell *matCellDef="let log">{{log.username}}</td>
46+
</ng-container>
47+
48+
<!-- Email Column -->
49+
<ng-container matColumnDef="email">
50+
<th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
51+
<td mat-cell *matCellDef="let log">{{log.email}}</td>
52+
</ng-container>
53+
54+
<!-- Activity Column -->
55+
<ng-container matColumnDef="activity">
56+
<th mat-header-cell *matHeaderCellDef mat-sort-header>Activity</th>
57+
<td mat-cell *matCellDef="let log">{{log.activity}}</td>
58+
</ng-container>
59+
60+
<!-- Endpoint Column -->
61+
<ng-container matColumnDef="endpoint">
62+
<th mat-header-cell *matHeaderCellDef mat-sort-header>Endpoint</th>
63+
<td mat-cell *matCellDef="let log">{{log.endpoint}}</td>
64+
</ng-container>
65+
66+
<!-- Timestamp Column -->
67+
<ng-container matColumnDef="timestamp">
68+
<th mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</th>
69+
<td mat-cell *matCellDef="let log">{{log.timestamp | date: 'short'}}</td>
70+
</ng-container>
71+
72+
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
73+
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
74+
75+
<!-- Row shown when there is no matching data -->
76+
<tr class="mat-row" *matNoDataRow>
77+
<td class="mat-cell" colspan="5">No data matching the filter "{{input.value}}"</td>
4978
</tr>
50-
</tbody>
51-
</table>
79+
</table>
80+
81+
<mat-paginator
82+
[pageSizeOptions]="[5, 10, 25, 100]"
83+
[pageSize]="5"
84+
[length]="totalRecords()"
85+
(page)="handlePageEvent($event)"
86+
showFirstLastButtons
87+
aria-label="Select page of activity logs"></mat-paginator>
88+
</div>
5289
</div>
5390
</div>
5491
</div>
Lines changed: 144 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,167 @@
1-
import { Component, inject, OnInit, signal } from '@angular/core';
1+
import { Component, ViewChild, AfterViewInit, signal, computed, effect, inject, OnInit } from '@angular/core';
22
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
3-
import { UserService } from '@core/services/user.service';
4-
import { DatePipe, NgFor } from '@angular/common';
3+
import { MatTableDataSource } from '@angular/material/table';
4+
import { MatPaginator, MatPaginatorModule, PageEvent } from '@angular/material/paginator';
5+
import { MatSort, MatSortModule } from '@angular/material/sort';
6+
import { UserService } from "@core/services/user.service";
7+
import { MatTableModule } from '@angular/material/table';
8+
import { MatFormFieldModule } from '@angular/material/form-field';
9+
import { MatInputModule } from '@angular/material/input';
10+
import { DatePipe, NgFor, NgIf } from '@angular/common';
11+
12+
// Define the ActivityLog interface
13+
interface ActivityLog {
14+
username: string;
15+
email: string;
16+
activity: string;
17+
endpoint: string;
18+
timestamp: Date;
19+
}
520

621
@Component({
722
selector: 'app-activity-log',
823
templateUrl: './activity-log.component.html',
9-
styleUrls: ['./activity-log.component.scss'],
1024
standalone: true,
1125
imports: [
1226
ReactiveFormsModule,
27+
MatTableModule,
28+
MatPaginatorModule,
29+
MatSortModule,
30+
MatFormFieldModule,
31+
MatInputModule,
1332
DatePipe,
14-
NgFor
33+
NgIf
1534
]
1635
})
17-
export class ActivityLogComponent implements OnInit {
36+
export class ActivityLogComponent implements AfterViewInit, OnInit {
1837
private fb = inject(FormBuilder);
19-
private userService = inject(UserService);
38+
private activityLogService = inject(UserService);
39+
40+
// Signals
41+
activityLogs = signal<ActivityLog[]>([]);
42+
totalRecords = signal<number>(0);
43+
loading = signal<boolean>(false);
44+
error = signal<string | null>(null);
45+
pageSize = signal<number>(5);
46+
pageIndex = signal<number>(0);
47+
48+
// Material table related properties
49+
displayedColumns = ['username', 'email', 'activity', 'endpoint', 'timestamp'];
50+
dataSource = signal(new MatTableDataSource<ActivityLog>([]));
51+
52+
// Form for API filtering
53+
filterForm = this.fb.group({
54+
username: [''],
55+
email: ['']
56+
});
2057

21-
filterForm!: FormGroup;
22-
activityLogs = signal<any[]>([]);
58+
@ViewChild(MatPaginator) paginator!: MatPaginator;
59+
@ViewChild(MatSort) sort!: MatSort;
2360

24-
ngOnInit(): void {
25-
this.filterForm = this.fb.group({
26-
username: [''],
27-
email: ['']
61+
constructor() {
62+
// Effect to update the dataSource when activityLogs changes
63+
effect(() => {
64+
const currentLogs = this.activityLogs();
65+
this.totalRecords.set(currentLogs.length);
66+
67+
// Manually handle pagination since datasource pagination is not working
68+
const start = this.pageIndex() * this.pageSize();
69+
const end = start + this.pageSize();
70+
const paginatedData = currentLogs.slice(start, end);
71+
72+
const dataSourceValue = new MatTableDataSource<ActivityLog>(paginatedData);
73+
74+
if (this.sort) {
75+
dataSourceValue.sort = this.sort;
76+
}
77+
78+
this.dataSource.set(dataSourceValue);
79+
});
80+
}
81+
82+
ngOnInit() {
83+
// Initialize by loading all logs
84+
this.loadActivityLogs();
85+
}
86+
87+
ngAfterViewInit() {
88+
// Ensure sort is applied after view initialization
89+
setTimeout(() => {
90+
if (this.sort) {
91+
const dataSourceValue = this.dataSource();
92+
dataSourceValue.sort = this.sort;
93+
this.dataSource.set(dataSourceValue);
94+
}
2895
});
2996
}
3097

31-
onFilter(): void {
32-
const { username, email } = this.filterForm.value;
33-
this.userService.getActivityLogs(username, email).subscribe({
98+
// Handle pagination events
99+
handlePageEvent(event: PageEvent) {
100+
this.pageSize.set(event.pageSize);
101+
this.pageIndex.set(event.pageIndex);
102+
}
103+
104+
loadActivityLogs() {
105+
this.loading.set(true);
106+
this.error.set(null);
107+
108+
this.activityLogService.getActivityLogs().subscribe({
109+
next: (logs) => {
110+
this.activityLogs.set(logs);
111+
this.pageIndex.set(0); // Reset to first page
112+
this.loading.set(false);
113+
},
114+
error: (err) => {
115+
console.error('Error loading activity logs', err);
116+
this.error.set('Failed to load activity logs. Please try again.');
117+
this.loading.set(false);
118+
}
119+
});
120+
}
121+
122+
onFilter() {
123+
const usernameValue = this.filterForm.get('username')?.value;
124+
const emailValue = this.filterForm.get('email')?.value;
125+
126+
this.loading.set(true);
127+
this.error.set(null);
128+
129+
this.activityLogService.getActivityLogs(usernameValue, emailValue).subscribe({
34130
next: (logs) => {
35131
this.activityLogs.set(logs);
132+
this.pageIndex.set(0); // Reset to first page
133+
this.loading.set(false);
134+
},
135+
error: (err) => {
136+
console.error('Error filtering activity logs', err);
137+
this.error.set('Failed to filter activity logs. Please try again.');
138+
this.loading.set(false);
36139
}
37140
});
38141
}
142+
143+
// Client-side filtering for the Material table
144+
applyFilter(event: Event) {
145+
const filterValue = (event.target as HTMLInputElement).value;
146+
const filteredLogs = this.activityLogs().filter(log =>
147+
Object.values(log).some(val =>
148+
val && val.toString().toLowerCase().includes(filterValue.toLowerCase())
149+
)
150+
);
151+
152+
// Update filtered records
153+
const start = this.pageIndex() * this.pageSize();
154+
const end = start + this.pageSize();
155+
const paginatedData = filteredLogs.slice(start, end);
156+
157+
const dataSourceValue = new MatTableDataSource<ActivityLog>(paginatedData);
158+
this.totalRecords.set(filteredLogs.length);
159+
this.pageIndex.set(0); // Reset to first page
160+
161+
if (this.sort) {
162+
dataSourceValue.sort = this.sort;
163+
}
164+
165+
this.dataSource.set(dataSourceValue);
166+
}
39167
}

0 commit comments

Comments
 (0)