fix: apply filters only on refresh

This commit is contained in:
bisco
2026-06-03 23:24:03 +02:00
parent 2c7ec7383b
commit f8de0e644a
4 changed files with 18 additions and 34 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ The initial schema stores:
- per-season averages, totals, and advanced metrics; - per-season averages, totals, and advanced metrics;
- per-game logs for best and worst performance views. - per-game logs for best and worst performance views.
The Angular dashboard applies live filter updates with a short debounce, quick position/league chips, local stat sorting, summary metrics, and a persistent profile panel on desktop. The Angular dashboard applies filters only when the user refreshes the result set, then supports local stat sorting, summary metrics, and an open/close profile panel on desktop.
## External Integrations ## External Integrations
+6 -8
View File
@@ -26,49 +26,47 @@
<input <input
type="search" type="search"
[(ngModel)]="filters.q" [(ngModel)]="filters.q"
(ngModelChange)="onFilterChange()"
placeholder="Name, role, team, nationality" placeholder="Name, role, team, nationality"
> >
</label> </label>
<label> <label>
Position Position
<select [(ngModel)]="filters.position" (ngModelChange)="onFilterChange()"> <select [(ngModel)]="filters.position">
<option value="">Any</option> <option value="">Any</option>
<option *ngFor="let position of positions" [value]="position">{{ position }}</option> <option *ngFor="let position of positions" [value]="position">{{ position }}</option>
</select> </select>
</label> </label>
<label> <label>
Role Role
<select [(ngModel)]="filters.role" (ngModelChange)="onFilterChange()"> <select [(ngModel)]="filters.role">
<option value="">Any</option> <option value="">Any</option>
<option *ngFor="let role of roles" [value]="role">{{ role }}</option> <option *ngFor="let role of roles" [value]="role">{{ role }}</option>
</select> </select>
</label> </label>
<label> <label>
League League
<select [(ngModel)]="filters.league" (ngModelChange)="onFilterChange()"> <select [(ngModel)]="filters.league">
<option value="">Any</option> <option value="">Any</option>
<option *ngFor="let league of leagues" [value]="league">{{ league }}</option> <option *ngFor="let league of leagues" [value]="league">{{ league }}</option>
</select> </select>
</label> </label>
<label> <label>
Min PPG Min PPG
<input type="number" [(ngModel)]="filters.minPoints" (ngModelChange)="onFilterChange()" min="0" step="0.1"> <input type="number" [(ngModel)]="filters.minPoints" min="0" step="0.1">
</label> </label>
<label> <label>
Min APG Min APG
<input type="number" [(ngModel)]="filters.minAssists" (ngModelChange)="onFilterChange()" min="0" step="0.1"> <input type="number" [(ngModel)]="filters.minAssists" min="0" step="0.1">
</label> </label>
<label> <label>
Min RPG Min RPG
<input type="number" [(ngModel)]="filters.minRebounds" (ngModelChange)="onFilterChange()" min="0" step="0.1"> <input type="number" [(ngModel)]="filters.minRebounds" min="0" step="0.1">
</label> </label>
<label> <label>
Min EFF Min EFF
<input <input
type="number" type="number"
[(ngModel)]="filters.minEfficiency" [(ngModel)]="filters.minEfficiency"
(ngModelChange)="onFilterChange()"
min="0" min="0"
step="0.1" step="0.1"
> >
+11 -4
View File
@@ -74,20 +74,27 @@ describe('AppComponent', () => {
assert.equal(component.selectedPlayer, null); assert.equal(component.selectedPlayer, null);
}); });
it('reacts to filter changes without requiring the search button', () => { it('does not apply changed filters until refresh is requested', () => {
let calls = 0; let calls = 0;
let requestedLeague = '';
const api = { const api = {
searchPlayers: () => { searchPlayers: (filters: { league: string }) => {
calls += 1; calls += 1;
requestedLeague = filters.league;
return of({ count: 0, results: [] }); return of({ count: 0, results: [] });
}, },
} as unknown as PlayerApiService; } as unknown as PlayerApiService;
const component = new AppComponent(api); const component = new AppComponent(api);
component.onFilterChange(); component.filters.league = 'LBA';
component.flushPendingSearch(); component.filters.league = 'ABA';
assert.equal(calls, 0);
component.search();
assert.equal(calls, 1); assert.equal(calls, 1);
assert.equal(requestedLeague, 'ABA');
}); });
it('exposes role options and sends the selected role as a filter', () => { it('exposes role options and sends the selected role as a filter', () => {
-21
View File
@@ -69,7 +69,6 @@ export class AppComponent {
loading = false; loading = false;
errorMessage = ''; errorMessage = '';
activeSort: SortKey = 'efficiency_rating'; activeSort: SortKey = 'efficiency_rating';
private pendingSearch: ReturnType<typeof setTimeout> | null = null;
constructor(private readonly playerApi: PlayerApiService) {} constructor(private readonly playerApi: PlayerApiService) {}
@@ -78,7 +77,6 @@ export class AppComponent {
} }
search(): void { search(): void {
this.cancelPendingSearch();
this.loading = true; this.loading = true;
this.errorMessage = ''; this.errorMessage = '';
@@ -97,19 +95,6 @@ export class AppComponent {
}); });
} }
onFilterChange(): void {
this.cancelPendingSearch();
this.pendingSearch = setTimeout(() => this.search(), 250);
}
flushPendingSearch(): void {
if (!this.pendingSearch) {
return;
}
this.cancelPendingSearch();
this.search();
}
clearFilters(): void { clearFilters(): void {
this.filters = { this.filters = {
q: '', q: '',
@@ -182,10 +167,4 @@ export class AppComponent {
return players.find((player) => player.id === this.selectedPlayer?.id) ?? null; return players.find((player) => player.id === this.selectedPlayer?.id) ?? null;
} }
private cancelPendingSearch(): void {
if (this.pendingSearch) {
clearTimeout(this.pendingSearch);
this.pendingSearch = null;
}
}
} }