-
- @if (api.description) {
-
{{ api.description }}
- }
-
- {{ api.type }}
-
- {{ api.status }}
-
+ @if (!searchLoading() && searchResults().length === 0) {
+
Aucune API trouvée pour « {{ searchQuery() }} »
+ }
+
+ @for (api of searchResults(); track api.id) {
+
+
+
+
+
+ @if (api.description) {
+
{{ api.description }}
+ }
+
+ {{ api.type }}
+ {{ api.status }}
- }
-
- }
-
- @if (browseData()!.subcategories.length === 0 && browseData()!.apis.length === 0) {
-
-
Aucun contenu dans cette catégorie.
+
+ }
+
+ @if (searchTotalPages() > 1) {
+
+
+ Page {{ searchPage() }} / {{ searchTotalPages() }}
+
}
}
@@ -192,6 +244,7 @@ import { CategoryBrowseResponse, Category, CategoryWithCounts } from '@datacat/s
export class BrowseComponent implements OnInit {
private route = inject(ActivatedRoute);
private categoryService = inject(CategoryService);
+ private apiService = inject(ApiService);
private destroyRef = inject(DestroyRef);
browseData = signal
(null);
@@ -199,6 +252,19 @@ export class BrowseComponent implements OnInit {
error = signal(null);
categoryId = signal(null);
+ /* Signaux recherche */
+ searchQuery = signal('');
+ searchResults = signal([]);
+ searchTotal = signal(0);
+ searchPage = signal(1);
+ searchLoading = signal(false);
+ readonly searchLimit = 12;
+
+ isSearchMode = computed(() => this.searchQuery().trim().length > 0);
+ searchTotalPages = computed(() => Math.ceil(this.searchTotal() / this.searchLimit));
+
+ private searchDebounce: ReturnType | null = null;
+
/* Signaux pour la modale */
showFormModal = signal(false);
formMode = signal<'create' | 'edit'>('create');
@@ -302,6 +368,37 @@ export class BrowseComponent implements OnInit {
});
}
+ onSearchChange(value: string) {
+ this.searchQuery.set(value);
+ this.searchPage.set(1);
+ if (this.searchDebounce) clearTimeout(this.searchDebounce);
+ if (value.trim()) {
+ this.searchDebounce = setTimeout(() => this.loadSearch(), 400);
+ }
+ }
+
+ loadSearch() {
+ this.searchLoading.set(true);
+ this.apiService.list({
+ search: this.searchQuery(),
+ page: this.searchPage(),
+ limit: this.searchLimit,
+ }).subscribe({
+ next: (res) => {
+ this.searchResults.set(res.items);
+ this.searchTotal.set(res.total);
+ this.searchLoading.set(false);
+ },
+ error: () => this.searchLoading.set(false),
+ });
+ }
+
+ setSearchPage(p: number) {
+ if (p < 1 || p > this.searchTotalPages()) return;
+ this.searchPage.set(p);
+ this.loadSearch();
+ }
+
typeBadgeClass(type: string): string {
return type === 'ASYNCAPI'
? 'fr-badge fr-badge--sm fr-badge--purple-glycine'