Python 68.8%
TypeScript 18.6%
CSS 8.7%
JavaScript 3.3%
HTML 0.6%
1// -----------------------------------------------------------------------------2// Rent-Ka — Rental listings aggregator (Canada, outside Québec)3// Author: Simon-Pierre Boucher — contact@spboucher.ai4// components/HistoriqueLouka.tsx: "Listing history" block (detail page)5// Timeline of REAL sync observations: price, description, area,6// availability, inclusions, photo changes, removals/returns. + "Past7// lives": likely recycled listings of the same unit (multi-signal score,8// explainable inference).9// -----------------------------------------------------------------------------10import { useEffect, useState } from "react";11import {12 HistoriqueLouka as HistoT, Recyclees,13 fetchHistorique, fetchRecyclees, fmtPrice,14} from "../api";1516const NBSP = " ";1718const fmtTs = (ts: number | null | undefined): string =>19 ts20 ? new Date(ts * 1000).toLocaleDateString("en-CA",21 { day: "numeric", month: "short", year: "numeric" })22 : "—";2324const EVENT_LABEL: Record<string, string> = {25 prix: "Price",26 description: "Description changed",27 superficie: "Area changed",28 dispo: "Availability changed",29 inclusions: "Inclusions changed",30 photos: "Photos changed",31 disparition: "Listing removed",32 reapparition: "Listing republished",33};3435export default function HistoriqueLouka({ uid }: { uid: string }) {36 const [d, setD] = useState<HistoT | null>(null);37 const [rec, setRec] = useState<Recyclees | null>(null);38 useEffect(() => {39 setD(null); setRec(null);40 fetchHistorique(uid).then(setD).catch(() => setD(null));41 fetchRecyclees(uid).then(setRec).catch(() => setRec(null));42 }, [uid]);43 if (!d) return null;4445 const variationPct = d.variation != null46 ? `${d.variation > 0 ? "+" : "−"}${Math.abs(Math.round(d.variation * 100))}${NBSP}%`47 : null;48 const items = d.timeline.slice(0, 12);49 const matches = rec?.matches ?? [];5051 return (52 <section className="f-bloc f-histolk" id="historique-louka">53 <h2>Listing history</h2>54 <div className="kv">55 <div className="cell">56 <div className="k">Tracked since</div>57 <div className="v">{fmtTs(d.premiere_observation)}</div>58 </div>59 <div className="cell">60 <div className="k">Online</div>61 <div className="v">{d.jours_en_ligne}{NBSP}day{d.jours_en_ligne > 1 ? "s" : ""}</div>62 </div>63 {d.prix_initial != null && d.prix_actuel != null && d.prix_initial !== d.prix_actuel && (64 <div className="cell">65 <div className="k">Initial → current price</div>66 <div className="v">67 {fmtPrice(d.prix_initial)} → {fmtPrice(d.prix_actuel)}68 {variationPct && ` (${variationPct})`}69 </div>70 </div>71 )}72 <div className="cell">73 <div className="k">Changes observed</div>74 <div className="v">{d.modifications}</div>75 </div>76 </div>7778 {items.length > 0 && (79 <ul className="hl-timeline">80 {items.map((it, i) => (81 <li key={`${it.ts}-${it.type}-${i}`}82 className={`hl-item hl-${it.type}`}>83 <span className="hl-date">{fmtTs(it.ts)}</span>84 <span className="hl-texte">85 {it.type === "prix" ? (86 it.prix_avant != null87 ? <>Price {it.prix_avant! > (it.prix ?? 0) ? "lowered" : "raised"} from {fmtPrice(it.prix_avant ?? null)} to <b>{fmtPrice(it.prix ?? null)}</b></>88 : <>First observed price: <b>{fmtPrice(it.prix ?? null)}</b></>89 ) : (90 EVENT_LABEL[it.type] ?? it.type91 )}92 </span>93 </li>94 ))}95 </ul>96 )}97 {items.length === 0 && (98 <p className="fine">99 No change observed since this listing's first sync.100 </p>101 )}102103 {matches.length > 0 && (104 <div className="hl-recyclees">105 <h3>Likely past lives of this unit</h3>106 {matches.slice(0, 3).map((m) => (107 <div className="hl-match" key={m.uid}>108 <div>109 <span className="zi-badge zi-modere">110 likely repost ({m.confiance}{NBSP}% confidence)111 </span>{" "}112 {m.prix != null && <>advertised at {fmtPrice(m.prix)}</>}113 {m.derniere_observation != null && <> until {fmtTs(m.derniere_observation)}</>}114 </div>115 <div className="hl-signaux">{m.signaux.join(" · ")}</div>116 </div>117 ))}118 <p className="fine">{rec?.methode} — inference, no automatic merging.</p>119 </div>120 )}121122 <p className="fine">{d.methode}</p>123 </section>124 );125}126