Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 98.9%
Python 0.6%
1// -----------------------------------------------------------------------------2// Lou-Ka — Agrégateur de logements à louer (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// pages/GestionPublic.tsx : page publique d'une gestion immobilière — /g/{source}5// Bandeau personnalisé par le gestionnaire (accroche, description, contact,6// logo) + inventaire relié automatiquement (12/page, pagination).7// -----------------------------------------------------------------------------8import { useEffect, useState } from "react";9import { Link, useParams } from "react-router-dom";10import { Listing, OrgProfile, fetchListings, fetchOrg } from "../api";11import ListingCard from "../components/ListingCard";12import Pager from "../components/Pager";13import { IcoCompass, IcoPin } from "../components/Icons";1415const PAGE_SIZE = 12;1617export default function GestionPublicPage() {18 const { sourceId = "" } = useParams();19 const [org, setOrg] = useState<OrgProfile | null | undefined>(undefined);20 const [listings, setListings] = useState<Listing[] | null>(null);21 const [total, setTotal] = useState(0);22 const [page, setPage] = useState(1);2324 useEffect(() => {25 fetchOrg(sourceId).then(setOrg).catch(() => setOrg(null));26 setPage(1);27 }, [sourceId]);2829 useEffect(() => {30 setListings(null);31 fetchListings({ source: sourceId }, PAGE_SIZE, (page - 1) * PAGE_SIZE)32 .then((r) => { setListings(r.listings); setTotal(r.total); })33 .catch(() => setListings([]));34 }, [sourceId, page]);3536 if (org === undefined) {37 return <div className="container profil"><div className="notice">Chargement…</div></div>;38 }39 if (org === null) {40 return (41 <div className="container notice">42 <div className="big"><IcoCompass size={40} /></div>43 <h2>Page introuvable</h2>44 <p>Cette gestion n'existe pas sur Lou-Ka.</p>45 <Link className="btn btn-primary" to="/">Explorer les logements</Link>46 </div>47 );48 }4950 return (51 <div className="container profil orgpub">52 <div className="orgpub-head">53 {org.logo_url && (54 <img className="orgpub-logo" src={org.logo_url} alt="" referrerPolicy="no-referrer" />55 )}56 <div className="orgpub-id">57 <span className="kicker">Gestion immobilière</span>58 <h1>{org.name}</h1>59 {org.tagline && <p className="orgpub-tagline">{org.tagline}</p>}60 <div className="orgpub-meta">61 <span className="stat-chip"><b>{org.active_listings}</b> logements actifs</span>62 {org.claimed && <span className="stat-chip orgpub-verified">Page gérée par l'organisation</span>}63 {org.phone && <a className="stat-chip" href={`tel:${org.phone}`}>{org.phone}</a>}64 {org.email && <a className="stat-chip" href={`mailto:${org.email}`}>{org.email}</a>}65 {org.website && (66 <a className="stat-chip" href={org.website} target="_blank" rel="noopener noreferrer">67 Site web ↗68 </a>69 )}70 </div>71 </div>72 </div>7374 {org.description && <p className="orgpub-desc">{org.description}</p>}7576 <div className="results-head">77 <h2><IcoPin size={16} /> Logements offerts</h2>78 <div className="results-tools">79 {listings && <span>{total} résultat{total > 1 ? "s" : ""}</span>}80 </div>81 </div>8283 {listings === null && (84 <div className="grid" aria-busy="true">85 {Array.from({ length: 4 }).map((_, i) => (86 <div className="skel" key={i}>87 <div className="sk-img" /><div className="sk-line" /><div className="sk-line short" />88 </div>89 ))}90 </div>91 )}9293 {listings !== null && (94 <>95 <div className="grid">96 {listings.map((l) => <ListingCard key={l.uid} l={l} />)}97 </div>98 <Pager page={page} pageSize={PAGE_SIZE} total={total} onPage={(p) => {99 setPage(p);100 window.scrollTo({ top: 0, behavior: "smooth" });101 }} />102 </>103 )}104 </div>105 );106}107