composant recherche et filtre disponible dans toute l'application
This commit is contained in:
@@ -29,24 +29,24 @@
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleVerifiedFilter()"
|
||||
[class.ring-2]="filters.verified"
|
||||
[class.ring-purple-500]="filters.verified"
|
||||
[class.ring-2]="filters().verified"
|
||||
[class.ring-purple-500]="filters().verified"
|
||||
class="group relative flex items-center justify-between gap-3 px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 hover:border-purple-500 dark:hover:border-purple-400 transition-all bg-white dark:bg-gray-800"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center"
|
||||
[class.bg-purple-100]="filters.verified"
|
||||
[class.dark:bg-purple-900]="filters.verified"
|
||||
[class.bg-purple-100]="filters().verified"
|
||||
[class.dark:bg-purple-900]="filters().verified"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
[class.text-purple-600]="filters.verified"
|
||||
[class.dark:text-purple-400]="filters.verified"
|
||||
[class.text-gray-400]="!filters.verified"
|
||||
[class.text-purple-600]="filters().verified"
|
||||
[class.dark:text-purple-400]="filters().verified"
|
||||
[class.text-gray-400]="!filters().verified"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
@@ -58,7 +58,7 @@
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300"> Profils vérifiés </span>
|
||||
</div>
|
||||
|
||||
@if (filters.verified) {
|
||||
@if (filters().verified) {
|
||||
<div
|
||||
class="flex-shrink-0 w-5 h-5 rounded-full bg-purple-600 text-white flex items-center justify-center"
|
||||
>
|
||||
@@ -91,7 +91,7 @@
|
||||
<!-- Tags des filtres actifs -->
|
||||
@if (hasActiveFilters()) {
|
||||
<div class="flex flex-wrap gap-2 mt-4">
|
||||
@if (filters.verified) {
|
||||
@if (filters().verified) {
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 text-xs font-medium"
|
||||
>
|
||||
@@ -126,11 +126,11 @@
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
@if (filters.secteur) {
|
||||
@if (filters().secteur) {
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 text-xs font-medium"
|
||||
>
|
||||
{{ filters.secteur }}
|
||||
{{ filters().secteur }}
|
||||
<button
|
||||
type="button"
|
||||
(click)="selectSector(null)"
|
||||
@@ -149,11 +149,11 @@
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
@if (filters.profession) {
|
||||
@if (filters().profession) {
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 text-xs font-medium"
|
||||
>
|
||||
{{ filters.profession }}
|
||||
{{ filters().profession }}
|
||||
<button
|
||||
type="button"
|
||||
(click)="selectProfession(null)"
|
||||
@@ -181,8 +181,8 @@
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleSectorDropdown()"
|
||||
[class.ring-2]="filters.secteur"
|
||||
[class.ring-purple-500]="filters.secteur"
|
||||
[class.ring-2]="filters().secteur"
|
||||
[class.ring-purple-500]="filters().secteur"
|
||||
class="w-full flex items-center justify-between gap-3 px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 hover:border-purple-500 dark:hover:border-purple-400 transition-all bg-white dark:bg-gray-800"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -191,9 +191,9 @@
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
[class.text-purple-600]="filters.secteur"
|
||||
[class.dark:text-purple-400]="filters.secteur"
|
||||
[class.text-gray-400]="!filters.secteur"
|
||||
[class.text-purple-600]="filters().secteur"
|
||||
[class.dark:text-purple-400]="filters().secteur"
|
||||
[class.text-gray-400]="!filters().secteur"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
@@ -203,7 +203,7 @@
|
||||
<path d="M16.5 6.5h-1v8.75a1.25 1.25 0 102.5 0V8a1.5 1.5 0 00-1.5-1.5z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ filters.secteur || 'Secteur' }}
|
||||
{{ filters().secteur || 'Secteur' }}
|
||||
</span>
|
||||
</div>
|
||||
<svg
|
||||
@@ -229,8 +229,8 @@
|
||||
type="button"
|
||||
(click)="selectSector(null)"
|
||||
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"
|
||||
[class.dark:bg-purple-900]="!filters.secteur"
|
||||
[class.bg-purple-50]="!filters().secteur"
|
||||
[class.dark:bg-purple-900]="!filters().secteur"
|
||||
>
|
||||
<span class="text-gray-700 dark:text-gray-300">Tous les secteurs</span>
|
||||
</button>
|
||||
@@ -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"
|
||||
>
|
||||
<span class="text-gray-700 dark:text-gray-300">{{ sector.nom }}</span>
|
||||
</button>
|
||||
@@ -255,8 +255,8 @@
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleProfessionDropdown()"
|
||||
[class.ring-2]="filters.profession"
|
||||
[class.ring-purple-500]="filters.profession"
|
||||
[class.ring-2]="filters().profession"
|
||||
[class.ring-purple-500]="filters().profession"
|
||||
class="w-full flex items-center justify-between gap-3 px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 hover:border-purple-500 dark:hover:border-purple-400 transition-all bg-white dark:bg-gray-800"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -265,9 +265,9 @@
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
[class.text-purple-600]="filters.profession"
|
||||
[class.dark:text-purple-400]="filters.profession"
|
||||
[class.text-gray-400]="!filters.profession"
|
||||
[class.text-purple-600]="filters().profession"
|
||||
[class.dark:text-purple-400]="filters().profession"
|
||||
[class.text-gray-400]="!filters().profession"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
@@ -279,7 +279,7 @@
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ filters.profession || 'Profession' }}
|
||||
{{ filters().profession || 'Profession' }}
|
||||
</span>
|
||||
</div>
|
||||
<svg
|
||||
@@ -305,8 +305,8 @@
|
||||
type="button"
|
||||
(click)="selectProfession(null)"
|
||||
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"
|
||||
[class.dark:bg-purple-900]="!filters.profession"
|
||||
[class.bg-purple-50]="!filters().profession"
|
||||
[class.dark:bg-purple-900]="!filters().profession"
|
||||
>
|
||||
<span class="text-gray-700 dark:text-gray-300">Toutes les professions</span>
|
||||
</button>
|
||||
@@ -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"
|
||||
>
|
||||
<span class="text-gray-700 dark:text-gray-300">{{ profile.profession }}</span>
|
||||
</button>
|
||||
@@ -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"
|
||||
>
|
||||
<span class="text-gray-700 dark:text-gray-300">{{ sort.label }}</span>
|
||||
</button>
|
||||
|
||||
@@ -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<SearchFilters>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="w-full space-y-4 sm:space-y-6 z-[800]">
|
||||
<!-- Filtres -->
|
||||
<div>
|
||||
<app-filter (filtersChanged)="onFiltersChanged($event)" />
|
||||
<app-filter />
|
||||
</div>
|
||||
|
||||
<!-- Barre de recherche -->
|
||||
|
||||
@@ -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<SearchFilters>();
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user