SPB Git

spb/vrai-prix Public

Vrai-Prix — l'évaluation du vrai prix des propriétés résidentielles au Québec.

TypeScript 96.7% CSS 3.1%
3.3 KB · 96 lines tsx
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2"use client";3import { useRef, useState } from "react";4import { useRouter } from "next/navigation";5import { useLang } from "./LangContext";67export interface Suggestion {8  id: string;9  adresse: string | null;10  apt: string | null;11  municipalite: string | null;12  typeProp: string;13}1415export default function SearchBox({16  onPick,17  placeholder,18}: {19  /** Si fourni : appelé au choix d'une adresse (le champ se vide) au lieu de naviguer. */20  onPick?: (s: Suggestion) => void;21  placeholder?: string;22}) {23  const { t } = useLang();24  const router = useRouter();25  const [q, setQ] = useState("");26  const [sugg, setSugg] = useState<Suggestion[]>([]);27  const [open, setOpen] = useState(false);28  const [searched, setSearched] = useState(false);29  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);3031  const onChange = (value: string) => {32    setQ(value);33    if (timer.current) clearTimeout(timer.current);34    if (value.trim().length < 3) {35      setSugg([]);36      setSearched(false);37      return;38    }39    timer.current = setTimeout(async () => {40      const res = await fetch(`/api/search?q=${encodeURIComponent(value)}`);41      const data = (await res.json()) as { results: Suggestion[] };42      setSugg(data.results);43      setSearched(true);44      setOpen(true);45    }, 200);46  };4748  return (49    <div className="relative w-full">50      <input51        value={q}52        onChange={(e) => onChange(e.target.value)}53        onFocus={() => setOpen(true)}54        onBlur={() => setTimeout(() => setOpen(false), 200)}55        placeholder={placeholder ?? t("searchPlaceholder")}56        className="vp-input text-base"57        aria-label={placeholder ?? t("searchPlaceholder")}58      />59      {open && sugg.length > 0 && (60        <ul className="absolute z-[1200] mt-2 w-full overflow-hidden rounded-[10px] border-[1.5px] border-ink bg-surface shadow-[6px_6px_0_rgba(20,24,20,0.85)]">61          {sugg.map((s) => (62            <li key={s.id} className="border-b border-[rgba(20,24,20,0.1)] last:border-0">63              <button64                className="flex w-full items-baseline justify-between gap-2 px-4 py-3 text-left transition-colors hover:bg-lime-soft"65                onMouseDown={() => {66                  if (onPick) {67                    onPick(s);68                    setQ("");69                    setSugg([]);70                    setSearched(false);71                  } else {72                    router.push(`/estimation/${s.id}`);73                  }74                }}75              >76                <span className="vp-display font-bold">77                  {s.adresse}78                  {s.apt ? ` app. ${s.apt}` : ""}79                </span>80                <span className="vp-mono shrink-0 text-[11px] uppercase tracking-[0.05em] text-ink-3">81                  {s.municipalite}82                </span>83              </button>84            </li>85          ))}86        </ul>87      )}88      {open && searched && sugg.length === 0 && q.trim().length >= 3 && (89        <div className="vp-mono absolute z-[1200] mt-2 w-full rounded-[10px] border-[1.5px] border-ink bg-surface px-4 py-3 text-[12px] uppercase tracking-[0.04em] text-ink-2 shadow-[6px_6px_0_rgba(20,24,20,0.85)]">90          {t("searchNoResult")}91        </div>92      )}93    </div>94  );95}96