Skip to content

Commit 8b72497

Browse files
Catalog fixes:
- sorting now works - filtering now works
1 parent 166ae91 commit 8b72497

5 files changed

Lines changed: 74 additions & 17 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ This project has an **Angular 20** frontend (this repo) and a **Flask** backend
3838

3939
- Auth: `POST /login`, `GET /version` (and token usage via interceptor).
4040
- Tasks: `GET/POST /tasks`, `POST /task-add`, `POST /task-update`, `GET /task-get`.
41-
- Catalogs: `GET /catalogs/search`, `GET /catalogs/list`.
41+
- Catalogs: `GET /catalogs/search`, `GET /catalogs/list`. List accepts optional query params: `page`, `per_page`, `sort_by`, `sort_order`, `catalog`, `name`, `constellation` (IAU 3-letter abbreviation, e.g. Cyg, Sgr). Backend must filter by these when provided.
4242
- Other: `GET /scopes` (telescopes), `POST /night-plan`.
4343

4444
When adding a new endpoint, add the route and DTOs on the backend, then add or update the Angular model and a service method that calls `Hevelius.apiUrl + '/your-path'` with the same contract.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Added About panel
99
- Fixed ExpressionChangedAfterItHasBeenCheckedError exception
1010
- The footer on login page now has links to a ChangeLog
11+
- Fixed catalog sorting and filtering
1112

1213
0.3.0 (2025-04-22)
1314

src/app/components/catalogs/catalogs.component.html

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
<div class="container">
22
<div class="filters" [@filterExpand]="isFilterVisible ? 'expanded' : 'collapsed'">
3-
<form [formGroup]="filterForm" class="filter-form">
3+
<form [formGroup]="filterForm" class="filter-form" (ngSubmit)="onFilterSubmit($event)">
4+
<mat-form-field class="filter-field">
5+
<mat-label>Name</mat-label>
6+
<input matInput formControlName="name" type="text">
7+
</mat-form-field>
8+
49
<mat-form-field class="filter-field">
510
<mat-label>Catalog</mat-label>
6-
<input matInput formControlName="catalog">
11+
<input matInput formControlName="catalog" type="text">
712
</mat-form-field>
813

914
<mat-form-field class="filter-field">
10-
<mat-label>Object Name</mat-label>
11-
<input matInput formControlName="name">
15+
<mat-label>Constellation</mat-label>
16+
<input matInput formControlName="constellation" type="text"
17+
[matAutocomplete]="constAuto">
18+
<mat-autocomplete #constAuto="matAutocomplete">
19+
<mat-option *ngFor="let abbr of filteredConstellations$ | async" [value]="abbr">
20+
{{ abbr }}
21+
</mat-option>
22+
</mat-autocomplete>
1223
</mat-form-field>
1324

1425
<div class="filter-actions">
15-
<button mat-button (click)="clearFilters()">Clear</button>
16-
<button mat-raised-button color="primary" (click)="applyFilters()">Apply</button>
26+
<button type="button" mat-button (click)="clearFilters()">Clear</button>
27+
<button type="submit" mat-raised-button color="primary">Apply</button>
1728
</div>
1829
</form>
1930
</div>

src/app/components/catalogs/catalogs.component.ts

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,33 @@ import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angul
88
import { trigger, state, style, transition, animate } from '@angular/animations';
99
import { TopBarService } from '../../services/top-bar.service';
1010
import { MatTableModule } from '@angular/material/table';
11+
import { AsyncPipe, NgForOf } from '@angular/common';
12+
import { Observable } from 'rxjs';
13+
import { map, startWith } from 'rxjs/operators';
1114

1215
import { MatFormFieldModule } from '@angular/material/form-field';
1316
import { MatInputModule } from '@angular/material/input';
1417
import { MatButtonModule } from '@angular/material/button';
1518
import { MatSelectModule } from '@angular/material/select';
1619
import { MatCheckboxModule } from '@angular/material/checkbox';
20+
import { MatAutocompleteModule } from '@angular/material/autocomplete';
21+
22+
/** IAU 88 constellation three-letter abbreviations (official). */
23+
export const CONSTELLATION_ABBREVIATIONS: string[] = [
24+
'And', 'Ant', 'Aps', 'Aqr', 'Aql', 'Ara', 'Ari', 'Aur', 'Boo', 'Cae', 'Cam', 'Cnc', 'CVn', 'CMa', 'CMi',
25+
'Cap', 'Car', 'Cas', 'Cen', 'Cep', 'Cet', 'Cha', 'Cir', 'Col', 'Com', 'CrA', 'CrB', 'Crv', 'Crt', 'Cru',
26+
'Cyg', 'Del', 'Dor', 'Dra', 'Equ', 'Eri', 'For', 'Gem', 'Gru', 'Her', 'Hor', 'Hya', 'Hyi', 'Ind', 'Lac',
27+
'Leo', 'LMi', 'Lep', 'Lib', 'Lup', 'Lyn', 'Lyr', 'Men', 'Mic', 'Mon', 'Mus', 'Nor', 'Oct', 'Oph', 'Ori',
28+
'Pav', 'Peg', 'Per', 'Phe', 'Pic', 'Psc', 'PsA', 'Pup', 'Pyx', 'Ret', 'Sge', 'Sgr', 'Sco', 'Scl', 'Sct',
29+
'Ser', 'Sex', 'Tau', 'Tel', 'Tri', 'TrA', 'Tuc', 'UMa', 'UMi', 'Vel', 'Vir', 'Vol', 'Vul'
30+
];
1731

1832
interface LoadObjectsParams {
1933
sort_by?: string;
2034
sort_order?: 'asc' | 'desc';
2135
catalog?: string;
2236
name?: string;
37+
constellation?: string;
2338
}
2439

2540
@Component({
@@ -52,6 +67,9 @@ interface LoadObjectsParams {
5267
MatButtonModule,
5368
MatSelectModule,
5469
MatCheckboxModule,
70+
MatAutocompleteModule,
71+
NgForOf,
72+
AsyncPipe,
5573
FormsModule,
5674
ReactiveFormsModule
5775
]
@@ -81,9 +99,14 @@ export class CatalogsComponent implements OnInit, OnDestroy {
8199
private subscriptions: Subscription[] = [];
82100
filterForm: FormGroup;
83101
isFilterVisible = false;
102+
filteredConstellations$: Observable<string[]>;
84103

85104
constructor() {
86105
this.initFilterForm();
106+
this.filteredConstellations$ = this.filterForm.get('constellation')!.valueChanges.pipe(
107+
startWith(''),
108+
map((v: string | null) => this.filterConstellations(v ?? ''))
109+
);
87110

88111
// Set initial state for top bar in constructor
89112
setTimeout(() => {
@@ -97,11 +120,24 @@ export class CatalogsComponent implements OnInit, OnDestroy {
97120

98121
private initFilterForm() {
99122
this.filterForm = this.fb.group({
123+
name: [null],
100124
catalog: [null],
101-
name: [null]
125+
constellation: [null]
102126
});
103127
}
104128

129+
private filterConstellations(value: string): string[] {
130+
const v = (value || '').trim().toLowerCase();
131+
if (!v) return CONSTELLATION_ABBREVIATIONS;
132+
return CONSTELLATION_ABBREVIATIONS.filter(c => c.toLowerCase().startsWith(v) || c.toLowerCase().includes(v));
133+
}
134+
135+
/** Prevent native form submit (which would reload and clear fields); apply filters on Enter. */
136+
onFilterSubmit(event: Event) {
137+
event.preventDefault();
138+
this.applyFilters();
139+
}
140+
105141
ngOnInit() {
106142
this.subscriptions.push(
107143
this.catalogsService.getTotalObjects().subscribe(total => {
@@ -128,23 +164,28 @@ export class CatalogsComponent implements OnInit, OnDestroy {
128164
});
129165
}
130166

131-
applyFilters() {
132-
const filters = this.filterForm.value;
133-
Object.keys(filters).forEach(key => {
134-
if (filters[key] === null || filters[key] === '') {
135-
delete filters[key];
136-
}
137-
});
167+
/** Current filter params from form (non-empty only). Used so pagination/sort keep filters. */
168+
private getFilterParams(): Partial<LoadObjectsParams> {
169+
const v = this.filterForm.value;
170+
const out: Partial<LoadObjectsParams> = {};
171+
if (v.name != null && v.name !== '') out.name = String(v.name).trim();
172+
if (v.catalog != null && v.catalog !== '') out.catalog = String(v.catalog).trim();
173+
if (v.constellation != null && v.constellation !== '') out.constellation = String(v.constellation).trim();
174+
return out;
175+
}
138176

177+
applyFilters() {
178+
this.currentPage = 1;
139179
this.loadObjects({
140-
...filters,
180+
...this.getFilterParams(),
141181
sort_by: this.currentSort.sort_by,
142182
sort_order: this.currentSort.sort_order
143183
});
144184
}
145185

146186
clearFilters() {
147-
this.filterForm.reset();
187+
this.filterForm.reset({ name: null, catalog: null, constellation: null });
188+
this.currentPage = 1;
148189
this.loadObjects({
149190
sort_by: this.currentSort.sort_by,
150191
sort_order: this.currentSort.sort_order
@@ -173,6 +214,7 @@ export class CatalogsComponent implements OnInit, OnDestroy {
173214
this.currentPage = event.pageIndex + 1;
174215
this.pageSize = event.pageSize;
175216
this.loadObjects({
217+
...this.getFilterParams(),
176218
sort_by: this.currentSort.sort_by,
177219
sort_order: this.currentSort.sort_order
178220
});
@@ -185,6 +227,7 @@ export class CatalogsComponent implements OnInit, OnDestroy {
185227
};
186228

187229
this.loadObjects({
230+
...this.getFilterParams(),
188231
sort_by: this.currentSort.sort_by,
189232
sort_order: this.currentSort.sort_order
190233
});

src/app/services/catalogs.service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,15 @@ export class CatalogsService {
5757
);
5858
}
5959

60+
/** GET /catalogs/list. Backend should accept optional query params: catalog, name, constellation (IAU 3-letter, e.g. Cyg, Sgr). */
6061
listObjects(params: {
6162
page?: number;
6263
per_page?: number;
6364
sort_by?: string;
6465
sort_order?: string;
6566
catalog?: string;
6667
name?: string;
68+
constellation?: string;
6769
} = {}): Observable<CatalogListResponse> {
6870
return this.http.get<CatalogListResponse>(
6971
`${this.baseUrl}/list`,

0 commit comments

Comments
 (0)