SPB Git forge

spb/immo-ka

Public

Immo-Ka — agrégateur des propriétés à vendre au Québec (73 connecteurs, ~40 000 annonces, React+FastAPI)

112commits 1branches 0releases
125.4 MBsize
maindefault branch
13 days agolast push
Python 47.5% HTML 27.9% TypeScript 15.5% CSS 7.2% JavaScript 2%
2.7 KB · 59 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// fiche/PropertyAmenities.tsx : inclusions et caractéristiques — grille5//   compacte d'items avec icône contextuelle (AmenityIco), dédoublonnés ;6//   8 visibles, « Voir les N ». Les « Inclusions » / « Exclusions » textuelles7//   de Centris (details) sont ajoutées en note quand elles existent.8// -----------------------------------------------------------------------------9import { useState } from "react";10import { Listing } from "../api";11import AmenityIco from "../components/AmenityIco";12import { Accordion, MoreButton, SectionCard } from "./ui";1314const norm = (s: string) => s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/\s+/g, " ").trim();1516export default function PropertyAmenities({ l }: { l: Listing }) {17  const [all, setAll] = useState(false);18  const seen = new Set<string>();19  const items = (l.features ?? []).map((f) => f.trim()).filter((f) => {20    const k = norm(f);21    if (!k || seen.has(k)) return false;22    seen.add(k); return true;23  });24  const d = l.details ?? {};25  const inclusions = typeof d["Inclusions"] === "string" ? String(d["Inclusions"]).trim() : "";26  const exclusions = typeof d["Exclusions"] === "string" ? String(d["Exclusions"]).trim() : "";27  const LIMIT = 8;28  const shown = all ? items : items.slice(0, LIMIT);2930  if (items.length === 0 && !inclusions && !exclusions) return null;3132  return (33    <SectionCard id="inclusions" title="Caractéristiques et inclusions"34                 sub={items.length ? `${items.length} élément${items.length > 1 ? "s" : ""} publié${items.length > 1 ? "s" : ""} par la source` : undefined}>35      {items.length > 0 && (36        <div className="ik-amen" role="list">37          {shown.map((t) => (38            <div className="ik-amen-it" role="listitem" key={t}>39              <span className="ik-amen-ico" aria-hidden="true"><AmenityIco label={t} size={15} fallback="check" /></span>40              <span className="ik-amen-txt">{t}</span>41            </div>42          ))}43        </div>44      )}45      {items.length > LIMIT && (46        <MoreButton onClick={() => setAll(!all)} expanded={all}>47          {all ? "Réduire" : `Voir les ${items.length} caractéristiques`}48        </MoreButton>49      )}50      {(inclusions || exclusions) && (51        <Accordion title="Inclusions et exclusions de la vente" small defaultOpen={items.length === 0}>52          {inclusions && <p><b>Inclus :</b> {inclusions}</p>}53          {exclusions && <p><b>Exclus :</b> {exclusions}</p>}54        </Accordion>55      )}56    </SectionCard>57  );58}59