/** * ============================================================================= * Job·Ka — Groupe KA * Auteur : Simon-Pierre Boucher * Contact : contact@spboucher.ai * Fichier : frontend/src/fiche/JobFacts.tsx * Rôle : « Le poste » — grille icône/valeur (mode, type, séniorité, langue, * expérience, scolarité, catégorie, région), rangées (publiée, date * limite, plateforme, lieu), avantages, description (tronquée), * champs bruts des exigences en accordéon. * Créé : 2026-09-07 * ============================================================================= */ import { ReactNode, useState } from "react"; import { Job, LANG_FR, MODE_FR, SENIORITY_FR, TYPE_FR, agoLabel, daysSince, formatDate } from "../api"; import { Ico } from "../components/Icons"; import { Accordion, MoreButton, SectionCard } from "./ui"; export default function JobFacts({ job }: { job: Job }) { const [open, setOpen] = useState(false); const r = job.requirements ?? {}; const facts: { ico: ReactNode; v: string; l: string }[] = []; if (job.work_mode) facts.push({ ico: , v: MODE_FR[job.work_mode] ?? job.work_mode, l: "Mode de travail" }); if (job.employment_type) facts.push({ ico: , v: TYPE_FR[job.employment_type] ?? job.employment_type, l: "Type d'emploi" }); if (job.seniority) facts.push({ ico: , v: SENIORITY_FR[job.seniority] ?? job.seniority, l: "Niveau" }); if (job.language) facts.push({ ico: , v: LANG_FR[job.language] ?? job.language, l: "Langue de l'offre" }); if (typeof r.experience_years === "number") facts.push({ ico: , v: `${r.experience_years} an${r.experience_years > 1 ? "s" : ""} min.`, l: "Expérience exigée" }); if (typeof r.education === "string" && r.education) facts.push({ ico: , v: r.education, l: "Scolarité exigée" }); if (Array.isArray(r.languages) && r.languages.length) facts.push({ ico: , v: r.languages.length === 2 ? "Bilingue" : r.languages.join(", "), l: "Langues exigées" }); if (job.category) facts.push({ ico: , v: job.category, l: "Catégorie" }); if (job.region) facts.push({ ico: , v: job.region, l: "Région" }); const days = daysSince(job.date_posted); const rows: [string, string][] = []; if (job.location_label || job.address) rows.push(["Lieu", job.location_label || [job.address, job.city].filter(Boolean).join(", ")]); if (job.date_posted) rows.push(["Publiée", `${formatDate(job.date_posted)}${days != null ? ` (${agoLabel(days)})` : ""}`]); if (job.date_deadline) rows.push(["Date limite", formatDate(job.date_deadline)]); if (job.ats) rows.push(["Plateforme de recrutement", job.ats]); const det = job.details ?? {}; if (typeof det.industry === "string" && det.industry) rows.push(["Secteur", det.industry as string]); if (typeof det.employment_label === "string" && det.employment_label) rows.push(["Statut annoncé", det.employment_label as string]); const texte = (job.description || "").trim(); const long = texte.length > 700; const bruts = Object.entries(r).filter(([k, v]) => !["experience_years", "education", "languages"].includes(k) && v != null && String(v).trim() && typeof v !== "object"); return ( {facts.length > 0 && (
{facts.map((x) => (
{x.v}
{x.l}
))}
)} {rows.length > 0 && (
{rows.map(([k, v]) =>
{k}{v}
)}
)} {(job.benefits?.length ?? 0) > 0 && ( <>

Avantages annoncés · {job.benefits.length}

{job.benefits.map((b) => (
{b}
))}
)}

Description du poste

{texte ? ( <>

{texte}

{long && setOpen(!open)} expanded={open}>{open ? "Réduire" : "Lire la suite"}} ) :

L'employeur ne fournit pas de description exploitable — consultez l'offre originale.

} {bruts.length > 0 && (
{bruts.map(([k, v]) =>
{k}{String(v)}
)}
)}
); }