// ----------------------------------------------------------------------------- // Rent-Ka — Rental listings aggregator (Canada, outside Québec) // Author: Simon-Pierre Boucher — contact@spboucher.ai // components/GestionnaireBloc.tsx: "Who manages this rental?" block (detail) // Manager profile (direct source) + Google reputation: real rating // distribution, recent average, trend and recurring themes computed over // the reviews synced in OUR database (rentka/managers.py) — not just the // average Google shows, and zero API calls on load. // The Google profile is only shown when the match is confident. // ----------------------------------------------------------------------------- import { useEffect, useState } from "react"; import { Gestionnaire, fetchGestionnaire } from "../api"; const NBSP = " "; const SENT_CLS: Record = { "négatif": "zi-eleve", "neutre": "zi-nc", "positif": "zi-ok", "negative": "zi-eleve", "neutral": "zi-nc", "positive": "zi-ok", }; export default function GestionnaireBloc({ source }: { source: string }) { const [d, setD] = useState(null); useEffect(() => { setD(null); fetchGestionnaire(source).then(setD).catch(() => setD(null)); }, [source]); // without an associated Google profile, the "Practical details" block is enough if (!d || !d.google_maps || d.google_maps.statut === "non_associe") return null; const g = d.google_maps; const avis = d.avis; const dist = avis?.distribution; const total = dist ? Object.values(dist).reduce((a, b) => a + b, 0) : 0; const recents = (d.avis_recents ?? []).filter((a) => a.texte).slice(0, 3); return (

Who manages this rental?

{d.nom}
{d.annonces_actives}{NBSP}active listing{d.annonces_actives > 1 ? "s" : ""} on Rent-Ka {d.site_web && ( <> {" · "} website ↗ )}
{g.note != null && (
★ {g.note.toFixed(1)} {g.nombre_avis}{NBSP}Google reviews — profile “{g.nom}”
)} {avis && avis.n > 0 && ( <> {dist && total > 0 && (
{[5, 4, 3, 2, 1].map((n) => { const c = dist[String(n)] ?? 0; return (
{n}★ = 4 ? "pos" : ""}`} style={{ width: `${Math.round((100 * c) / total)}%` }} /> {c}
); })}
)}
{avis.moyenne_12m != null && ( Last 12 months average: {avis.moyenne_12m.toFixed(1)} ({avis.n_12m} reviews) )} {avis.tendance && ( {avis.tendance} )}
{(avis.plaintes_frequentes?.length ?? 0) > 0 && (
Recurring complaints in reviews:{" "} {avis.plaintes_frequentes!.map((t) => ( {t} ))}
)} {recents.length > 0 && (
Recent review excerpts ({recents.length}) {recents.map((a, i) => (
{a.note != null ? `${a.note}★` : "—"} {" "} {a.texte}
{a.date ? new Date(a.date).toLocaleDateString("en-CA", { month: "long", year: "numeric" }) : ""} {a.reponse_proprietaire && " · the manager replied"}
))}
)} )}

Google Maps profile matched automatically ({Math.round((g.confiance_association ?? 0) * 100)}{NBSP}% confidence —{" "} {g.methode}). Statistics computed over the {avis?.n ?? 0} reviews synced in the Rent-Ka database {g.nombre_avis && avis && avis.n < g.nombre_avis ? ` (sample of the most recent; Google reports ${g.nombre_avis})` : ""}. Themes detected by lexicon — indicative inference, not a human reading.

); }