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/TrueCostCard.tsx : « Coût réel mensuel » — loyer + frais non inclus,5// chaque poste étiqueté (inclus / observé / estimé / inconnu), total et6// annuel, prix au pi² avec percentile réellement calculé, estimation7// Hydro-Québec (cache ; bouton pour lancer le calcul à la demande).8// -----------------------------------------------------------------------------9import { useState } from "react";10import { CoutReel, HydroEstimate, fetchHydro, fmtPrice } from "../api";11import { IcoBolt2, IcoWallet } from "../components/Icons";12import { Accordion, ErrorState, SectionCard, SkeletonLines, NBSP } from "./ui";13import { Res } from "./useFicheData";1415const ST: Record<string, string> = { included: "inclus", observed: "observé", estimated: "estimé", unknown: "inconnu", calculated: "calculé", inferred: "estimé" };1617export default function TrueCostCard({ cout, hydro, adresse, uid, lat, lng, onRetry }: {18 cout: Res<CoutReel>; hydro: Res<HydroEstimate>; adresse: string; uid: string;19 lat: number | null; lng: number | null; onRetry: () => void;20}) {21 const [h, setH] = useState<HydroEstimate | null>(null);22 const [busy, setBusy] = useState(false);23 const d = cout.status === "ok" ? cout.data : null;24 const hy = h ?? (hydro.status === "ok" ? hydro.data : null);25 const hydroVisible = hy && (hy.disponible || hy.en_attente || !/captcha|configuré|incomplète/.test(hy.raison || ""));26 if (cout.status === "na" || (d && d.loyer == null)) return null;2728 const lancer = () => {29 setBusy(true);30 fetchHydro(adresse, { uid, lat, lng }, true).then(setH).catch(() => {}).finally(() => setBusy(false));31 };3233 return (34 <SectionCard id="cout" title="Coût réel mensuel" icon={<IcoWallet size={18} />}35 sub="Loyer + frais non inclus, chaque poste avec son statut">36 {cout.status === "loading" && <SkeletonLines n={4} />}37 {cout.status === "error" && <ErrorState onRetry={onRetry}>Calcul du coût réel temporairement indisponible.</ErrorState>}38 {d && (39 <>40 <ul className="lk-cost">41 {d.lignes.map((li) => (42 <li key={li.poste}>43 <span className="n">{li.poste}<span className={`lk-pill ${li.statut in ST ? (li.statut === "inferred" || li.statut === "calculated" ? "estimated" : li.statut) : "unknown"}`}>{ST[li.statut] ?? li.statut}</span></span>44 <span className={`v ${li.montant == null ? "na" : ""}`}>45 {li.montant != null && li.montant > 0 && fmtPrice(li.montant)}46 {li.montant === 0 && li.statut === "included" && `0${NBSP}$`}47 {li.montant == null && "—"}48 </span>49 </li>50 ))}51 {d.total_estime != null && (52 <li className="total"><span className="n">Total estimé</span><span className="v">≈{NBSP}{fmtPrice(d.total_estime)}{NBSP}/mois</span></li>53 )}54 {d.annuel_estime != null && (55 <li className="annuel"><span className="n">soit sur 12 mois</span><span className="v">≈{NBSP}{fmtPrice(d.annuel_estime)}</span></li>56 )}57 </ul>58 {d.postes_inconnus.length > 0 && (59 <p className="lk-cost-note">Non chiffrables avec les données publiées : {d.postes_inconnus.join(", ").toLowerCase()} — le total réel peut être plus élevé.</p>60 )}61 {d.pi2 && (62 <p className="lk-cost-note">63 <b>{d.pi2.valeur.toFixed(2).replace(".", ",")}{NBSP}$/pi²</b>64 {d.pi2.percentile_secteur != null && <> — moins cher que <b>{100 - d.pi2.percentile_secteur}{NBSP}%</b> des {d.pi2.n_secteur} logements comparables du secteur (~2{NBSP}km)</>}65 {d.pi2.percentile_secteur == null && d.pi2.percentile_ville != null && <> — moins cher que <b>{100 - d.pi2.percentile_ville}{NBSP}%</b> des {d.pi2.n_ville} comparables ({d.pi2.portee_ville})</>}66 {d.pi2.percentile_note && <> {d.pi2.percentile_note}</>}67 </p>68 )}69 {hydroVisible && hy && (70 <div className="lk-note info" style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>71 <IcoBolt2 size={16} />72 {hy.disponible ? (73 <span><b>Électricité : {fmtPrice(hy.cout_mensuel!)}{NBSP}/mois</b> (≈{NBSP}{fmtPrice(hy.cout_annuel!)}{NBSP}/an{hy.kwh_annuel ? `, ${hy.kwh_annuel.toLocaleString("fr-CA")} kWh` : ""}) — estimation Hydro-Québec fondée sur la consommation réelle du logement.</span>74 ) : hy.en_attente ? (75 <>76 <span style={{ flex: 1 }}>Estimation Hydro-Québec du coût d'électricité disponible à la demande.</span>77 <button type="button" className="lk-btn lk-btn-ghost" style={{ minHeight: 38 }} onClick={lancer} disabled={busy}>78 {busy ? "Estimation en cours…" : "Estimer"}79 </button>80 </>81 ) : (82 <span>Hydro-Québec n'a pas d'estimation pour cette adresse.</span>83 )}84 </div>85 )}86 <Accordion title="Méthodologie" small><p>{d.methode}</p></Accordion>87 </>88 )}89 </SectionCard>90 );91}92