SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
4.8 KB · 87 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Lou-Ka — Agrégateur de logements à louer (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// fiche/PropertyQuickFacts.tsx : « Le logement » — grille compacte icône +5//   valeur + libellé (chambres, salle de bain, type, superficie, étage,6//   meublé, animaux, disponibilité, bail…) puis rangées pratiques7//   (gestionnaire, prix affiché, en ligne depuis, synchronisé, historique).8// -----------------------------------------------------------------------------9import { ReactNode } from "react";10import { Listing, fmtAvailability, fmtPrice, sourceName } from "../api";11import {12  IcoBath, IcoBed, IcoBuilding, IcoCalendar, IcoDoc, IcoHouse, IcoPaw, IcoRuler, IcoSofa, IcoUsers,13} from "../components/Icons";14import { SectionCard, NBSP, relTime } from "./ui";1516const PETS: Record<string, string> = { oui: "Acceptés", non: "Refusés", conditions: "Sous conditions" };1718export default function PropertyQuickFacts({ l }: { l: Listing }) {19  const f = l.digest?.faits;20  const conf = l.digest?.confiance ?? {};21  const d = l.details ?? {};22  const dispo = fmtAvailability(l.availability_date);23  const facts: { ico: ReactNode; v: string; l: string }[] = [];2425  if (l.bedrooms != null)26    facts.push({ ico: <IcoBed size={17} />, v: l.bedrooms === 0 ? "Studio" : `${Math.round(l.bedrooms)}`, l: l.bedrooms === 0 ? "Aire ouverte" : `Chambre${l.bedrooms > 1 ? "s" : ""}` });27  const sdb = (l as unknown as { bathrooms?: number | null }).bathrooms;28  if (sdb != null)29    facts.push({ ico: <IcoBath size={17} />, v: `${sdb}`, l: `Salle${sdb > 1 ? "s" : ""} de bain` });30  else if (f?.salle_de_bain && conf.salle_de_bain !== "faible")31    facts.push({ ico: <IcoBath size={17} />, v: f.salle_de_bain === "commune" ? "Partagée" : "Privée", l: "Salle de bain" });32  if (l.unit_type) facts.push({ ico: <IcoHouse size={17} />, v: l.unit_type, l: "Type" });33  if (l.area_sqft) facts.push({ ico: <IcoRuler size={17} />, v: `${Math.round(l.area_sqft).toLocaleString("fr-CA")}${NBSP}pi²`, l: "Superficie" });34  if (d.floor != null) facts.push({ ico: <IcoBuilding size={17} />, v: `${d.floor}ᵉ`, l: "Étage" });35  if (l.furnished != null) facts.push({ ico: <IcoSofa size={17} />, v: l.furnished ? "Meublé" : "Non meublé", l: "Ameublement" });36  if (l.pets) facts.push({ ico: <IcoPaw size={17} />, v: PETS[l.pets] ?? l.pets, l: "Animaux" });37  if (dispo) {38    const iso = l.availability_date;39    const court = iso && iso !== "now"40      ? new Date(iso + "T00:00:00").toLocaleDateString("fr-CA", { day: "numeric", month: "short", year: "numeric" }).replace(/^1 /, "1ᵉʳ ")41      : "Maintenant";42    facts.push({ ico: <IcoCalendar size={17} />, v: court, l: "Disponible" });43  }44  if (f?.duree_bail_minimale_mois) facts.push({ ico: <IcoDoc size={17} />, v: `${f.duree_bail_minimale_mois} mois`, l: "Bail minimum" });45  if (f?.nb_occupants_total && conf.nb_occupants_total !== "faible")46    facts.push({ ico: <IcoUsers size={17} />, v: `${f.nb_occupants_total}`, l: "Occupants" });4748  const enLigne = l.first_seen ? Math.max(0, Math.round((Date.now() / 1000 - l.first_seen) / 86400)) : null;49  const rows: [string, string][] = [["Gestionnaire", sourceName(l.source)]];50  if (l.price_label && l.price != null && l.price_label.replace(/\s/g, "") !== `${fmtPrice(l.price)}/mois`.replace(/\s/g, ""))51    rows.push(["Prix affiché par la source", l.price_label]);52  if (enLigne != null) rows.push(["En ligne sur Lou-Ka", enLigne === 0 ? "depuis aujourd'hui" : `depuis ${enLigne}${NBSP}jour${enLigne > 1 ? "s" : ""}`]);53  const maj = relTime(l.updated_at);54  if (maj) rows.push(["Synchronisé", maj]);55  if (f?.depot_mentionne && conf.depot_mentionne !== "faible") rows.push(["Dépôt mentionné", f.depot_mentionne]);5657  if (facts.length === 0 && rows.length <= 1) return null;5859  return (60    <SectionCard id="logement" title="Le logement">61      {facts.length > 0 && (62        <div className="lk-facts" role="list">63          {facts.map((x) => (64            <div className="lk-fact" role="listitem" key={x.l + x.v}>65              <span className="lk-fact-ico" aria-hidden="true">{x.ico}</span>66              <span className="lk-fact-txt">67                <div className="lk-fact-v">{x.v}</div>68                <div className="lk-fact-l">{x.l}</div>69              </span>70            </div>71          ))}72        </div>73      )}74      <div className="lk-facts-rows">75        {rows.map(([k, v]) => (76          <div className="lk-kv" key={k}><span className="k">{k}</span><span className="v">{v}</span></div>77        ))}78      </div>79      {(l.digest?.incoherences?.length ?? 0) > 0 && (80        <p className="lk-note warn">81          Incohérence relevée dans l'annonce : {l.digest!.incoherences.join(" ; ")}82        </p>83      )}84    </SectionCard>85  );86}87