SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
4.2 KB · 119 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Lou-Ka — Location court terme3// components/CtMapView.tsx : carte des hébergements court terme (Ka Maps),4// pilotée par le viewport via /api/ct/listings.geojson — chargée paresseusement.5// -----------------------------------------------------------------------------6import { useCallback, useMemo } from "react";7import { useNavigate } from "react-router-dom";8import "mapbox-gl/dist/mapbox-gl.css";9import "@groupe-ka/ka-maps/styles.css";10import type { MapProperty } from "@groupe-ka/ka-maps";11import { cameraFromParams, cameraToParams } from "@groupe-ka/ka-maps";12import {13  KaBrandBadge,14  KaMapView,15  LoadingIndicator,16  PropertyPreview,17  ResultCount,18  SearchAreaControl,19  Tilt3DControl,20} from "@groupe-ka/ka-maps/react";21import { CtFilters, ctSourceName } from "../ctapi";22import { louKaMapTheme } from "../kamaps/theme";23import { ctMapAdapter } from "../kamaps/ctAdapter";24import { MAPBOX_TOKEN } from "../kamaps/config";2526const QUEBEC_CENTER = { lat: 46.82, lng: -71.25 };2728function PreviewCard({ p }: { p: MapProperty }) {29  const navigate = useNavigate();30  const extra = (p.extra ?? {}) as {31    title?: string | null; priceLabel?: string | null;32    capacity?: number | null; bedrooms?: number | null;33    rating?: number | null; source?: string;34  };35  const prix = p.price != null36    ? `${p.price.toLocaleString("fr-CA", { maximumFractionDigits: 0 })} $`37    : extra.priceLabel || "Prix sur demande";38  const meta = [39    p.propertyType,40    extra.capacity ? `${extra.capacity} pers.` : "",41    extra.bedrooms ? `${extra.bedrooms} ch.` : "",42    extra.rating != null ? `★ ${extra.rating.toLocaleString("fr-CA", { maximumFractionDigits: 1 })}` : "",43  ].filter(Boolean).join(" · ");44  return (45    <div className="mv-pop">46      {p.thumbnailUrl ? (47        <img src={p.thumbnailUrl} alt="" loading="lazy" />48      ) : (49        <div className="mv-noimg" aria-hidden="true">⌂</div>50      )}51      <div className="mv-pop-body">52        <div className="mv-pop-price">53          {prix}54          <small>{p.price != null ? " /nuit" : ""}</small>55        </div>56        <div className="mv-pop-title">{extra.title ?? p.address ?? ""}</div>57        <div className="mv-pop-meta">{meta}</div>58        <div className="mv-pop-src">{ctSourceName(extra.source ?? "")}</div>59        <a60          className="mv-pop-cta"61          href={p.originalUrl}62          onClick={(e) => {63            e.preventDefault();64            navigate(p.originalUrl ?? "/court-terme");65          }}66        >67          Voir la fiche →68        </a>69      </div>70    </div>71  );72}7374export default function CtMapView({ filters }: { filters: CtFilters }) {75  const initialCamera = useMemo(() => {76    const cam = cameraFromParams(new URLSearchParams(window.location.search));77    return cam ?? { ...QUEBEC_CENTER, zoom: 6.2 };78  }, []);7980  const onMoveEnd = useCallback((center: { lat: number; lng: number }, zoom: number) => {81    const url = new URL(window.location.href);82    url.search = cameraToParams({ ...center, zoom }, url.searchParams).toString();83    window.history.replaceState(null, "", url);84  }, []);8586  const mapFilters = useMemo(87    () => Object.fromEntries(Object.entries(filters).filter(([, v]) => v)),88    [filters],89  );9091  return (92    <div className="mapview" role="application" aria-label="Carte des hébergements court terme">93      <KaMapView94        theme={louKaMapTheme}95        adapter={ctMapAdapter}96        mapboxToken={MAPBOX_TOKEN}97        filters={mapFilters}98        center={initialCamera}99        zoom={initialCamera.zoom}100        pitch={45}101        basemap={{ theme: "default", showLandmarks: true }}102        cluster={{ maxZoom: 15, valueClamp: [50, 2000] }}103        searchMode="manual"104        onMoveEnd={onMoveEnd}105      >106        <KaBrandBadge />107        <Tilt3DControl />108        <SearchAreaControl />109        <LoadingIndicator label="Mise à jour des hébergements…" />110        <ResultCount111          emptyTitle="Aucun hébergement trouvé dans cette zone."112          emptyHint="Élargissez la carte ou modifiez vos filtres."113        />114        <PropertyPreview render={(p) => <PreviewCard p={p} />} />115      </KaMapView>116    </div>117  );118}119