SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
7.8 KB · 234 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Groupe KA — Annuaire des déménageurs du Québec (page partagée Lou-Ka/Immo-Ka)3// pages/Demenageurs.tsx : liste complète des entreprises de déménagement de la4// province (17 régions), filtrable par région et par texte — /api/demenageurs.5// -----------------------------------------------------------------------------6import { useEffect, useMemo, useState } from "react";78type Mover = {9  id: string;10  name: string;11  city: string;12  region: string;13  address: string;14  phone: string;15  website: string;16  rating: number | null;17  reviews: number | null;18  category: string;19};2021type Doc = {22  generated: string | null;23  total: number;24  count: number;25  regions: { region: string; n: number }[];26  movers: Mover[];27};2829const telHref = (p: string) => "tel:+1" + p.replace(/\D/g, "").slice(-10);3031const host = (u: string) => {32  try {33    return new URL(u).hostname.replace(/^www\./, "");34  } catch {35    return u;36  }37};3839export default function DemenageursPage() {40  const [doc, setDoc] = useState<Doc | null>(null);41  const [error, setError] = useState<string | null>(null);42  const [q, setQ] = useState("");43  const [region, setRegion] = useState("");4445  useEffect(() => {46    fetch("/api/demenageurs")47      .then((r) => {48        if (!r.ok) throw new Error("HTTP " + r.status);49        return r.json();50      })51      .then(setDoc)52      .catch((e) => setError(String(e)));53  }, []);5455  const filtered = useMemo(() => {56    if (!doc) return [];57    const needle = q.trim().toLowerCase();58    return doc.movers.filter(59      (m) =>60        (!region || m.region === region) &&61        (!needle || (m.name + " " + m.city).toLowerCase().includes(needle)),62    );63  }, [doc, q, region]);6465  const groups = useMemo(() => {66    const order = (doc?.regions ?? []).map((r) => r.region);67    const by: Record<string, Mover[]> = {};68    for (const m of filtered) (by[m.region] ??= []).push(m);69    return order70      .filter((r) => by[r]?.length)71      .map((r) => ({ region: r, movers: by[r] }));72  }, [doc, filtered]);7374  const filtering = q.trim() !== "" || region !== "";7576  return (77    <div className="container sources">78      <span className="kicker">Annuaire — services de déménagement</span>79      <h1>Déménageurs du Québec</h1>80      <p className="sub">81        L'annuaire le plus complet des entreprises de déménagement de la82        province : <b>{doc ? doc.total : "…"}</b> déménageurs répertoriés dans83        les 17 régions administratives du Québec, avec téléphone, site web et84        note Google. Recensement continu par le Groupe KA85        {doc?.generated ? ` — mis à jour le ${doc.generated}` : ""}.86      </p>8788      {error && <div className="notice">⚠️ {error}</div>}89      {!doc && !error && <div className="notice">Chargement…</div>}9091      {doc && (92        <>93          <div94            style={{95              display: "flex",96              flexWrap: "wrap",97              gap: 10,98              alignItems: "center",99              margin: "6px 0 10px",100            }}101          >102            <input103              type="search"104              value={q}105              onChange={(e) => setQ(e.target.value)}106              placeholder="Rechercher un déménageur ou une ville…"107              aria-label="Rechercher un déménageur ou une ville"108              style={{109                flex: "1 1 240px",110                maxWidth: 380,111                padding: "10px 14px",112                border: "1.5px solid var(--line)",113                borderRadius: 10,114                font: "inherit",115                background: "var(--surface)",116                color: "inherit",117              }}118            />119            <select120              value={region}121              onChange={(e) => setRegion(e.target.value)}122              aria-label="Filtrer par région administrative"123              style={{124                padding: "10px 12px",125                border: "1.5px solid var(--line)",126                borderRadius: 10,127                font: "inherit",128                background: "var(--surface)",129                color: "inherit",130                maxWidth: "100%",131              }}132            >133              <option value="">Toutes les régions ({doc.total})</option>134              {doc.regions.map((r) => (135                <option key={r.region} value={r.region}>136                  {r.region} ({r.n})137                </option>138              ))}139            </select>140            {filtering && (141              <span style={{ color: "var(--ink-2)", fontSize: 14 }}>142                {filtered.length} résultat{filtered.length > 1 ? "s" : ""}143              </span>144            )}145          </div>146147          {filtered.length === 0 && (148            <div className="notice">149              Aucun déménageur ne correspond à cette recherche.150            </div>151          )}152153          {groups.map((g) => (154            <section key={g.region}>155              <h2 style={{ marginTop: 34 }}>156                {g.region}{" "}157                <span style={{ color: "var(--ink-2)", fontWeight: 600 }}>158                  ({g.movers.length})159                </span>160              </h2>161              <div className="src-wrap" style={{ marginTop: 12 }}>162                <table className="src-table">163                  <thead>164                    <tr>165                      <th>Entreprise</th>166                      <th>Ville</th>167                      <th>Téléphone</th>168                      <th>Site web</th>169                      <th>Note Google</th>170                    </tr>171                  </thead>172                  <tbody>173                    {g.movers.map((m) => (174                      <tr key={m.id}>175                        <td>176                          <b>{m.name}</b>177                          {m.category && (178                            <div179                              style={{ fontSize: 12, color: "var(--ink-2)" }}180                            >181                              {m.category}182                            </div>183                          )}184                        </td>185                        <td>{m.city}</td>186                        <td style={{ whiteSpace: "nowrap" }}>187                          {m.phone ? (188                            <a href={telHref(m.phone)}>{m.phone}</a>189                          ) : (190                            "—"191                          )}192                        </td>193                        <td>194                          {m.website ? (195                            <a196                              href={m.website}197                              target="_blank"198                              rel="noopener noreferrer nofollow"199                            >200                              {host(m.website)}201                            </a>202                          ) : (203                            "—"204                          )}205                        </td>206                        <td style={{ whiteSpace: "nowrap" }}>207                          {m.rating208                            ? `★ ${m.rating.toFixed(1)} (${m.reviews ?? 0})`209                            : "—"}210                        </td>211                      </tr>212                    ))}213                  </tbody>214                </table>215              </div>216            </section>217          ))}218219          <p220            className="sub"221            style={{ marginTop: 34, fontSize: 13, maxWidth: 720 }}222          >223            Annuaire informatif compilé à partir des fiches publiques Google.224            Avant d'embaucher un déménageur, vérifiez qu'il est inscrit au225            Registraire des entreprises du Québec, qu'il détient les assurances226            requises et demandez toujours une soumission écrite. Le Groupe KA227            n'a aucun lien d'affaires avec les entreprises listées.228          </p>229        </>230      )}231    </div>232  );233}234