// ----------------------------------------------------------------------------- // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Project: Toit-Ka // components/MapView.tsx : Toit-Ka Maps — carte du produit propulsée par le // framework partagé Ka Maps (Mapbox Standard 3D, pastilles de prix par // univers, « Rechercher dans cette zone », aperçu React). // ----------------------------------------------------------------------------- import { useCallback, useEffect, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import "mapbox-gl/dist/mapbox-gl.css"; import "@groupe-ka/ka-maps/styles.css"; import type { MapProperty } from "@groupe-ka/ka-maps"; import { cameraFromParams, cameraToParams } from "@groupe-ka/ka-maps"; import { KaBrandBadge, KaMapView, LoadingIndicator, PropertyPreview, ResultCount, SearchAreaControl, Tilt3DControl, useKaMap, } from "@groupe-ka/ka-maps/react"; import { ListingFilters, sourceName } from "../api"; import { Ico } from "./Icons"; import { toitKaMapTheme } from "../kamaps/theme"; import { toitKaMapAdapter } from "../kamaps/adapter"; import { MAPBOX_TOKEN } from "../kamaps/config"; const QUEBEC_CENTER = { lat: 46.4, lng: -72.5 }; /** Aperçu compact d'une annonce — même langage visuel que ListingCard. */ function PreviewCard({ p }: { p: MapProperty }) { const navigate = useNavigate(); const extra = (p.extra ?? {}) as { tx?: string; title?: string | null; priceLabel?: string | null; source?: string; }; const louer = extra.tx === "louer"; const prix = p.price != null ? `${p.price.toLocaleString("fr-CA", { maximumFractionDigits: 0 })} $${louer ? " /mois" : ""}` : extra.priceLabel || "Prix sur demande"; const meta = [ louer ? "À louer" : "À vendre", p.propertyType, p.bedrooms != null ? `${p.bedrooms} ch.` : "", ].filter(Boolean).join(" · "); return (
{p.thumbnailUrl ? ( ) : ( )}
{prix}
{p.address ?? extra.title ?? ""}
{meta}
{sourceName(extra.source ?? "")}
{ e.preventDefault(); navigate(p.originalUrl ?? "/"); }} > Voir la fiche →
); } /** Pont déclaratif : sélection/survol venus de la liste de résultats. */ function SelectionBridge({ selectedUid, hoveredUid }: { selectedUid: string | null; hoveredUid: string | null; }) { const map = useKaMap(); useEffect(() => { map?.select(selectedUid, "app"); }, [map, selectedUid]); useEffect(() => { map?.setHovered(hoveredUid, "app"); }, [map, hoveredUid]); return null; } export interface MapViewProps { filters: ListingFilters; selectedUid?: string | null; hoveredUid?: string | null; onSelect?: (uid: string | null) => void; } export default function MapView({ filters, selectedUid = null, hoveredUid = null, onSelect, }: MapViewProps) { const initialCamera = useMemo(() => { const cam = cameraFromParams(new URLSearchParams(window.location.search)); return cam ?? { ...QUEBEC_CENTER, zoom: 6.2 }; }, []); const onMoveEnd = useCallback((center: { lat: number; lng: number }, zoom: number) => { const url = new URL(window.location.href); url.search = cameraToParams({ ...center, zoom }, url.searchParams).toString(); window.history.replaceState(null, "", url); }, []); const mapFilters = useMemo( () => Object.fromEntries( Object.entries(filters).filter(([k, v]) => v && k !== "sort"), ), [filters], ); return (
onSelect?.(p?.id ?? null)} > } />
); }