// ----------------------------------------------------------------------------- // Lou-Ka — Agrégateur de logements à louer (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // pages/Ville.tsx : pages programmatiques SEO — /villes, /ville/:ville[/:type] // Le serveur rend le même contenu en HTML (louka/seo.py) ; ici, la version // interactive avec les cartes d'annonces de l'app. // ----------------------------------------------------------------------------- import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { Listing } from "../api"; import ListingCard from "../components/ListingCard"; interface VilleRow { city: string; slug: string; n: number; avg_price: number | null; } interface VilleData { city: string; slug: string; unit_type: string | null; n: number; avg: number | null; med: number | null; types: { unit_type: string; slug: string; n: number }[]; listings: Listing[]; neighbors: VilleRow[]; } const fmt = (p: number | null) => p == null ? "—" : `${Math.round(p).toLocaleString("fr-CA")} $`; async function get(path: string): Promise { const res = await fetch(path); if (!res.ok) throw new Error(`${res.status}`); return res.json(); } export function VillesPage() { const [villes, setVilles] = useState(null); const [error, setError] = useState(null); useEffect(() => { get<{ villes: VilleRow[] }>("/api/seo/villes") .then((r) => setVilles(r.villes)) .catch((e) => setError(String(e))); }, []); return (
Répertoire — logements par ville

Logements à louer par ville

Toutes les villes du Québec où Lou-Ka recense des logements à louer, avec le nombre d'annonces actives et le loyer moyen.

{error &&
⚠️ {error}
} {!villes && !error &&
Chargement…
} {villes && (
    {villes.map((v) => (
  • {v.city} {" — "} {v.n} annonce{v.n > 1 ? "s" : ""} {v.avg_price != null && <> · loyer moyen {fmt(v.avg_price)}}
  • ))}
)}
); } export default function VillePage() { const { ville = "", type } = useParams(); const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { setData(null); setError(null); const qs = type ? `?type=${encodeURIComponent(type)}` : ""; get(`/api/seo/ville/${encodeURIComponent(ville)}${qs}`) .then(setData) .catch((e) => setError(e.message === "404" ? "Ville ou type inconnu." : String(e))); window.scrollTo(0, 0); }, [ville, type]); if (error) return (

Page introuvable

⚠️ {error} Voir toutes les villes
); if (!data) return (
Chargement…
); const what = data.unit_type ? `${data.unit_type} à louer` : "Logements à louer"; return (
Villes — {data.city}

{what} à {data.city}

{data.n} annonce{data.n > 1 ? "s" : ""} active{data.n > 1 ? "s" : ""} {data.avg != null && <> · loyer moyen {fmt(data.avg)}} {data.med != null && <> · médian {fmt(data.med)}}

{!data.unit_type && data.types.length > 1 && (

{data.types.map((t) => ( {t.unit_type} ({t.n}) ))}

)} {data.unit_type && (

← Tous les logements à {data.city}

)}
{data.listings.map((l) => ( ))}
{data.neighbors.length > 0 && ( <>

Autres villes

{data.neighbors.map((v) => ( {v.city} ({v.n}) ))}

)}
); }