Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 98.9%
Python 0.6%
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