Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 98.9%
Python 0.6%
1// -----------------------------------------------------------------------------2// Lou-Ka — Location court terme3// components/CtPriceAnalysis.tsx : bloc « Analyse du prix / nuit » (fiche CT)4// Position du tarif dans la distribution des hébergements comparables5// (segment : type + capacité + région touristique, calculé par l'API).6// -----------------------------------------------------------------------------7import { CtPriceContext, fmtNight } from "../ctapi";89export function ctDealBadge(p: CtPriceContext | null) {10 if (!p || p.deviation == null || p.verdict == null) return null;11 const pct = Math.round(Math.abs(p.deviation) * 100);12 if (p.verdict === "sous")13 return { cls: "deal-good", txt: `≈ ${pct} % sous le prix médian du segment` };14 if (p.verdict === "dessus")15 return { cls: "deal-high", txt: `≈ ${pct} % au-dessus du prix médian` };16 return { cls: "deal-ok", txt: "Dans les prix du segment" };17}1819function Histo({ p, price }: { p: CtPriceContext; price: number }) {20 const bins = p.histogram;21 if (!bins || bins.length < 4) return null;22 const W = 320, H = 96, top = 18, bottom = 16;23 const lo = bins[0].x0, hi = bins[bins.length - 1].x1;24 if (hi <= lo) return null;25 const max = Math.max(...bins.map((b) => b.n), 1);26 const x = (v: number) => ((Math.min(Math.max(v, lo), hi) - lo) / (hi - lo)) * W;27 const bw = W / bins.length;28 const plotH = H - top - bottom;29 const priceX = x(price), medX = x(p.median);30 const close = Math.abs(priceX - medX) < 64;31 const lblAnchor = (px: number) => (px < 56 ? "start" : px > W - 56 ? "end" : "middle");32 return (33 <svg className="fv-histo" viewBox={`0 0 ${W} ${H}`} role="img"34 aria-label={`Position du prix (${price} $/nuit) parmi ${p.n} hébergements comparables`}>35 {bins.map((b, i) => {36 const h = Math.max(1.5, (b.n / max) * plotH);37 const inBin = price >= b.x0 && price < b.x1;38 return (39 <rect key={i} x={i * bw + 1} y={H - bottom - h} rx="2"40 width={Math.max(1, bw - 2)} height={h}41 fill={inBin ? "var(--accent, #ff6a00)" : "rgba(204, 85, 0, 0.28)"}>42 <title>{`${b.x0} $ – ${b.x1} $ : ${b.n} hébergement${b.n > 1 ? "s" : ""}`}</title>43 </rect>44 );45 })}46 {/* fourchette interquartile (bande) */}47 <rect x={x(p.p25)} y={H - bottom} width={Math.max(2, x(p.p75) - x(p.p25))}48 height="3.5" rx="1.5" fill="rgba(204, 85, 0, 0.45)" />49 {/* marqueur médiane */}50 <line x1={medX} x2={medX} y1={top - 2} y2={H - bottom}51 stroke="var(--accent-deep, #cc5500)" strokeWidth="1.6" strokeDasharray="3 3" />52 {!close && (53 <text x={medX} y={top - 7} textAnchor={lblAnchor(medX)}54 className="fv-histo-lbl fv-histo-lbl-fv">Médiane</text>55 )}56 {/* marqueur du prix demandé */}57 <line x1={priceX} x2={priceX} y1={top - 2} y2={H - bottom}58 stroke="var(--ink, #141814)" strokeWidth="2" />59 <text x={priceX} y={close ? top - 7 : H - 4} textAnchor={lblAnchor(priceX)}60 className="fv-histo-lbl">Ce prix{close ? " / médiane" : ""}</text>61 <text x="1" y={H - 4} className="fv-histo-axis" textAnchor="start">{lo} $</text>62 <text x={W - 1} y={H - 4} className="fv-histo-axis" textAnchor="end">{hi} $</text>63 </svg>64 );65}6667export default function CtPriceAnalysis({ p, price }:68 { p: CtPriceContext | null; price: number | null }) {69 if (!p || price == null) return null;70 const badge = ctDealBadge(p);71 return (72 <section className="f-bloc f-fairvalue" id="analyse-prix">73 <h2>Analyse du prix Lou-Ka</h2>74 <div className="fv-head">75 <div>76 <div className="fv-value">{fmtNight(p.median)} <small>/ nuit</small></div>77 <div className="fv-range">78 Prix médian du segment · 50 % des prix entre {fmtNight(p.p25)} et {fmtNight(p.p75)}79 </div>80 </div>81 {badge && <div className={`fv-badge ${badge.cls}`}>{badge.txt}</div>}82 </div>83 <Histo p={p} price={price} />84 <div className="fv-meta">85 <span>Segment : <b>{p.segment}</b></span>86 <span>{p.n.toLocaleString("fr-CA")} hébergements comparables</span>87 <span>Moins cher que {100 - p.percentile} % du segment</span>88 </div>89 <p className="fine">90 Comparaison indicative des tarifs affichés (« à partir de ») par les91 sources pour des hébergements du même type dans la même région — les92 prix réels varient selon les dates et la durée du séjour.93 </p>94 </section>95 );96}97