Skip to content

Commit

Permalink
Merge pull request #24 from ramses-i/table_filtering
Browse files Browse the repository at this point in the history
Component: <app-table-action> filtering implementation
  • Loading branch information
lannodev authored Nov 3, 2024
2 parents 45d889b + 283bebe commit ee32af2
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div>
<router-outlet></router-outlet>
<app-responsive-helper></app-responsive-helper>
<ngx-sonner-toaster [theme]="themeService.isDark ? 'dark' : 'light'"/>
<ngx-sonner-toaster [theme]="themeService.isDark ? 'dark' : 'light'"></ngx-sonner-toaster>
</div>
2 changes: 1 addition & 1 deletion src/app/core/constants/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class Menu {
},
{
icon: 'assets/icons/heroicons/outline/exclamation-triangle.svg',
label: 'Erros',
label: 'Errors',
route: '/errors',
children: [
{ label: '404', route: '/errors/404' },
Expand Down
12 changes: 12 additions & 0 deletions src/app/core/services/table-filter.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Injectable, signal } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class TableFilterService {
searchField = signal<string>('');
statusField = signal<string>('');
orderField = signal<string>('');

constructor() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@ <h3 class="text-sm font-medium text-muted-foreground">Showing 08 of 100 users</h
<div class="absolute left-2.5 top-2.5">
<svg-icon src="./assets/icons/heroicons/outline/magnifying-glass.svg" [svgClass]="'h-4 w-4'"> </svg-icon>
</div>
<input class="py-2 pl-8 pr-2" placeholder="Search users" type="text" value="" />
<input
name="search"
class="py-2 pl-8 pr-2"
placeholder="Search users"
type="text"
value=""
(input)="onSearchChange($event)" />
</label>
</div>
<div class="flex flex-wrap gap-2.5">
<select class="w-28 p-2 text-muted-foreground">
<select name="status" class="w-28 p-2 text-muted-foreground" (change)="onStatusChange($event)">
<option value="">All</option>
<option value="1">Active</option>
<option value="2">Disabled</option>
<option value="2">Pending</option>
<option value="3">Pending</option>
</select>
<select class="w-28 p-2 text-muted-foreground">
<option value="1">Latest</option>
<option value="2">Older</option>
<option value="3">Oldest</option>
<select name="order" class="w-28 p-2 text-muted-foreground" (change)="onOrderChange($event)">
<option value="1">Newest</option>
<option value="2">Oldest</option>
</select>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ describe('TableActionComponent', () => {

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TableActionComponent]
})
.compileComponents();
imports: [TableActionComponent],
}).compileComponents();

fixture = TestBed.createComponent(TableActionComponent);
component = fixture.componentInstance;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { TableFilterService } from 'src/app/core/services/table-filter.service';

@Component({
selector: 'app-table-action',
Expand All @@ -8,4 +9,21 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
templateUrl: './table-action.component.html',
styleUrl: './table-action.component.scss',
})
export class TableActionComponent {}
export class TableActionComponent {
constructor(public filterService: TableFilterService) {}

onSearchChange(value: Event) {
const input = value.target as HTMLInputElement;
this.filterService.searchField.set(input.value);
}

onStatusChange(value: Event) {
const selectElement = value.target as HTMLSelectElement;
this.filterService.statusField.set(selectElement.value);
}

onOrderChange(value: Event) {
const selectElement = value.target as HTMLSelectElement;
this.filterService.orderField.set(selectElement.value);
}
}
2 changes: 2 additions & 0 deletions src/app/modules/uikit/pages/table/model/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ export interface User {
occupation: string;
hobbies: string[];
selected: boolean;
status: number;
created_at: string;
}
2 changes: 1 addition & 1 deletion src/app/modules/uikit/pages/table/table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ <h3 class="font-semibold text-foreground">Team Members</h3>
<tr app-table-header (onCheck)="toggleUsers($event)"></tr>
</thead>
<tbody>
@for (user of users(); track $index) {
@for (user of filteredUsers(); track $index) {
<tr class="hover:bg-card/50" app-table-row [user]="user"></tr>
} @empty {
<tr>
Expand Down
5 changes: 2 additions & 3 deletions src/app/modules/uikit/pages/table/table.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ describe('TableComponent', () => {

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TableComponent]
})
.compileComponents();
imports: [TableComponent],
}).compileComponents();

fixture = TestBed.createComponent(TableComponent);
component = fixture.componentInstance;
Expand Down
47 changes: 44 additions & 3 deletions src/app/modules/uikit/pages/table/table.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HttpClient } from '@angular/common/http';
import { Component, OnInit, signal } from '@angular/core';
import { Component, computed, OnInit, signal } from '@angular/core';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { User } from './model/user.model';
import { FormsModule } from '@angular/forms';
Expand All @@ -9,6 +9,7 @@ import { TableRowComponent } from './components/table-row/table-row.component';
import { TableActionComponent } from './components/table-action/table-action.component';
import { toast } from 'ngx-sonner';
import { dummyData } from 'src/app/shared/dummy/user.dummy';
import { TableFilterService } from 'src/app/core/services/table-filter.service';

@Component({
selector: 'app-table',
Expand All @@ -27,13 +28,13 @@ import { dummyData } from 'src/app/shared/dummy/user.dummy';
export class TableComponent implements OnInit {
users = signal<User[]>([]);

constructor(private http: HttpClient) {
constructor(private http: HttpClient, private filterService: TableFilterService) {
this.http.get<User[]>('https://freetestapi.com/api/v1/users?limit=8').subscribe({
next: (data) => this.users.set(data),
error: (error) => {
this.users.set(dummyData);
this.handleRequestError(error);
}
},
});
}

Expand All @@ -58,5 +59,45 @@ export class TableComponent implements OnInit {
});
}

filteredUsers = computed(() => {
const search = this.filterService.searchField().toLowerCase();
const status = this.filterService.statusField();
const order = this.filterService.orderField();

return (
this.users()
.filter(
(user) =>
user.name.toLowerCase().includes(search) ||
user.username.toLowerCase().includes(search) ||
user.email.toLowerCase().includes(search) ||
user.phone.includes(search),
)
.filter((user) => {
if (!status) return true; // No status filter applied
switch (status) {
case '1':
return user.status === 1; // Active
case '2':
return user.status === 2; // Disabled
case '3':
return user.status === 3; // Pending
default:
return true;
}
})
// Sort based on the order field
.sort((a, b) => {
const defaultNewest = !order || order === '1'; // Apply Newest if order is unset
if (defaultNewest) {
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
} else if (order === '2') {
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
}
return 0;
})
);
});

ngOnInit() {}
}
18 changes: 17 additions & 1 deletion src/app/shared/dummy/user.dummy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { User } from "src/app/modules/uikit/pages/table/model/user.model";
import { User } from 'src/app/modules/uikit/pages/table/model/user.model';

export const dummyData: User[] = [
{
Expand All @@ -12,6 +12,8 @@ export const dummyData: User[] = [
occupation: 'Software Engineer',
hobbies: ['coding', 'hiking', 'reading'],
selected: false,
status: 1,
created_at: '2024-10-12T12:34:56Z',
},
{
id: 2,
Expand All @@ -24,6 +26,8 @@ export const dummyData: User[] = [
occupation: 'Graphic Designer',
hobbies: ['drawing', 'photography', 'travel'],
selected: false,
status: 1,
created_at: '2024-10-14T12:34:56Z',
},
{
id: 3,
Expand All @@ -36,6 +40,8 @@ export const dummyData: User[] = [
occupation: 'Data Scientist',
hobbies: ['data analysis', 'cycling', 'music'],
selected: true,
status: 2,
created_at: '2024-10-16T12:34:56Z',
},
{
id: 4,
Expand All @@ -48,6 +54,8 @@ export const dummyData: User[] = [
occupation: 'Marketing Specialist',
hobbies: ['writing', 'yoga', 'baking'],
selected: false,
status: 2,
created_at: '2024-10-18T12:34:56Z',
},
{
id: 5,
Expand All @@ -60,6 +68,8 @@ export const dummyData: User[] = [
occupation: 'Product Manager',
hobbies: ['innovation', 'gaming', 'finance'],
selected: true,
status: 1,
created_at: '2024-10-20T12:34:56Z',
},
{
id: 6,
Expand All @@ -72,6 +82,8 @@ export const dummyData: User[] = [
occupation: 'UI/UX Designer',
hobbies: ['design', 'gardening', 'swimming'],
selected: false,
status: 1,
created_at: '2024-10-22T12:34:56Z',
},
{
id: 7,
Expand All @@ -84,6 +96,8 @@ export const dummyData: User[] = [
occupation: 'Mobile Developer',
hobbies: ['app development', 'traveling', 'reading'],
selected: false,
status: 1,
created_at: '2024-10-26T12:34:56Z',
},
{
id: 8,
Expand All @@ -96,5 +110,7 @@ export const dummyData: User[] = [
occupation: 'DevOps Engineer',
hobbies: ['automation', 'gaming', 'blogging'],
selected: true,
status: 3,
created_at: '2024-10-28T12:34:56Z',
},
];
56 changes: 56 additions & 0 deletions tests-e2e/table.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { test, expect, Page } from '@playwright/test';

test.beforeEach(async ({ page }) => {
await page.goto('/components/table', { waitUntil: 'networkidle' });
await page.getByRole('navigation').locator('div').filter({ hasText: 'Components' }).nth(3).click();
await page.getByRole('link', { name: 'Table' }).click();
});

async function checkTableRowCount(page: Page, filterSelector: string, value: string, expectedRowCount: number) {
await page.locator(filterSelector).selectOption(value);

await expect(page.locator('table tbody tr')).toHaveCount(expectedRowCount, { timeout: 5000 });
}

async function checkCellInRow(page: Page, cellContent: string, expectedRowIndex: number) {
const rows = await page.locator('table tbody tr');
const cellTexts = await rows.nth(expectedRowIndex).locator('td').allTextContents();
expect(cellTexts).toContain(cellContent);
}

test('check user filter updates table', async ({ page }) => {
await page.fill('input[name="search"]', 'john');

await expect(page.locator('table tbody tr')).toHaveCount(2);
});

test('check user filter empty restores table', async ({ page }) => {
await page.fill('input[name="search"]', '');

await expect(page.locator('table tbody tr')).toHaveCount(8);
});

test('check user status filter updates table', async ({ page }) => {
const statusFilters = [
{ value: '', expectedRowCount: 8 }, // All
{ value: '1', expectedRowCount: 5 }, // Active
{ value: '2', expectedRowCount: 2 }, // Disabled
{ value: '3', expectedRowCount: 1 }, // Pending
];

for (const filter of statusFilters) {
await checkTableRowCount(page, 'select[name="status"]', filter.value, filter.expectedRowCount);
}
});

test('check user order filter - newest first', async ({ page }) => {
await page.locator('select[name="order"]').selectOption('1');

await checkCellInRow(page, 'emmawilson', 0);
});

test('check user order filter - oldest first', async ({ page }) => {
await page.locator('select[name="order"]').selectOption('2');

await checkCellInRow(page, 'johndoe', 0);
});

0 comments on commit ee32af2

Please sign in to comment.