TypeScript 90.1%
JavaScript 3.2%
Python 3.2%
CSS 1.8%
HTML 1.8%
1// UQO Éval — laboratoire pédagogique d'évaluation immobilière2// lib/kamaps.ts : thème UQO Éval Maps (framework Ka Maps) + adaptateur /api/nearby.3// La carte affiche des VALEURS ESTIMÉES (jamais des prix demandés) :4// pastilles encre à liseré turquoise UQO, sélection bleu UQO — le langage5// visuel du site (encre #16181b, bleu #006085, turquoise #2bccae).67import type {8 BoundsQuery,9 BoundsQueryResult,10 KaAppSource,11 KaDataAdapter,12 KaMapTheme,13 MapProperty,14} from "@groupe-ka/ka-maps";1516// Le framework ka-maps fige l'union KaAppSource à ses apps d'origine ;17// « uqo-eval » n'y figure pas — l'id ne sert qu'au thème/URL, déjà fournis ici.18const APP_SOURCE = "uqo-eval" as KaAppSource;1920const INK = "#16181b";21const RED = "#2bccae"; // turquoise UQO (liseré/halo)22const BORDEAUX = "#00465f"; // bleu UQO profond (grappes)2324export const MAPBOX_TOKEN: string =25 process.env.NEXT_PUBLIC_MAPBOX_TOKEN ??26 "pk.eyJ1Ijoic3Bib3VjaGVyIiwiYSI6ImNtc3Fyb3k4djAwOTgyenB3dWt6NHBjc2kifQ.poqLf0ADy3lIh28O-pFI2Q";2728/** Zoom minimal de chargement : sous ce niveau, on invite à zoomer. */29export const MIN_ZOOM_FETCH = 12;3031export const TYPE_FR: Record<string, string> = {32 unifamilial: "Unifamiliale",33 condo_ou_multi: "Condo / multi",34 plex: "Plex",35 chalet: "Chalet",36 terrain: "Terrain",37 maison_mobile: "Maison mobile",38 autre: "Autre",39};4041export const uqoEvalMapTheme: KaMapTheme = {42 id: "uqo-eval",43 productName: "UQO Éval Maps",44 accent: "#006085", // bleu UQO45 onAccent: "#ffffff",46 fontFamily: "var(--font-inter), system-ui, sans-serif",47 markers: {48 sale: {49 background: INK,50 text: "#ffffff",51 halo: RED,52 selectedBackground: "#006085",53 selectedText: "#ffffff",54 },55 // Valeurs estimées : pastille encre, liseré turquoise — visuellement56 // distinctes d'un prix demandé (jamais présentées comme une annonce).57 valuation: {58 background: INK,59 text: "#ffffff",60 halo: RED,61 selectedBackground: "#006085",62 selectedText: "#ffffff",63 },64 },65 cluster: {66 background: BORDEAUX,67 text: "#ffffff",68 border: "rgba(43,204,174,0.5)",69 },70 supportsDark: false,71};7273interface NearbyUnit {74 id: string;75 adresse: string | null;76 apt: string | null;77 municipalite: string | null;78 typeProp: string | null;79 lat: number;80 lng: number;81 est2026: number | null;82}8384function toMapProperty(u: NearbyUnit): MapProperty {85 return {86 id: u.id,87 appSource: APP_SOURCE,88 latitude: u.lat,89 longitude: u.lng,90 kind: "valuation",91 estimatedValue: u.est2026 ?? undefined,92 propertyType: u.typeProp ? TYPE_FR[u.typeProp] ?? u.typeProp : undefined,93 address: u.apt ? `${u.adresse} app. ${u.apt}` : u.adresse ?? undefined,94 city: u.municipalite ?? undefined,95 originalUrl: `/estimation/${u.id}`,96 extra: { typeProp: u.typeProp },97 };98}99100/** Adaptateur viewport→données : /api/nearby (contrat partagé avec iOS). */101export const uqoEvalMapAdapter: KaDataAdapter = {102 id: "uqo-eval-nearby",103 appSource: APP_SOURCE,104 async fetchInBounds(query: BoundsQuery): Promise<BoundsQueryResult> {105 if (query.zoom < MIN_ZOOM_FETCH) return { properties: [] };106 const { bbox } = query;107 const lat = (bbox.north + bbox.south) / 2;108 const lng = (bbox.east + bbox.west) / 2;109 const halfLat = Math.abs(bbox.north - lat);110 const halfLng = Math.abs(bbox.east - lng);111 const params = new URLSearchParams({112 lat: lat.toFixed(6),113 lng: lng.toFixed(6),114 halfLat: halfLat.toFixed(6),115 halfLng: halfLng.toFixed(6),116 limit: "1500",117 });118 const res = await fetch(`/api/nearby?${params}`, { signal: query.signal });119 if (!res.ok) throw new Error(`Carte : API ${res.status}`);120 const data = (await res.json()) as { results: NearbyUnit[] };121 return { properties: data.results.map(toMapProperty) };122 },123};124