/** * ============================================================================= * Job·Ka — Groupe KA * Auteur : Simon-Pierre Boucher * Contact : contact@spboucher.ai * Fichier : frontend/src/components/JobCard.tsx * Rôle : Rangée d'offre éditoriale — titre fort, salaire en exergue à * droite, métadonnées calmes (mode, type, séniorité, exigences), * fraîcheur, cœur ♥ favoris. Zéro carte bordée : filets fins + * barre d'accent au survol. * Créé : 2026-08-17 Modifié : 2026-08-25 * ============================================================================= */ import { Link } from "react-router-dom"; import { agoLabel, daysSince, displayTitle, formatSalary, Job, MODE_FR, SENIORITY_FR, TYPE_FR, yearlyEquiv, } from "../api"; import { FavButton } from "../favorites"; const NEW_DAYS = 3; // « Nouveau » : publiée il y a 3 jours ou moins export default function JobCard({ job }: { job: Job }) { const salary = formatSalary(job); const yearly = yearlyEquiv(job); const days = daysSince(job.date_posted); const isNew = days !== null && days <= NEW_DAYS; const exp = job.requirements?.experience_years; const langs = job.requirements?.languages; // métadonnées calmes, séparées par des points médians (pas de soupe de pilules) const tags: string[] = []; if (job.work_mode) tags.push(MODE_FR[job.work_mode] ?? job.work_mode); if (job.employment_type) tags.push(TYPE_FR[job.employment_type] ?? job.employment_type); if (job.seniority) tags.push(SENIORITY_FR[job.seniority] ?? job.seniority); if (typeof exp === "number") tags.push(`${exp} an${exp > 1 ? "s" : ""} d'exp.`); if (langs && langs.length === 2) tags.push("Bilingue"); if (job.category) tags.push(job.category); return (

{isNew && Nouveau} {job.ka_reco && ( Recommandé pour vous )} {displayTitle(job)}

{job.employer} {job.city && {job.city}} {job.is_direct !== false && ( source directe )}

{tags.length > 0 && (

{tags.map((t, i) => ( {t} ))}

)}
{salary ? ( <> {salary} {yearly && {yearly}} ) : ( Salaire non affiché )} {days !== null ? `Publiée ${agoLabel(days)}` : job.ats}
); }