SPB Git forge

spb/house-ka

Public
18commits 1branches 0releases
1.9 MBsize
maindefault branch
19 days agolast push
Python 67% TypeScript 18.2% CSS 14.4%
3.7 KB · 92 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// House-Ka — Homes-for-sale aggregator (Canada outside Québec, Ontario first)3// Author: Simon-Pierre Boucher — contact@spboucher.ai4// pages/Agencies.tsx : brokerage registry — banner → brokerage (office)5// -----------------------------------------------------------------------------6import { useEffect, useState } from "react";7import { Link } from "react-router-dom";8import { Franchise, fetchAgencies } from "../api";910export default function AgenciesPage() {11  const [data, setData] = useState<Franchise[] | null>(null);12  const [error, setError] = useState<string | null>(null);13  const [open, setOpen] = useState<Record<string, boolean>>({});1415  useEffect(() => {16    document.title = "Covered brokerages | House-Ka";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">Registry — brokerages & offices</span>26      <h1>Sources by brokerage</h1>27      <p className="sub">28        Every covered brokerage or team publishes its board's full inventory29        through the CREA DDF feed. Each listing is synced by a dedicated30        connector and deduplicated by DDF number — the same property published31        on several sites is only counted once. Click a source to see its homes.32      </p>3334      {error && <div className="notice"> {error}</div>}35      {!data && !error && <div className="notice">Loading…</div>}3637      {data && (38        <>39          <p className="stats-foot" style={{ marginTop: 0 }}>40            <b>{totalProps.toLocaleString("en-CA")}</b> homes ·{" "}41            <b>{data.length}</b> banners ·{" "}42            <b>{totalSub.toLocaleString("en-CA")}</b> offices43          </p>4445          <div className="franchise-list">46            {data.map((f) => {47              const isOpen = open[f.franchise] ?? false;48              return (49                <div className="franchise" key={f.franchise}>50                  <button51                    className="franchise-head"52                    onClick={() => setOpen({ ...open, [f.franchise]: !isOpen })}53                    aria-expanded={isOpen}54                  >55                    <span className="fh-caret">{isOpen ? "▾" : "▸"}</span>56                    <span className="fh-name">{f.franchise}</span>57                    <span className="fh-sub">58                      {f.sub_agencies} office{f.sub_agencies > 1 ? "s" : ""}59                    </span>60                    <span className="fh-count">{f.total.toLocaleString("en-CA")}</span>61                  </button>6263                  {isOpen && (64                    <ul className="subagency-list">65                      {f.agencies.map((a) => (66                        <li key={a.name}>67                          <span className="sa-name">{a.name}</span>68                          <Link69                            className="count-pill"70                            to={`/?source=${encodeURIComponent(a.sources[0])}`}71                          >72                            {a.count.toLocaleString("en-CA")}73                          </Link>74                        </li>75                      ))}76                    </ul>77                  )}78                </div>79              );80            })}81          </div>8283          <p className="stats-foot">84            Sites sharing the same CREA DDF pool also serve as backup sources,85            deduplicated by DDF number — no double counting.86          </p>87        </>88      )}89    </div>90  );91}92