/** * ============================================================================= * Job·Ka — Groupe KA * Auteur : Simon-Pierre Boucher * Contact : contact@spboucher.ai * Fichier : frontend/src/pages/Job.tsx * Rôle : Fiche d'une offre — refonte premium 2026-09-07 (même socle que * les fiches Lou-Ka v3 / Immo-Ka v3). Ordre DOM = ordre visuel, * identique mobile ET desktop (aucun `order`) : héro (employeur · * salaire · capsules · titre · actions) → En bref → navigation * sticky → Salaire et marché → Le poste (+ avantages, description) → * Où travailler → Dossier → Sources. Desktop ≥ 1024 px : colonne * principale + aside sticky. Aucun scrollIntoView à l'ouverture. * Créé : 2026-08-17 Modifié : 2026-09-07 * ============================================================================= */ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { Job, displayTitle, fetchJob, formatSalary } from "../api"; import { Ico } from "../components/Icons"; import { jobFavItem, useFav } from "../favorites"; import "../fiche/fiche.css"; import { setCurrentListing } from "../fiche/current"; import { comparaisonSalaire, enBref } from "../fiche/synthese"; import JobHero from "../fiche/JobHero"; import SectionNav, { NavItem } from "../fiche/SectionNav"; import SalaryCard from "../fiche/SalaryCard"; import JobFacts from "../fiche/JobFacts"; import { DesktopAside, Dossier, KaAssistant, LocationCard, Sources, StickyCTA, Summary, usePastElement } from "../fiche/Closing"; import { Skeleton, useToast } from "../fiche/ui"; function FicheSkeleton() { return (
); } export default function JobPage() { const { uid = "" } = useParams(); const [job, setJob] = useState(null); const [error, setError] = useState(""); const { ids, toggle, connected } = useFav(); const [toast, showToast] = useToast(); const actionsRef = useRef(null); const pastHero = usePastElement(actionsRef); useEffect(() => { setJob(null); setError(""); setCurrentListing(null); fetchJob(uid).then((j) => { setJob(j); setCurrentListing(j); }).catch((e) => setError(String(e))); window.scrollTo(0, 0); return () => setCurrentListing(null); }, [uid]); useEffect(() => { document.body.classList.add("jk-fiche-page"); return () => document.body.classList.remove("jk-fiche-page"); }, []); useEffect(() => { if (job) document.title = `${displayTitle(job)} — ${job.employer}${formatSalary(job) ? ` · ${formatSalary(job)}` : ""} | Job·Ka`; }, [job]); const fav = !!(job && ids.has(job.uid)); const onFav = useCallback(() => { if (!job) return; toggle(jobFavItem(job)); if (connected) showToast(fav ? "Retiré des favoris" : "Ajouté à vos favoris"); }, [job, toggle, connected, fav, showToast]); const onShare = useCallback(async () => { if (!job) return; const url = `https://www.job-ka.com/emploi/${encodeURIComponent(job.uid)}`; try { if (navigator.share) { await navigator.share({ title: `${displayTitle(job)} — ${job.employer} | Job·Ka`, url }); return; } await navigator.clipboard.writeText(url); showToast("Lien copié"); } catch { /* annulé */ } }, [job, showToast]); const cmp = useMemo(() => (job ? comparaisonSalaire(job) : null), [job]); const brief = useMemo(() => (job ? enBref(job) : []), [job]); if (error) return (

Offre introuvable

Cette offre n'existe pas ou a été retirée par l'employeur.

Retour aux offres
); if (!job) return ; const geo = job.lat != null && job.lng != null; const nav: NavItem[] = [ { id: "resume", label: "Résumé" }, ...(formatSalary(job) || job.salary_context ? [{ id: "salaire", label: "Salaire" }] : []), { id: "poste", label: "Poste" }, ...(job.benefits?.length ? [{ id: "avantages", label: "Avantages" }] : []), { id: "description", label: "Description" }, ...(geo ? [{ id: "carte", label: "Carte" }] : []), { id: "dossier", label: "Dossier" }, { id: "sources", label: "Sources" }, ]; return (
); }