"use client";
import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import { getApiUrl } from "../utils/getApiUrl";
import { pickStrapiImage, type StrapiMediaLike } from "../utils/strapiImage";
import CarouselCompetences from "./CarouselCompetences";
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";
import ModalGlossaire from "./ModalGlossaire";
interface ImageData extends StrapiMediaLike {
name?: string;
}
interface CompetenceData {
name: string;
content: string;
picture?: ImageData[];
}
interface GlossaireItem {
mot_clef: string;
slug: string;
variantes: string[];
description: string;
images?: ImageData[];
}
interface ContentSectionProps {
competenceData: CompetenceData | null;
glossaireData: GlossaireItem[];
titleClass?: string;
contentClass?: string;
}
/**
* Fiche détail compétences — refonte "Digital Atelier" (étape 7.c).
*
* Trois changements structurants par rapport à la version pré-refonte :
*
* 1. **Style tokenisé** : même gabarit "feuillet de vellum" que le portfolio.
* Les classes hardcodées `bg-white/70 text-blue-700 font-headline font-bold`
* disparaissent. Le corps éditorial est rendu en `prose` Newsreader, les
* titres Markdown en Manrope `text-primary`.
*
* 2. **Keywords glossaire & chatbot sans styles inline** : on retire les
* `style="color: red/blue; cursor: pointer"` injectés dans le HTML. On
* conserve les classes `.keyword` / `.chatbot-keyword` historiques et on
* les stylise via `globals.css` avec la palette Stitch (voir `.glossary-keyword`).
* Pour rester rétro-compatible avec les classes historiques, `keyword` est
* renommée `glossary-keyword` dans la transformation.
*
* 3. **Event listeners scopés au wrapper** (ref `contentRef`) plutôt que
* `document.body.addEventListener`. Avant : risque de fuite + interaction
* avec d'autres parties du DOM. Après : la zone "contenu" capture ses clics
* en bubbling, comportement identique mais sans effet de bord global.
*
* 4. **Chatbot via FAB global** (étape 7.e) : plus de `` local dans
* cette fiche. Un clic sur "IA locale" dispatch `CustomEvent("grasbot:open")`
* que le FAB monté dans `layout.tsx` écoute pour ouvrir le chatbot partagé.
*/
export default function ContentSectionCompetences({
competenceData,
glossaireData,
}: ContentSectionProps) {
const [selectedMot, setSelectedMot] = useState(null);
const contentRef = useRef(null);
const apiUrl = getApiUrl();
// Délégation locale : capte les clics sur les keywords injectés dans le Markdown,
// sans polluer document.body comme avant la refonte.
useEffect(() => {
const node = contentRef.current;
if (!node) return;
const handleClick = (event: Event) => {
const target = event.target as HTMLElement;
if (target.dataset?.chatbot === "true") {
window.dispatchEvent(new CustomEvent("grasbot:open"));
return;
}
if (target.classList?.contains("glossary-keyword")) {
const mot = target.getAttribute("data-mot");
if (!mot) return;
const glossaireMot = glossaireData.find((g) => g.mot_clef === mot);
setSelectedMot(glossaireMot || null);
}
};
node.addEventListener("click", handleClick);
return () => node.removeEventListener("click", handleClick);
}, [glossaireData]);
if (!competenceData) {
return (
search_off
Cette compétence est introuvable.
arrow_back
Retour aux compétences
);
}
const { name, content, picture } = competenceData;
const images =
picture
?.map((img: ImageData) => {
const picked = pickStrapiImage(apiUrl, img, "full");
const url =
picked?.src ??
(img.url || img.formats?.large?.url
? `${apiUrl}${img.formats?.large?.url ?? img.url}`
: null);
if (!url) return null;
return {
url,
alt: img.name || `Visuel de la compétence ${name}`,
};
})
.filter((item): item is { url: string; alt: string } => item != null) || [];
/**
* Transforme le Markdown en injectant des spans `.glossary-keyword` / `.chatbot-keyword`
* autour des mots-clés trouvés. Les styles sont définis dans `globals.css`
* (palette Stitch, soulignement pointillé) plutôt qu'inline dans l'attribut style.
*/
function transformMarkdownWithKeywords(text: string) {
if (!text) return "";
let modifiedText = text;
modifiedText = modifiedText.replace(
/\bIA locale\b/g,
`IA locale`
);
if (glossaireData.length) {
glossaireData.forEach(({ mot_clef, variantes }) => {
const regexVariants = variantes
.map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
.join("|");
const regex = new RegExp(`\\b(${mot_clef}|${regexVariants})\\b`, "gi");
modifiedText = modifiedText.replace(regex, (match) => {
return `${match}`;
});
});
}
return modifiedText;
}
const contentWithLinks = transformMarkdownWithKeywords(content);
return (