TypeScript 90.1%
JavaScript 3.2%
Python 3.2%
CSS 1.8%
HTML 1.8%
1"use client";2// UQO Éval — laboratoire pédagogique d'évaluation immobilière3// components/KaCarte.tsx : UQO Éval Maps — recherche par carte propulsée4// par le framework partagé Ka Maps (moteur Mapbox GL, style Standard 3D par5// défaut + bouton 2D). Remplace l'ancienne carte Leaflet/OSM : pastilles de6// VALEURS ESTIMÉES 2026 (grappes + moyennes), chargement piloté par le7// viewport (annulation, cache), aperçu React, bilingue FR/EN.8// À charger via next/dynamic({ ssr: false }) — Mapbox GL est navigateur-seul.910import { useState } from "react";11import { useRouter } from "next/navigation";12import "mapbox-gl/dist/mapbox-gl.css";13import "@groupe-ka/ka-maps/styles.css";14import type { MapProperty } from "@groupe-ka/ka-maps";15import { formatFullPrice } from "@groupe-ka/ka-maps";16import {17 KaBrandBadge,18 KaMapView,19 LoadingIndicator,20 LocateControl,21 PropertyPreview,22 Tilt3DControl,23} from "@groupe-ka/ka-maps/react";24import { useLang } from "@/components/LangContext";25import {26 MAPBOX_TOKEN,27 MIN_ZOOM_FETCH,28 uqoEvalMapAdapter,29 uqoEvalMapTheme,30} from "@/lib/kamaps";3132const DEFAULT_CENTER = { lat: 45.5421, lng: -73.6646 }; // Montréal3334/** Aperçu d'une unité d'évaluation — valeur estimée, jamais un prix. */35function PreviewCard({ p, fr, close }: { p: MapProperty; fr: boolean; close: () => void }) {36 const router = useRouter();37 return (38 <div className="vpm-pop">39 <div className="klabel">{fr ? "Estimation 2026" : "2026 estimate"}</div>40 <div className="vpm-pop-value">41 {p.estimatedValue != null ? formatFullPrice(p.estimatedValue) : "—"}42 </div>43 <div className="vpm-pop-addr">{p.address}</div>44 <div className="vpm-pop-meta">45 {[p.city, p.propertyType].filter(Boolean).join(" · ")}46 </div>47 <button48 type="button"49 className="vpm-pop-cta"50 onClick={() => {51 close();52 router.push(p.originalUrl ?? "/");53 }}54 >55 {fr ? "Voir l'estimation complète →" : "See full estimate →"}56 </button>57 </div>58 );59}6061export default function KaCarte() {62 const { lang } = useLang();63 const fr = lang === "fr";64 const [zoom, setZoom] = useState(13);65 const [count, setCount] = useState<number | null>(null);6667 const zoomedOut = zoom < MIN_ZOOM_FETCH;6869 return (70 <div className="vpm-wrap">71 <KaMapView72 theme={uqoEvalMapTheme}73 adapter={uqoEvalMapAdapter}74 mapboxToken={MAPBOX_TOKEN}75 center={DEFAULT_CENTER}76 zoom={13.5}77 pitch={50}78 searchMode="auto"79 cluster={{ maxZoom: 15, radius: 44, valueClamp: [40_000, 5_000_000] }}80 onMoveEnd={(_c, z) => setZoom(z)}81 onData={(c) => setCount(c)}82 >83 <KaBrandBadge />84 <Tilt3DControl />85 <LocateControl label={fr ? "Me localiser" : "Locate me"} />86 <LoadingIndicator label={fr ? "Mise à jour des estimations…" : "Updating estimates…"} />87 <PropertyPreview88 render={(p, close) => <PreviewCard p={p} fr={fr} close={close} />}89 />90 <div className="vpm-chip" role="status">91 {zoomedOut92 ? fr93 ? "Zoomez pour voir les valeurs estimées"94 : "Zoom in to see estimated values"95 : count != null96 ? fr97 ? `${count.toLocaleString("fr-CA")} propriétés affichées`98 : `${count.toLocaleString("en-CA")} properties shown`99 : fr100 ? "Chargement…"101 : "Loading…"}102 </div>103 </KaMapView>104 </div>105 );106}107