TypeScript 50.6%
JavaScript 46.1%
SQL 2.1%
Shell 0.6%
CSS 0.5%
1"use client";2import { useCallback, useEffect, useRef, useState } from "react";3import { useRouter } from "next/navigation";4import { MapView, type MapPoint } from "@/components/map-view";5import { SearchBar } from "@/components/search-bar";6import { ListingCardView } from "@/components/listing-card";7import type { ListingCard } from "@/lib/types";8import { IconClose } from "@/components/icons";910export function CarteView() {11 const [market, setMarket] = useState<"achat" | "location">("achat");12 const [cats, setCats] = useState<string[]>([]);13 const [points, setPoints] = useState<MapPoint[]>([]);14 const [sel, setSel] = useState<ListingCard | null>(null);15 const [center, setCenter] = useState<[number, number] | undefined>([45.55, -73.65]);16 const [zoom, setZoom] = useState(11);17 const [n, setN] = useState(0);18 const router = useRouter(); const bboxRef = useRef<[number, number, number, number] | null>(null); const timer = useRef<ReturnType<typeof setTimeout> | null>(null);19 const load = useCallback((bbox: [number, number, number, number], z: number) => {20 bboxRef.current = bbox;21 if (timer.current) clearTimeout(timer.current);22 timer.current = setTimeout(async () => {23 const q = new URLSearchParams({ marche: market, bbox: bbox.join(","), format: "points", tri: "recent" }); if (cats.length) q.set("type", cats.join(","));24 const r = await fetch(`/api/search?${q}`).then((r) => r.json()).catch(() => null);25 if (r?.points) { setN(r.points.length); setPoints(r.points); }26 }, 250);27 }, [market, cats]);28 useEffect(() => { if (bboxRef.current) load(bboxRef.current, zoom); }, [market, cats, load, zoom]);29 const pick = async (p: MapPoint) => { const r = await fetch(`/api/search?marche=${market}&q=&limit=1&bbox=${p.lng - 1e-6},${p.lat - 1e-6},${p.lng + 1e-6},${p.lat + 1e-6}`).then((r) => r.json()).catch(() => null); const item = r?.items?.find((i: ListingCard) => i.uid === p.uid) ?? r?.items?.[0]; setSel(item ?? null); };30 const CATS = market === "location" ? [["logement", "Logements"], ["condo", "Condos"], ["maison", "Maisons"]] : [["maison", "Maisons"], ["condo", "Condos"], ["plex", "Plex"], ["terrain", "Terrains"]];31 return (32 <div className="relative h-[calc(100dvh-56px-64px)] md:h-[calc(100dvh-64px)]">33 <MapView points={points} center={center} zoom={zoom} rent={market === "location"} activeUid={sel?.uid ?? null} onSelect={pick} onMove={(b, z) => { setZoom(z); load(b, z); }} />34 <div className="absolute top-3 left-3 right-3 md:right-auto md:w-[520px] flex flex-col gap-2">35 <div className="tv-card p-2 flex gap-2 items-center">36 <div className="inline-flex rounded-full border border-line p-0.5 bg-white shrink-0"><button type="button" onClick={() => { setMarket("achat"); setCats([]); }} className={`px-3 py-1.5 rounded-full text-xs font-semibold ${market === "achat" ? "bg-brand text-white" : "text-ink-2"}`}>Acheter</button><button type="button" onClick={() => { setMarket("location"); setCats([]); }} className={`px-3 py-1.5 rounded-full text-xs font-semibold ${market === "location" ? "bg-brand text-white" : "text-ink-2"}`}>Louer</button></div>37 <div className="flex-1 min-w-0"><SearchBar market={market} size="md" onPick={async (city) => { const r = await fetch(`/api/suggest?q=${encodeURIComponent(city)}`).then((r) => r.json()); const pl = r.places?.[0]; const c = await fetch(`/api/search?marche=${market}&ville=${encodeURIComponent(pl?.name ?? city)}&limit=1&format=points`).then((r) => r.json()); const p = c.points?.[0]; if (p) { setCenter([p.lat, p.lng]); setZoom(12); } }} /></div>38 </div>39 <div className="flex gap-2 overflow-x-auto no-scrollbar">{CATS.map(([v, l]) => <button key={v} type="button" className="tv-chip bg-white shrink-0" data-active={cats.includes(v)} onClick={() => setCats((c) => c.includes(v) ? c.filter((x) => x !== v) : [...c, v])}>{l}</button>)}<span className="tv-chip bg-white shrink-0 !text-ink-3">{n >= 2000 ? "2 000+" : n.toLocaleString("fr-CA")} dans la zone{n >= 2000 ? " · zoomez pour tout voir" : ""}</span></div>40 </div>41 {sel && (42 <div className="absolute bottom-3 left-3 right-3 md:left-auto md:w-96">43 <div className="relative"><ListingCardView c={sel} /><button type="button" onClick={() => setSel(null)} className="absolute -top-2 -right-2 p-1.5 rounded-full bg-white shadow border border-line" aria-label="Fermer"><IconClose width={14} height={14} /></button></div>44 <button type="button" onClick={() => router.push(`/annonce/${encodeURIComponent(sel.uid)}`)} className="tv-btn tv-btn-primary w-full mt-2">Voir la fiche</button>45 </div>46 )}47 </div>48 );49}50