// ----------------------------------------------------------------------------- // Immo-Ka — Agrégateur de propriétés à vendre (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // fiche/PropertyQuickFacts.tsx : « La propriété » — grille compacte icône + // valeur + libellé (type, chambres, salles de bain, salles d'eau, superficie, // terrain, année, stationnement, MLS), rangées pratiques (agence, courtier, // en ligne depuis, synchronisé), puis TOUTES les caractéristiques publiées // par la source et le tableau des pièces en accordéons (aucune donnée // supprimée par rapport à la fiche v2). // ----------------------------------------------------------------------------- import { ReactNode } from "react"; import { Listing, Room, fmtArea, fmtPrice, sourceName } from "../api"; import { Ico } from "../components/Icons"; import { Accordion, SectionCard, NBSP, relTime } from "./ui"; // clés techniques de `details` jamais montrées dans « Caractéristiques » const DETAIL_HIDDEN = new Set([ "pieces", "price_from", "cover_thumb", "photo_captions", "img_audited", "needs_image_review", "postal_code", "region", "transaction", "prix_pi2", "prix_m2", "listing_origin_url", "listed_at", ]); // clés déjà représentées dans la grille (évite le doublon dans le tableau) const IN_GRID = new Set(["Type de propriété", "Année de construction", "Superficie habitable", "Superficie du terrain", "Stationnement (total)"]); export default function PropertyQuickFacts({ l }: { l: Listing }) { const d = l.details ?? {}; const facts: { ico: ReactNode; v: string; l: string }[] = []; if (l.property_type) facts.push({ ico: , v: l.property_type, l: "Type" }); if (l.bedrooms != null) facts.push({ ico: , v: `${Math.round(l.bedrooms)}`, l: `Chambre${l.bedrooms > 1 ? "s" : ""}` }); if (l.bathrooms != null) facts.push({ ico: , v: `${l.bathrooms}`, l: `Salle${l.bathrooms > 1 ? "s" : ""} de bain` }); if (l.powder_rooms != null && l.powder_rooms > 0) facts.push({ ico: , v: `${l.powder_rooms}`, l: `Salle${l.powder_rooms > 1 ? "s" : ""} d'eau` }); if (l.area_sqft != null) facts.push({ ico: , v: fmtArea(l.area_sqft)!, l: "Superficie habitable" }); if (l.lot_sqft != null) facts.push({ ico: , v: fmtArea(l.lot_sqft)!, l: "Terrain" }); if (l.year_built != null) facts.push({ ico: , v: String(l.year_built), l: "Année de construction" }); const park = d["Stationnement (total)"] ?? d["Stationnement"]; if (park != null && String(park).trim()) facts.push({ ico: , v: String(park), l: "Stationnement" }); const pieces = d["Nb de pièces"]; if (pieces != null && String(pieces).trim()) facts.push({ ico: , v: String(pieces), l: "Pièces" }); if (l.mls) facts.push({ ico: , v: l.mls, l: "Nº Centris / MLS" }); const enLigne = l.days_on_market ?? (l.first_seen ? Math.max(0, Math.round((Date.now() / 1000 - l.first_seen) / 86400)) : null); const rows: [string, ReactNode][] = [["Agence / source", sourceName(l.source)]]; if (l.agency && l.agency !== sourceName(l.source)) rows.push(["Bannière", l.agency]); if (l.broker_name) rows.push(["Courtier inscripteur", l.broker_name]); if (l.broker_phone) rows.push(["Téléphone", {l.broker_phone}]); if (l.price_label && l.price != null && l.price_label.replace(/\s/g, "") !== fmtPrice(l.price).replace(/\s/g, "")) rows.push(["Prix affiché par la source", l.price_label]); if (enLigne != null) rows.push(["En ligne sur Immo-Ka", enLigne === 0 ? "depuis aujourd'hui" : `depuis ${enLigne}${NBSP}jour${enLigne > 1 ? "s" : ""}`]); const maj = relTime(l.updated_at); if (maj) rows.push(["Synchronisé", maj]); const detEntries = Object.entries(d) .filter(([k, v]) => !DETAIL_HIDDEN.has(k) && !IN_GRID.has(k) && (typeof v === "string" || typeof v === "number") && String(v).trim()); const rooms: Room[] = Array.isArray(d.pieces) ? (d.pieces as Room[]) : []; if (facts.length === 0 && rows.length <= 1 && detEntries.length === 0 && rooms.length === 0) return null; return ( {facts.length > 0 && (
{facts.map((x) => (
{x.v}
{x.l}
))}
)}
{rows.map(([k, v]) => (
{k}{v}
))}
{detEntries.length > 0 && (
{detEntries.map(([k, v]) => (
{k} {/^https?:\/\//.test(String(v)) ? Ouvrir ↗ : {String(v)}}
))}
)} {rooms.length > 0 && ( 1 ? "s" : ""}`}>
{rooms.map((r, i) => ( ))}
PièceNiveauDimensionsRevêtement
{r.nom || "—"}{r.niveau || "—"} {r.dimensions || "—"}{r.revetement || "—"}
)}
); }