SPB Git

spb/toit-ka Public

Toit-Ka — louer ou acheter un toit au Québec, un seul endroit (fusion Lou-Ka × Immo-Ka) — www.toit-ka.com

Python 40.2% TypeScript 39% CSS 20.2% HTML 0.7%
3.6 KB · 106 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Author: Simon-Pierre Boucher3// Contact: contact@spboucher.ai4// Project: Toit-Ka5// components/PropertyMap.tsx : mini-carte d'une annonce (fiche détail).6// Fond Ka Maps (Mapbox Standard 3D), anneaux de marchabilité 5/15 min et7// marqueur-prix pulsant — couleur d'anneaux selon l'univers.8// -----------------------------------------------------------------------------9import { useEffect, useRef } from "react";10import mapboxgl from "mapbox-gl";11import "mapbox-gl/dist/mapbox-gl.css";12import { applyKaBasemapConfig, KA_STYLE_URL } from "@groupe-ka/ka-maps";13import { formatCompactPrice } from "@groupe-ka/ka-maps";14import { MAPBOX_TOKEN } from "../kamaps/config";1516/** Cercle géodésique approché (72 côtés) pour les anneaux de marche. */17function circle(lng: number, lat: number, radiusM: number): GeoJSON.Feature {18  const pts: [number, number][] = [];19  for (let i = 0; i <= 72; i++) {20    const a = (i / 72) * 2 * Math.PI;21    const dLat = (radiusM * Math.cos(a)) / 111_000;22    const dLng = (radiusM * Math.sin(a)) / (111_000 * Math.cos((lat * Math.PI) / 180));23    pts.push([lng + dLng, lat + dLat]);24  }25  return { type: "Feature", geometry: { type: "Polygon", coordinates: [pts] }, properties: {} };26}2728const RING_COLORS: Record<string, [string, string]> = {29  louer: ["#1c5c41", "#123f2e"],30  acheter: ["#e23744", "#b3202b"],31};3233export default function PropertyMap({ lat, lng, price, tx }: {34  lat: number;35  lng: number;36  price?: number | null;37  tx: "louer" | "acheter";38}) {39  const div = useRef<HTMLDivElement>(null);4041  useEffect(() => {42    if (!div.current) return;43    const [fill, line] = RING_COLORS[tx] ?? RING_COLORS.acheter;44    const map = new mapboxgl.Map({45      container: div.current,46      accessToken: MAPBOX_TOKEN,47      style: KA_STYLE_URL,48      center: [lng, lat],49      zoom: 15.2,50      pitch: 45,51      scrollZoom: false,52      dragRotate: false,53      attributionControl: false,54    });55    map.addControl(new mapboxgl.AttributionControl({ compact: true }), "bottom-right");56    map.addControl(new mapboxgl.NavigationControl({ showCompass: false }), "top-right");57    map.touchZoomRotate.disableRotation();5859    map.once("load", () => {60      applyKaBasemapConfig(map, "light");61      map.addSource("rings", {62        type: "geojson",63        data: {64          type: "FeatureCollection",65          features: [circle(lng, lat, 400), circle(lng, lat, 1200)],66        },67      });68      map.addLayer({69        id: "rings-fill", type: "fill", source: "rings", slot: "top",70        paint: { "fill-color": fill, "fill-opacity": 0.035 },71      });72      map.addLayer({73        id: "rings-line", type: "line", source: "rings", slot: "top",74        paint: {75          "line-color": line, "line-opacity": 0.55,76          "line-width": 1.4, "line-dasharray": [3, 3],77        },78      });79    });8081    const el = document.createElement("div");82    el.className = "pm-marker";83    el.innerHTML =84      `<div class="pm-pill">${price != null ? formatCompactPrice(price) : "•"}</div>` +85      `<div class="pm-pulse"></div><div class="pm-dot"></div>`;86    const marker = new mapboxgl.Marker({ element: el, anchor: "bottom" })87      .setLngLat([lng, lat])88      .addTo(map);8990    return () => {91      marker.remove();92      map.remove();93    };94  }, [lat, lng, price, tx]);9596  return (97    <div className="pm-wrap">98      <div ref={div} style={{ position: "absolute", inset: 0 }} role="img" aria-label="Carte de l'annonce" />99      <div className="pm-rings-legend" aria-hidden="true">100        <span>◌ 5 min à pied</span>101        <span>◌ 15 min à pied</span>102      </div>103    </div>104  );105}106