diff --git a/ai-act-compass.jsx b/ai-act-compass.jsx index ae0112a..7b9e301 100644 --- a/ai-act-compass.jsx +++ b/ai-act-compass.jsx @@ -8,9 +8,11 @@ import { import { t } from './src/lib/i18n.js'; import { computeCategory, + computeRoleNotes, PROHIBITED_PRACTICES, ANNEX_III_AREAS, ART50_TRIGGERS, + ART5_CARVEOUTS, } from './src/lib/classify.js'; /* ============================================================================ @@ -491,6 +493,11 @@ const QUICKWINS = { ], }; +// Identifies the art. 27 FRIA quickwin so it can be gated by computeRoleNotes. +// Keep this in sync with QUICKWINS.HAUT_RISQUE_ANNEXE_III; if other quickwins +// ever cite art. 27 for unrelated reasons, this predicate needs tightening. +const isFriaItem = (item) => (item.refs || []).some(r => r === 'art. 27'); + /* --------------------------------------------------------------------------- * CHECKLIST by category * ------------------------------------------------------------------------- */ @@ -1304,7 +1311,7 @@ function PrintSection({ icon: Icon, title, breakBefore = true, children }) { ); } -function generateReport(answers, result, lang) { +function generateReport(answers, result, lang, friaRequired = false) { const meta = CATEGORIES_META[result.primary]; const role = ROLES.find(r => r.id === answers.role); const lines = []; @@ -1323,7 +1330,9 @@ function generateReport(answers, result, lang) { result.justifications.forEach(j => lines.push(` - [${j.ref}] ${j.label}`)); lines.push(''); lines.push(t(UI.reportQuickwins, lang)); - (QUICKWINS[result.primary] || []).forEach((q, i) => { + (QUICKWINS[result.primary] || []) + .filter(q => !isFriaItem(q) || friaRequired) + .forEach((q, i) => { lines.push(` ${i + 1}. ${t(q.titre, lang)} [${t(q.delai, lang)}]`); lines.push(` ${t(q.action, lang)}`); lines.push(` ${t(UI.reportRefs, lang)} : ${q.refs.map(r => t(r, lang)).join(' | ')}`); @@ -1366,11 +1375,11 @@ const htmlEscape = (s) => String(s ?? '').replace(/[&<>"']/g, c => ( { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c] )); -function buildPrintHTML({ result, answers, lang, today, checked }) { +function buildPrintHTML({ result, answers, lang, today, checked, friaRequired = false }) { const meta = CATEGORIES_META[result.primary]; const role = ROLES.find(r => r.id === answers.role); const nature = NATURES.find(n => n.id === answers.nature); - const QW = QUICKWINS[result.primary] || []; + const QW = (QUICKWINS[result.primary] || []).filter(q => !isFriaItem(q) || friaRequired); const CL = CHECKLIST[result.primary] || []; const extraCL = (result.secondary || []).map(s => ({ cat: s, items: CHECKLIST[s] || [] })); const applicableTimeline = TIMELINE.filter(m => @@ -1641,7 +1650,12 @@ function Result({ answers, result, onRestart }) { useEffect(() => { window.scrollTo({ top: 0, behavior: 'auto' }); }, []); const meta = CATEGORIES_META[result.primary]; + const roleNotes = useMemo( + () => computeRoleNotes(answers, answers.role, lang), + [answers, lang], + ); const QW = QUICKWINS[result.primary] || []; + const gatedQW = QW.filter(item => !isFriaItem(item) || roleNotes.friaRequired); const CL = CHECKLIST[result.primary] || []; const role = ROLES.find(r => r.id === answers.role); const nature = NATURES.find(n => n.id === answers.nature); @@ -1654,7 +1668,7 @@ function Result({ answers, result, onRestart }) { ); const handleCopy = async () => { - const txt = generateReport(answers, result, lang); + const txt = generateReport(answers, result, lang, roleNotes.friaRequired); // Tentative API moderne (nécessite un contexte sécurisé HTTPS) try { if (navigator.clipboard && navigator.clipboard.writeText) { @@ -1709,7 +1723,7 @@ function Result({ answers, result, onRestart }) { setPdfBusy(true); // 1. Build print CSS + HTML - const { css, html } = buildPrintHTML({ result, answers, lang, today, checked }); + const { css, html } = buildPrintHTML({ result, answers, lang, today, checked, friaRequired: roleNotes.friaRequired }); // 2. Inject the HTML into a hidden sandbox AND inject the