From 4716e8262819d313302f0dbb382589ef4590e45b Mon Sep 17 00:00:00 2001 From: styve Lioumba Date: Fri, 28 Nov 2025 11:21:39 +0100 Subject: [PATCH] composant recherche et filtre disponible dans toute l'application --- src/app/domain/{ => search}/search-filters.ts | 0 src/app/domain/search/search.repository.ts | 12 ++++ .../infrastructure/search/search.service.ts | 57 +++++++++++++++ src/app/routes/home/home.component.ts | 2 +- .../profile-list/profile-list.component.ts | 7 +- .../features/filter/filter.component.html | 70 +++++++++---------- .../features/filter/filter.component.ts | 44 +++--------- .../features/search/search.component.html | 2 +- .../features/search/search.component.ts | 21 ++---- 9 files changed, 128 insertions(+), 87 deletions(-) rename src/app/domain/{ => search}/search-filters.ts (100%) create mode 100644 src/app/domain/search/search.repository.ts create mode 100644 src/app/infrastructure/search/search.service.ts diff --git a/src/app/domain/search-filters.ts b/src/app/domain/search/search-filters.ts similarity index 100% rename from src/app/domain/search-filters.ts rename to src/app/domain/search/search-filters.ts diff --git a/src/app/domain/search/search.repository.ts b/src/app/domain/search/search.repository.ts new file mode 100644 index 0000000..de7415f --- /dev/null +++ b/src/app/domain/search/search.repository.ts @@ -0,0 +1,12 @@ +import { SearchFilters } from '@app/domain/search/search-filters'; +import { WritableSignal } from '@angular/core'; + +export interface SearchRepository { + search(query: string): void; + sortBy(value: string): void; + filterByProfileVerified(): void; + filterBySecteur(secteur: string): void; + filterByProfession(profession: string): void; + reset(): void; + getFilters(): WritableSignal; +} diff --git a/src/app/infrastructure/search/search.service.ts b/src/app/infrastructure/search/search.service.ts new file mode 100644 index 0000000..05ba7b0 --- /dev/null +++ b/src/app/infrastructure/search/search.service.ts @@ -0,0 +1,57 @@ +import { Injectable, signal, WritableSignal } from '@angular/core'; +import { SearchRepository } from '@app/domain/search/search.repository'; +import { SearchFilters } from '@app/domain/search/search-filters'; + +@Injectable({ + providedIn: 'root', +}) +export class SearchService implements SearchRepository { + private filters = signal({ + search: '', + verified: false, + secteur: null, + profession: null, + sort: 'recent', + }); + + filterByProfession(profession: string | null) { + const filter = { ...this.filters(), profession }; + this.filters.set(filter); + } + + filterByProfileVerified() { + const filters = { ...this.filters(), verified: true }; + this.filters.set(filters); + } + + filterBySecteur(secteur: string | null) { + const filters = { ...this.filters(), secteur }; + this.filters.set(filters); + } + + getFilters(): WritableSignal { + return this.filters; + } + + reset() { + const filters = { + ...this.filters(), + verified: false, + sort: 'recent', + search: '', + profession: null, + secteur: null, + }; + this.filters.set(filters); + } + + search(query: string) { + const filters = { ...this.filters(), search: query }; + this.filters.set(filters); + } + + sortBy(value: string) { + const filters = { ...this.filters(), sort: value }; + this.filters.set(filters); + } +} diff --git a/src/app/routes/home/home.component.ts b/src/app/routes/home/home.component.ts index a927182..7dcbb9f 100644 --- a/src/app/routes/home/home.component.ts +++ b/src/app/routes/home/home.component.ts @@ -1,7 +1,7 @@ import { Component, inject } from '@angular/core'; import { SearchComponent } from '@app/shared/features/search/search.component'; import { Router } from '@angular/router'; -import { SearchFilters } from '@app/domain/search-filters'; +import { SearchFilters } from '@app/domain/search/search-filters'; @Component({ selector: 'app-home', diff --git a/src/app/routes/profile/profile-list/profile-list.component.ts b/src/app/routes/profile/profile-list/profile-list.component.ts index c0618ec..e8dbb4c 100644 --- a/src/app/routes/profile/profile-list/profile-list.component.ts +++ b/src/app/routes/profile/profile-list/profile-list.component.ts @@ -5,7 +5,8 @@ import { UntilDestroy } from '@ngneat/until-destroy'; import { ProfileFacade } from '@app/ui/profiles/profile.facade'; import { LoadingComponent } from '@app/shared/components/loading/loading.component'; import { Router } from '@angular/router'; -import { SearchFilters } from '@app/domain/search-filters'; +import { SearchFilters } from '@app/domain/search/search-filters'; +import { SearchService } from '@app/infrastructure/search/search.service'; @Component({ selector: 'app-profile-list', @@ -16,6 +17,7 @@ import { SearchFilters } from '@app/domain/search-filters'; }) @UntilDestroy() export class ProfileListComponent implements OnInit { + private readonly searchService = inject(SearchService); private readonly facade = inject(ProfileFacade); private readonly router = inject(Router); @@ -23,12 +25,13 @@ export class ProfileListComponent implements OnInit { protected readonly loading = this.facade.loading; protected readonly error = this.facade.error; + protected readonly searchFilters = this.searchService.getFilters(); + ngOnInit() { this.facade.load(); } showNewQuery(filters: SearchFilters) { - console.log(filters); this.router.navigate(['/profiles'], { queryParams: { search: filters.search } }); } } diff --git a/src/app/shared/features/filter/filter.component.html b/src/app/shared/features/filter/filter.component.html index 6d9c35d..1446037 100644 --- a/src/app/shared/features/filter/filter.component.html +++ b/src/app/shared/features/filter/filter.component.html @@ -29,24 +29,24 @@ } - @if (filters.secteur) { + @if (filters().secteur) { - {{ filters.secteur }} + {{ filters().secteur }} @@ -239,8 +239,8 @@ type="button" (click)="selectSector(sector.nom)" class="w-full px-4 py-2.5 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" - [class.bg-purple-50]="filters.secteur === sector.nom" - [class.dark:bg-purple-900]="filters.secteur === sector.nom" + [class.bg-purple-50]="filters().secteur === sector.nom" + [class.dark:bg-purple-900]="filters().secteur === sector.nom" > {{ sector.nom }} @@ -255,8 +255,8 @@ @@ -315,8 +315,8 @@ type="button" (click)="selectProfession(profile.profession)" class="w-full px-4 py-2.5 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" - [class.bg-purple-50]="filters.profession === profile.profession" - [class.dark:bg-purple-900]="filters.profession === profile.profession" + [class.bg-purple-50]="filters().profession === profile.profession" + [class.dark:bg-purple-900]="filters().profession === profile.profession" > {{ profile.profession }} @@ -374,8 +374,8 @@ type="button" (click)="selectSort(sort)" class="w-full px-4 py-2.5 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" - [class.bg-purple-50]="filters.sort === sort.value" - [class.dark:bg-purple-900]="filters.sort === sort.value" + [class.bg-purple-50]="filters().sort === sort.value" + [class.dark:bg-purple-900]="filters().sort === sort.value" > {{ sort.label }} diff --git a/src/app/shared/features/filter/filter.component.ts b/src/app/shared/features/filter/filter.component.ts index 2abe652..8c3b72e 100644 --- a/src/app/shared/features/filter/filter.component.ts +++ b/src/app/shared/features/filter/filter.component.ts @@ -1,8 +1,8 @@ -import { Component, inject, OnInit, output } from '@angular/core'; -import { SearchFilters } from '@app/domain/search-filters'; +import { Component, inject, OnInit } from '@angular/core'; import { SectorFacade } from '@app/ui/sectors/sector.facade'; import { ProfileFacade } from '@app/ui/profiles/profile.facade'; import { NgTemplateOutlet } from '@angular/common'; +import { SearchService } from '@app/infrastructure/search/search.service'; interface SortOption { value: string; @@ -18,7 +18,7 @@ interface SortOption { styleUrl: './filter.component.scss', }) export class FilterComponent implements OnInit { - filtersChanged = output(); + private readonly searchService = inject(SearchService); // État des dropdowns showSectorDropdown = false; @@ -26,13 +26,7 @@ export class FilterComponent implements OnInit { showSortDropdown = false; // Filtres - filters: SearchFilters = { - search: '', - verified: false, - secteur: null, - profession: null, - sort: 'recent', - }; + filters = this.searchService.getFilters(); protected readonly sectorFacade = inject(SectorFacade); protected readonly sectors = this.sectorFacade.sectors; @@ -57,13 +51,12 @@ export class FilterComponent implements OnInit { ]; get sortLabel(): string { - return this.sortOptions.find((s) => s.value === this.filters.sort)?.label || 'Trier par'; + return this.sortOptions.find((s) => s.value === this.filters().sort)?.label || 'Trier par'; } // Gestion du filtre "Vérifiés" toggleVerifiedFilter(): void { - this.filters.verified = !this.filters.verified; - this.emitFilters(); + this.filters().verified = !this.filters().verified; } // Gestion des dropdowns @@ -87,42 +80,27 @@ export class FilterComponent implements OnInit { // Sélection des filtres selectSector(sector: string | null): void { - this.filters.secteur = sector; + this.searchService.filterBySecteur(sector); this.showSectorDropdown = false; - this.emitFilters(); } selectProfession(profession: string | null): void { - this.filters.profession = profession; + this.searchService.filterByProfession(profession); this.showProfessionDropdown = false; - this.emitFilters(); } selectSort(sort: SortOption): void { - this.filters.sort = sort.value; + this.searchService.sortBy(sort.value); this.showSortDropdown = false; - this.emitFilters(); } // Vérifier si des filtres sont actifs hasActiveFilters(): boolean { - return this.filters.verified || !!this.filters.secteur || !!this.filters.profession; + return this.filters().verified || !!this.filters().secteur || !!this.filters().profession; } // Effacer tous les filtres clearAllFilters(): void { - this.filters = { - search: '', - verified: false, - secteur: null, - profession: null, - sort: 'recent', - }; - this.emitFilters(); - } - - // Émettre les changements de filtres - private emitFilters(): void { - this.filtersChanged.emit({ ...this.filters }); + this.searchService.reset(); } } diff --git a/src/app/shared/features/search/search.component.html b/src/app/shared/features/search/search.component.html index 5f57c84..4656624 100644 --- a/src/app/shared/features/search/search.component.html +++ b/src/app/shared/features/search/search.component.html @@ -1,7 +1,7 @@
- +
diff --git a/src/app/shared/features/search/search.component.ts b/src/app/shared/features/search/search.component.ts index 2cf2141..c88f199 100644 --- a/src/app/shared/features/search/search.component.ts +++ b/src/app/shared/features/search/search.component.ts @@ -1,8 +1,9 @@ import { Component, inject, output } from '@angular/core'; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; import { FilterComponent } from '@app/shared/features/filter/filter.component'; -import { SearchFilters } from '@app/domain/search-filters'; +import { SearchFilters } from '@app/domain/search/search-filters'; import { NgTemplateOutlet } from '@angular/common'; +import { SearchService } from '@app/infrastructure/search/search.service'; @Component({ selector: 'app-search', @@ -12,28 +13,18 @@ import { NgTemplateOutlet } from '@angular/common'; styleUrl: './search.component.scss', }) export class SearchComponent { + private readonly searchService = inject(SearchService); onSearchChange = output(); private formBuilder: FormBuilder = inject(FormBuilder); - // Filtres - filters: SearchFilters = { - search: '', - verified: false, - secteur: null, - profession: null, - sort: 'recent', - }; - searchForm = this.formBuilder.group({ search: new FormControl('', Validators.required), }); onSubmit() { const search = this.searchForm.value.search?.toLowerCase()!; - this.onSearchChange.emit({ ...this.filters, search }); - } - - onFiltersChanged(event: SearchFilters) { - this.filters = event; + this.searchService.search(search); + const filters = this.searchService.getFilters(); + this.onSearchChange.emit({ ...filters(), search }); } }