SPB Git

spb/ora-ka Public

Ora-Ka — cinq agrégateurs Ka, une barre de recherche hybride (exact + sémantique)

Python 80% TypeScript 12.9% CSS 6.8%
3.7 KB · 92 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Immo-Ka — Agrégateur de propriétés à vendre (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// pages/Sources.tsx : registre des agences — éclaté par bannière → sous-agence5// -----------------------------------------------------------------------------6import { Ico } from "../components/Icons";7import { useEffect, useState } from "react";8import { Link } from "react-router-dom";9import { Franchise, fetchAgencies } from "../api";1011export default function SourcesPage() {12  const [data, setData] = useState<Franchise[] | null>(null);13  const [error, setError] = useState<string | null>(null);14  const [open, setOpen] = useState<Record<string, boolean>>({});1516  useEffect(() => {17    fetchAgencies().then((r) => setData(r.franchises)).catch((e) => setError(String(e)));18  }, []);1920  const totalProps = (data ?? []).reduce((s, f) => s + f.total, 0);21  const totalSub = (data ?? []).reduce((s, f) => s + f.sub_agencies, 0);2223  return (24    <div className="container sources">25      <span className="kicker">Registre — agences & sous-agences</span>26      <h1>Sources par sous-agence</h1>27      <p className="sub">28        Chaque bannière immobilière du Québec est éclatée par sous-agence (bureau).29        Chaque annonce est synchronisée par un connecteur dédié et dédoublonnée par30        numéro Centris. Cliquez un compte pour voir les propriétés.31      </p>3233      {error && <div className="notice"> {error}</div>}34      {!data && !error && <div className="notice">Chargement…</div>}3536      {data && (37        <>38          <p className="stats-foot" style={{ marginTop: 0 }}>39            <b>{totalProps.toLocaleString("fr-CA")}</b> propriétés ·{" "}40            <b>{data.length}</b> bannières ·{" "}41            <b>{totalSub.toLocaleString("fr-CA")}</b> sous-agences42          </p>4344          <div className="franchise-list">45            {data.map((f) => {46              const isOpen = open[f.franchise] ?? false;47              return (48                <div className="franchise" key={f.franchise}>49                  <button50                    className="franchise-head"51                    onClick={() => setOpen({ ...open, [f.franchise]: !isOpen })}52                    aria-expanded={isOpen}53                  >54                    <span className="fh-caret">{isOpen ? "▾" : "▸"}</span>55                    <span className="fh-name">{f.franchise}</span>56                    <span className="fh-sub">57                      {f.sub_agencies} sous-agence{f.sub_agencies > 1 ? "s" : ""}58                    </span>59                    <span className="fh-count">{f.total.toLocaleString("fr-CA")}</span>60                  </button>6162                  {isOpen && (63                    <ul className="subagency-list">64                      {f.agencies.map((a) => (65                        <li key={a.name}>66                          <span className="sa-name">{a.name}</span>67                          <Link68                            className="count-pill"69                            to={`/?source=${encodeURIComponent(a.sources[0])}`}70                          >71                            {a.count.toLocaleString("fr-CA")}72                          </Link>73                        </li>74                      ))}75                    </ul>76                  )}77                </div>78              );79            })}80          </div>8182          <p className="stats-foot">83            Les sous-agences des grandes bannières (RE/MAX, Via Capitale, Century 21…)84            servent aussi de source de secours, dédoublonnées par n° Centris — aucun85            double-comptage.86          </p>87        </>88      )}89    </div>90  );91}92