Python 49.6%
TypeScript 25.5%
CSS 24.1%
1// -----------------------------------------------------------------------------2// Home-Ka — US real-estate aggregator (Groupe KA)3// Author: Simon-Pierre Boucher — contact@spboucher.ai4// pages/Sources.tsx : public registry of data sources (/api/sources)5// -----------------------------------------------------------------------------6import { useEffect, useState } from "react";7import { Link } from "react-router-dom";8import { Source, fetchSources, registerSourceNames } from "../api";910function ago(ts: number | null): string {11 if (ts == null) return "never";12 const s = Math.max(0, Math.floor(Date.now() / 1000 - (ts > 1e12 ? ts / 1000 : ts)));13 if (s < 3600) return `${Math.round(s / 60)} min ago`;14 if (s < 172800) return `${Math.round(s / 3600)} h ago`;15 return `${Math.round(s / 86400)} d ago`;16}1718export default function SourcesPage() {19 const [sources, setSources] = useState<Source[] | null>(null);20 const [error, setError] = useState<string | null>(null);2122 useEffect(() => {23 fetchSources()24 .then((r) => { registerSourceNames(r.sources); setSources(r.sources); })25 .catch((e) => setError(String(e)));26 }, []);2728 const totalListings = (sources ?? []).reduce((s, x) => s + x.active_listings, 0);29 const enabled = (sources ?? []).filter((s) => s.enabled).length;3031 return (32 <div className="container sources">33 <span className="kicker">Registry — data sources</span>34 <h1>Data sources</h1>35 <p className="sub">36 Every listing on Home-Ka comes from a registered source — brokerage37 feeds, MLS/RESO APIs and listing platforms. Each source is synced by a38 dedicated connector and de-duplicated by MLS number and property match.39 Click a count to browse its homes.40 </p>4142 {error && <div className="notice">{error}</div>}43 {!sources && !error && <div className="notice">Loading…</div>}4445 {sources && (46 <>47 <p className="stats-foot" style={{ marginTop: 0 }}>48 <b>{totalListings.toLocaleString("en-US")}</b> active listings ·{" "}49 <b>{sources.length}</b> sources ·{" "}50 <b>{enabled}</b> enabled51 </p>5253 <div className="q-table-wrap">54 <table className="q-table">55 <thead>56 <tr>57 <th>Source</th><th>Connector</th><th>States</th>58 <th>Active listings</th><th>Last sync</th>59 </tr>60 </thead>61 <tbody>62 {sources.map((s) => (63 <tr key={s.id}>64 <td>65 {s.name || s.id}66 {!s.enabled && <span className="adm-badge adm-off"> disabled</span>}67 </td>68 <td>{s.connector_type ?? "—"}</td>69 <td>{s.states?.length ? s.states.join(", ") : "—"}</td>70 <td className="num">71 {s.active_listings > 072 ? <Link className="count-pill" to={`/?source=${encodeURIComponent(s.id)}`}>73 {s.active_listings.toLocaleString("en-US")}74 </Link>75 : "0"}76 </td>77 <td>{ago(s.last_sync)}</td>78 </tr>79 ))}80 </tbody>81 </table>82 </div>8384 <p className="stats-foot">85 Listings published by more than one source are de-duplicated by MLS86 number and by matching the underlying property — no double counting.87 </p>88 </>89 )}90 </div>91 );92}93