// ----------------------------------------------------------------------------- // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Project: Groupe Ka / Ka Maps (Rent-Ka integration) // components/MapView.tsx: Rent-Ka Maps — the product map, powered by // le framework partagé Ka Maps (moteur MapLibre, pastilles de prix GPU, // clustering natif, « Rechercher dans cette zone », aperçu React). // · chargée paresseusement (React.lazy) comme avant ; // · données pilotées par le viewport : /api/listings.geojson?bbox=… // (requêtes annulables, cache, jamais de tempête pendant le pan) ; // · caméra partageable dans l'URL (?lat=…&lng=…&zoom=…). // ----------------------------------------------------------------------------- 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 { louKaMapTheme } from "../kamaps/theme"; import { louKaMapAdapter } from "../kamaps/adapter"; import { MAPBOX_TOKEN } from "../kamaps/config"; const CANADA_CENTER = { lat: 47.5, lng: -84.5 }; const fmtDispo = (iso: string | null | undefined): string => !iso ? "" : iso === "now" ? "Available now" : `Available ${new Date(iso + "T12:00:00").toLocaleDateString("en-CA", { day: "numeric", month: "short", year: "numeric" })}`; /** Compact rental preview — same visual language as ListingCard. */ function PreviewCard({ p }: { p: MapProperty }) { const navigate = useNavigate(); const extra = (p.extra ?? {}) as { title?: string | null; priceLabel?: string | null; availabilityDate?: string | null; source?: string; areaSqft?: number | null; }; const prix = p.price != null ? `$${p.price.toLocaleString("en-CA", { maximumFractionDigits: 0 })}` : extra.priceLabel || "Price on request"; const meta = [ p.propertyType, extra.areaSqft ? `${Math.round(extra.areaSqft)} sq ft` : "", fmtDispo(extra.availabilityDate), ].filter(Boolean).join(" · "); return (
{p.thumbnailUrl ? ( ) : ( )}
{prix} {p.price != null ? " /month" : ""}
{extra.title ?? p.address ?? ""}
{meta}
{sourceName(extra.source ?? "")}
{ e.preventDefault(); navigate(p.originalUrl ?? "/"); }} > See the listing →
); } /** Pont déclaratif : reflète la sélection/le survol venus de la liste. */ 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) { // initial camera: shared URL (?lat&lng&zoom) else Canada const initialCamera = useMemo(() => { const cam = cameraFromParams(new URLSearchParams(window.location.search)); return cam ?? { ...CANADA_CENTER, zoom: 4.4 }; }, []); // caméra → URL (replaceState : pas de re-render du routeur) 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(([, v]) => v)), [filters], ); return (
onSelect?.(p?.id ?? null)} > } />
); }