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/DesktopAside.tsx : colonne droite sticky (≥ 1024 px) — prix, capsule5// marché, adresse, CTA, PDF, Lou-Ka Score en miniature, constats clés,6// favoris / partage. Masquée sur mobile (display:none, pas de réordonnancement).7// -----------------------------------------------------------------------------8import { Listing, fmtPrice, sourceName } from "../api";9import { IcoDoc, IcoExternal, IcoHeart, IcoShare, IcoSparkles } from "../components/Icons";10import { ScoreRing } from "./LouKaScore";11import { PriceCapsule } from "./PropertyHero";12import { BriefList } from "./PropertySummary";13import { ComparaisonPrix, Constat, LouKaScore } from "./synthese";14import { NBSP, relTime } from "./ui";1516export default function DesktopAside({ l, cmp, score, brief, fav, onFav, onShare }: {17 l: Listing; cmp: ComparaisonPrix | null; score: LouKaScore; brief: Constat[];18 fav: boolean; onFav: () => void; onShare: () => void;19}) {20 const maj = relTime(l.updated_at);21 return (22 <aside className="lk-aside" aria-label="Résumé et actions">23 <div className="lk-card lk-aside-card">24 <div>25 <div className="lk-price">{fmtPrice(l.price, l.price_label)}{l.price != null && <small>/{NBSP}mois</small>}</div>26 <div className="lk-price-row" style={{ marginTop: 8 }}><PriceCapsule cmp={cmp} /></div>27 </div>28 <div className="lk-aside-addr">{l.address || l.title}{l.city ? <><br />{[l.sector, l.city].filter(Boolean).join(", ")}</> : null}</div>29 <a className="lk-btn lk-btn-primary" href={`/passerelle/${encodeURIComponent(l.uid)}`} target="_blank" rel="noopener noreferrer">30 Voir l'annonce chez {sourceName(l.source)} <IcoExternal size={16} />31 </a>32 <div className="lk-actions">33 <button type="button" className={`lk-btn lk-btn-ghost ${fav ? "on" : ""}`} style={{ flex: 1 }} aria-pressed={fav} onClick={onFav}>34 <IcoHeart size={17} filled={fav} /> {fav ? "Favori" : "Favoris"}35 </button>36 <button type="button" className="lk-btn lk-btn-ghost" style={{ flex: 1 }} onClick={onShare}><IcoShare size={16} /> Partager</button>37 <a className="lk-btn lk-btn-ghost lk-btn-icon" aria-label="Fiche PDF" title="Fiche PDF" href={`/api/listings/${encodeURIComponent(l.uid)}/pdf`} download><IcoDoc size={17} /></a>38 </div>39 <button type="button" className="lk-btn lk-btn-ghost lk-ka-inline" onClick={() => window.dispatchEvent(new Event("lk:askka"))}>40 <IcoSparkles size={16} /> Demander à Ka41 </button>42 {(score.value != null || brief.length > 0) && <hr className="lk-aside-sep" />}43 {score.value != null && (44 <div className="lk-aside-score">45 <ScoreRing value={score.value} partial={score.partial} size={56} />46 <div className="lk-aside-score-txt">47 <b>Lou-Ka Score · {score.label}</b>48 {score.partial ? "Score partiel" : `Emplacement ${score.emplacement} · Prix ${score.prix}`}49 </div>50 </div>51 )}52 {brief.length > 0 && <BriefList items={brief.slice(0, 4)} compact />}53 {maj && <div className="lk-aside-meta">Synchronisé {maj} · {sourceName(l.source)}</div>}54 </div>55 </aside>56 );57}58