// -----------------------------------------------------------------------------
// Author: Simon-Pierre Boucher
// Contact: contact@spboucher.ai
// Project: Toit-Ka
// pages/Category.tsx : pages programmatiques /louer/{ville}[/{type}],
// /acheter/{ville}[/{type}] et /{tx}/type/{type} — mêmes URL que le rendu
// SEO serveur (toitka/seo.py), le SPA prend le relais au montage.
// -----------------------------------------------------------------------------
import { useEffect, useState } from "react";
import { Link, useParams, useSearchParams } from "react-router-dom";
import { Listing, Tx, fetchListings, fmtPrice, resolveSeo, setDocTitle } from "../api";
import ListingCard from "../components/ListingCard";
import { Ico } from "../components/Icons";
const PAGE_SIZE = 48; // aligné sur toitka/seo.py
export default function CategoryPage({ tx }: { tx: Tx }) {
const { ville, type } = useParams<{ ville?: string; type?: string }>();
const [params, setParams] = useSearchParams();
const page = Math.max(1, Number(params.get("page")) || 1);
const louer = tx === "louer";
const [labels, setLabels] = useState<{ city?: string; type?: string } | null>(null);
const [listings, setListings] = useState
(null);
const [total, setTotal] = useState(0);
const [error, setError] = useState(null);
useEffect(() => {
setLabels(null); setListings(null); setError(null);
resolveSeo(tx, ville, type)
.then(setLabels)
.catch(() => setError("Page introuvable."));
}, [tx, ville, type]);
useEffect(() => {
if (!labels) return;
setListings(null);
fetchListings(
{ tx, city: labels.city, type: labels.type, sort: "price_asc" },
PAGE_SIZE, (page - 1) * PAGE_SIZE)
.then((r) => { setListings(r.listings); setTotal(r.total); })
.catch((e) => setError(String(e)));
window.scrollTo(0, 0);
}, [tx, labels, page]);
const verbe = louer ? "à louer" : "à vendre";
const noun = louer ? "Logements" : "Propriétés";
const h1 = labels
? labels.city && labels.type
? `${labels.type} ${verbe} à ${labels.city}`
: labels.city
? `${noun} ${verbe} à ${labels.city}`
: `${labels.type} ${verbe} au Québec`
: "";
useEffect(() => { if (h1) setDocTitle(h1); }, [h1]);
if (error)
return (
Page introuvable
{error}
Toutes les annonces
);
const pages = Math.max(1, Math.ceil(total / PAGE_SIZE));
const refine = new URLSearchParams({ tx });
if (labels?.city) refine.set("city", labels.city);
if (labels?.type) refine.set("type", labels.type);
return (
{h1 || "Chargement…"}
{listings && (
{total.toLocaleString("fr-CA")} annonces
{listings.length > 0 && listings[0].price != null && (
<> · à partir de {fmtPrice(listings[0].price, tx)}>
)}
{" · "}
Affiner la recherche (carte, filtres)
)}
{!listings && !error && (
{Array.from({ length: 8 }).map((_, i) => (
))}
)}
{listings && (
{listings.map((l) => )}
)}
{pages > 1 && (
)}
);
}