TypeScript 50.6%
JavaScript 46.1%
SQL 2.1%
Shell 0.6%
CSS 0.5%
1"use client";2import { useRouter, useSearchParams } from "next/navigation";3import { useCallback, useState } from "react";4import { IconFilter, IconClose } from "./icons";56const SALE_CATS = [["maison", "Maison"], ["condo", "Condo"], ["plex", "Plex"], ["terrain", "Terrain"], ["chalet", "Chalet"], ["commercial", "Commercial"]];7const RENT_CATS = [["logement", "Logement"], ["condo", "Condo"], ["maison", "Maison"], ["chambre", "Chambre"]];8const SALE_PRICES = [200000, 300000, 400000, 500000, 600000, 750000, 1000000, 1500000, 2000000];9const RENT_PRICES = [800, 1000, 1200, 1500, 1800, 2000, 2500, 3000, 4000];1011export function useFilterNav() {12 const router = useRouter(); const sp = useSearchParams();13 const set = useCallback((patch: Record<string, string | null>) => {14 const n = new URLSearchParams(sp.toString());15 for (const [k, v] of Object.entries(patch)) { if (v == null || v === "") n.delete(k); else n.set(k, v); }16 n.delete("page");17 router.push(`/recherche?${n.toString()}`, { scroll: false });18 }, [router, sp]);19 return { sp, set };20}2122export function FilterBar() {23 const { sp, set } = useFilterNav();24 const rent = sp.get("marche") === "location";25 const cats = (sp.get("type") ?? "").split(",").filter(Boolean);26 const [more, setMore] = useState(false);27 const toggleCat = (c: string) => set({ type: (cats.includes(c) ? cats.filter((x) => x !== c) : [...cats, c]).join(",") || null });28 const PRICES = rent ? RENT_PRICES : SALE_PRICES;29 const active = ["type", "prix_min", "prix_max", "chambres", "sdb", "superficie", "annee"].filter((k) => sp.get(k)).length;30 const fmt = (v: number) => rent ? `${v.toLocaleString("fr-CA")} $` : v >= 1e6 ? `${(v / 1e6).toLocaleString("fr-CA")} M$` : `${v / 1000} k$`;31 return (32 <div className="space-y-2">33 <div className="flex gap-2 overflow-x-auto no-scrollbar py-1 -mx-4 px-4 md:mx-0 md:px-0">34 <div className="inline-flex rounded-full border border-line p-0.5 bg-white shrink-0" role="group" aria-label="Marché">35 <button type="button" onClick={() => set({ marche: "achat", type: null, prix_min: null, prix_max: null })} className={`px-3 py-1.5 rounded-full text-sm font-semibold ${!rent ? "bg-brand text-white" : "text-ink-2"}`} aria-pressed={!rent}>Acheter</button>36 <button type="button" onClick={() => set({ marche: "location", type: null, prix_min: null, prix_max: null })} className={`px-3 py-1.5 rounded-full text-sm font-semibold ${rent ? "bg-brand text-white" : "text-ink-2"}`} aria-pressed={rent}>Louer</button>37 </div>38 {(rent ? RENT_CATS : SALE_CATS).map(([v, l]) => <button key={v} type="button" className="tv-chip shrink-0" data-active={cats.includes(v)} onClick={() => toggleCat(v)} aria-pressed={cats.includes(v)}>{l}</button>)}39 <select aria-label="Prix minimum" className="tv-chip shrink-0 appearance-none" value={sp.get("prix_min") ?? ""} onChange={(e) => set({ prix_min: e.target.value || null })}>40 <option value="">Prix min</option>{PRICES.map((p) => <option key={p} value={p}>{fmt(p)}</option>)}41 </select>42 <select aria-label="Prix maximum" className="tv-chip shrink-0 appearance-none" value={sp.get("prix_max") ?? ""} onChange={(e) => set({ prix_max: e.target.value || null })}>43 <option value="">Prix max</option>{PRICES.map((p) => <option key={p} value={p}>{fmt(p)}</option>)}44 </select>45 <select aria-label="Chambres minimum" className="tv-chip shrink-0 appearance-none" value={sp.get("chambres") ?? ""} onChange={(e) => set({ chambres: e.target.value || null })}>46 <option value="">Chambres</option>{[1, 2, 3, 4, 5].map((n) => <option key={n} value={n}>{n}+ ch.</option>)}47 </select>48 <button type="button" className="tv-chip shrink-0" data-active={more} onClick={() => setMore(!more)} aria-expanded={more}><IconFilter width={14} height={14} /> Plus de filtres{active ? ` (${active})` : ""}</button>49 {active > 0 && <button type="button" className="tv-chip shrink-0 !text-ink-3" onClick={() => set({ type: null, prix_min: null, prix_max: null, chambres: null, sdb: null, superficie: null, annee: null })}><IconClose width={14} height={14} /> Effacer</button>}50 </div>51 {more && (52 <div className="tv-card p-3 grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">53 <label className="space-y-1"><span className="tv-label">Salles de bain</span><select className="tv-chip w-full appearance-none" value={sp.get("sdb") ?? ""} onChange={(e) => set({ sdb: e.target.value || null })}><option value="">Indifférent</option>{[1, 2, 3].map((n) => <option key={n} value={n}>{n}+</option>)}</select></label>54 <label className="space-y-1"><span className="tv-label">Superficie min (pi²)</span><input type="number" inputMode="numeric" className="tv-chip w-full !pl-3" placeholder="ex. 1000" defaultValue={sp.get("superficie") ?? ""} onBlur={(e) => set({ superficie: e.target.value || null })} /></label>55 {!rent && <label className="space-y-1"><span className="tv-label">Construite après</span><input type="number" inputMode="numeric" className="tv-chip w-full !pl-3" placeholder="ex. 2000" defaultValue={sp.get("annee") ?? ""} onBlur={(e) => set({ annee: e.target.value || null })} /></label>}56 <label className="space-y-1"><span className="tv-label">Trier</span><select className="tv-chip w-full appearance-none" value={sp.get("tri") ?? "recent"} onChange={(e) => set({ tri: e.target.value })}><option value="recent">Plus récentes</option><option value="price_asc">Prix croissant</option><option value="price_desc">Prix décroissant</option>{!rent && <option value="deal">Écart estimation / prix</option>}<option value="area_desc">Superficie</option></select></label>57 </div>58 )}59 </div>60 );61}62