/** * ============================================================================= * Job·Ka — Groupe KA * Auteur : Simon-Pierre Boucher * Contact : contact@spboucher.ai * Fichier : frontend/src/pages/Ville.tsx * Rôle : Pages programmatiques SEO — /villes (répertoire) et /ville/:ville. * Le serveur rend le même contenu en HTML (jobka/seo.py) ; ici, la * version interactive avec les cartes d'offres de l'app. * Créé : 2026-08-23 Modifié : 2026-08-23 * ============================================================================= */ import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { fetchJobs, Job } from "../api"; import JobCard from "../components/JobCard"; interface VilleRow { city: string; slug: string; n: number; employers: number; with_salary: number; } interface VilleData { city: string; slug: string; n: number; employers: number; with_salary: number; neighbors: VilleRow[]; } async function get(path: string): Promise { const res = await fetch(path); if (!res.ok) throw new Error(`${res.status}`); return res.json(); } export function VillesPage() { const [villes, setVilles] = useState(null); const [error, setError] = useState(null); useEffect(() => { get<{ villes: VilleRow[] }>("/api/seo/villes") .then((r) => setVilles(r.villes)) .catch((e) => setError(String(e))); window.scrollTo(0, 0); }, []); return (

Offres d'emploi par ville

Toutes les villes du Québec où Job-Ka recense des offres d'emploi, avec le nombre de postes ouverts et d'employeurs qui recrutent.

{error &&
⚠️ {error}
} {!villes && !error &&
Chargement…
} {villes && (
    {villes.map((v) => (
  • {v.city} {" — "} {v.n} offre{v.n > 1 ? "s" : ""} · {v.employers} employeur {v.employers > 1 ? "s" : ""}
  • ))}
)}
); } export default function VillePage() { const { ville = "" } = useParams(); const [data, setData] = useState(null); const [jobs, setJobs] = useState(null); const [error, setError] = useState(null); useEffect(() => { setData(null); setJobs(null); setError(null); get(`/api/seo/ville/${encodeURIComponent(ville)}`) .then((d) => { setData(d); return fetchJobs({ city: d.city }, 60).then((r) => setJobs(r.jobs)); }) .catch((e) => setError(e.message === "404" ? "Ville inconnue." : String(e))); window.scrollTo(0, 0); }, [ville]); if (error) return (

Page introuvable

⚠️ {error} Voir toutes les villes
); if (!data) return (
Chargement…
); return (

Villes — {data.city}

Offres d'emploi à {data.city}

{data.n} poste{data.n > 1 ? "s" : ""} chez {data.employers} employeur {data.employers > 1 ? "s" : ""} {data.with_salary > 0 && ( <> · {data.with_salary} avec salaire affiché )}

{!jobs &&
Chargement des offres…
} {jobs && jobs.map((j) => )}
{data.neighbors.length > 0 && ( <>

Autres villes

{data.neighbors.map((v) => ( {v.city} ({v.n}) ))}

)}

← Toutes les offres d'emploi au Québec

); }