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