SPB Git forge

spb/home-ka

Public
10commits 1branches 0releases
793.0 KBsize
maindefault branch
20 days agolast push
Python 49.6% TypeScript 25.5% CSS 24.1%
2.5 KB · 57 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Home-Ka — US real-estate aggregator (Groupe KA)3// Author: Simon-Pierre Boucher — contact@spboucher.ai4// components/ListingCard.tsx : home card (results grid)5// -----------------------------------------------------------------------------6import { Link } from "react-router-dom";7import { Listing, STATUS_LABELS, fmtArea, fmtBaths, fmtCityLine, fmtPrice, sourceName } from "../api";8import { Ico } from "./Icons";9import PropertyImg from "./PropertyImg";1011export default function ListingCard({ l }: { l: Listing }) {12  // lightweight thumbnail if the connector provides one (mobile/cellular),13  // otherwise the full-size cover photo14  const img = (typeof l.details?.cover_thumb === "string" && l.details.cover_thumb)15    || (l.images && l.images.length > 0 ? l.images[0] : null);16  const meta: { ico: string; txt: string }[] = [];17  if (l.bedrooms != null) meta.push({ ico: "bed", txt: `${l.bedrooms} bd` });18  if (l.bathrooms != null) meta.push({ ico: "bath", txt: `${fmtBaths(l.bathrooms)} ba` });19  const area = fmtArea(l.living_area_sqft);20  if (area) meta.push({ ico: "area", txt: area });21  const statusBadge = l.status && l.status !== "active"22    ? STATUS_LABELS[l.status] ?? l.status : null;2324  return (25    <Link to={`/property/${encodeURIComponent(l.uid)}`} className="card">26      <div className="card-img">27        <PropertyImg src={img} alt={l.street_address || l.title} type={l.property_type} />28        {l.property_type && <span className="badge type">{l.property_type}</span>}29        {statusBadge && <span className="badge status">{statusBadge}</span>}30        {l.images.length > 1 && (31          <span className="badge right"><Ico name="camera" size={12} /> {l.images.length}</span>32        )}33      </div>34      <div className="card-body">35        <div className="card-price">36          {fmtPrice(l.list_price, l.price_label)}37        </div>38        <div className="card-title">{l.street_address || l.title}</div>39        <div className="card-meta">40          <span>{fmtCityLine(l) || "—"}</span>41        </div>42        {meta.length > 0 && (43          <div className="card-specs">44            {meta.map((m) => (45              <span key={m.ico}><Ico name={m.ico} size={13} /> {m.txt}</span>46            ))}47          </div>48        )}49        <div className="card-foot">50          <span className="source-tag">{sourceName(l.source)}</span>51          {l.mls_id && <span className="avail">MLS {l.mls_id}</span>}52        </div>53      </div>54    </Link>55  );56}57