generated from bisco/codex-bootstrap
merge: apply filters only on refresh
This commit is contained in:
@@ -21,7 +21,7 @@ The initial schema stores:
|
||||
- per-season averages, totals, and advanced metrics;
|
||||
- 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
|
||||
|
||||
|
||||
@@ -26,49 +26,47 @@
|
||||
<input
|
||||
type="search"
|
||||
[(ngModel)]="filters.q"
|
||||
(ngModelChange)="onFilterChange()"
|
||||
placeholder="Name, role, team, nationality"
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
Position
|
||||
<select [(ngModel)]="filters.position" (ngModelChange)="onFilterChange()">
|
||||
<select [(ngModel)]="filters.position">
|
||||
<option value="">Any</option>
|
||||
<option *ngFor="let position of positions" [value]="position">{{ position }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Role
|
||||
<select [(ngModel)]="filters.role" (ngModelChange)="onFilterChange()">
|
||||
<select [(ngModel)]="filters.role">
|
||||
<option value="">Any</option>
|
||||
<option *ngFor="let role of roles" [value]="role">{{ role }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
League
|
||||
<select [(ngModel)]="filters.league" (ngModelChange)="onFilterChange()">
|
||||
<select [(ngModel)]="filters.league">
|
||||
<option value="">Any</option>
|
||||
<option *ngFor="let league of leagues" [value]="league">{{ league }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
Min EFF
|
||||
<input
|
||||
type="number"
|
||||
[(ngModel)]="filters.minEfficiency"
|
||||
(ngModelChange)="onFilterChange()"
|
||||
min="0"
|
||||
step="0.1"
|
||||
>
|
||||
|
||||
@@ -74,20 +74,27 @@ describe('AppComponent', () => {
|
||||
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 requestedLeague = '';
|
||||
const api = {
|
||||
searchPlayers: () => {
|
||||
searchPlayers: (filters: { league: string }) => {
|
||||
calls += 1;
|
||||
requestedLeague = filters.league;
|
||||
return of({ count: 0, results: [] });
|
||||
},
|
||||
} as unknown as PlayerApiService;
|
||||
const component = new AppComponent(api);
|
||||
|
||||
component.onFilterChange();
|
||||
component.flushPendingSearch();
|
||||
component.filters.league = 'LBA';
|
||||
component.filters.league = 'ABA';
|
||||
|
||||
assert.equal(calls, 0);
|
||||
|
||||
component.search();
|
||||
|
||||
assert.equal(calls, 1);
|
||||
assert.equal(requestedLeague, 'ABA');
|
||||
});
|
||||
|
||||
it('exposes role options and sends the selected role as a filter', () => {
|
||||
|
||||
@@ -69,7 +69,6 @@ export class AppComponent {
|
||||
loading = false;
|
||||
errorMessage = '';
|
||||
activeSort: SortKey = 'efficiency_rating';
|
||||
private pendingSearch: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
constructor(private readonly playerApi: PlayerApiService) {}
|
||||
|
||||
@@ -78,7 +77,6 @@ export class AppComponent {
|
||||
}
|
||||
|
||||
search(): void {
|
||||
this.cancelPendingSearch();
|
||||
this.loading = true;
|
||||
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 {
|
||||
this.filters = {
|
||||
q: '',
|
||||
@@ -182,10 +167,4 @@ export class AppComponent {
|
||||
return players.find((player) => player.id === this.selectedPlayer?.id) ?? null;
|
||||
}
|
||||
|
||||
private cancelPendingSearch(): void {
|
||||
if (this.pendingSearch) {
|
||||
clearTimeout(this.pendingSearch);
|
||||
this.pendingSearch = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user