/* Admin — Modules Formations + Rétro-planning */ const { useState: useStateC, useMemo: useMemoC } = React; /* ============================================================ TRAINING MODULES (catalog, sessions, enrollments) ============================================================ */ const TRAINING_CATALOG = [ { code: "FIA-101", title: "Prise en main des outils IA (ChatGPT, Copilot)", duration: 7, level: "Débutant", price: 950 }, { code: "FIA-102", title: "IA générative pour la création visuelle (Firefly, Midjourney)", duration: 14, level: "Intermédiaire", price: 1850 }, { code: "FIA-103", title: "IA pour le marketing & la rédaction", duration: 7, level: "Intermédiaire", price: 1100 }, { code: "FIA-104", title: "Automatiser ses tâches administratives avec l'IA", duration: 7, level: "Débutant", price: 990 }, { code: "FIA-201", title: "RGPD, droits d'auteur & responsabilité IA", duration: 4, level: "Tous niveaux", price: 750 }, { code: "FIA-202", title: "Construire une politique IA pour son entreprise", duration: 4, level: "Dirigeant", price: 850 }, { code: "FIA-203", title: "IA pour la relation client et le commerce", duration: 7, level: "Intermédiaire", price: 1100 } ]; const CITIES = ["Paris", "Lyon", "Toulouse", "Bordeaux", "Lille", "Distanciel"]; const TRAINERS = ["Marc Dubois", "Sophie Levrier", "Karim Benali", "Julie Mercier", "Antoine Cazenave"]; function buildSessions() { // Build mock sessions for each catalog item with bookings driven by FESPA_DATA const data = window.FESPA_DATA; const respondents = data.respondents.filter(r => r.trainingPlan); const SESS_DAYS = [ "2026-06-12", "2026-06-26", "2026-07-08", "2026-09-15", "2026-09-29", "2026-10-13", "2026-10-27", "2026-11-10", "2026-11-24", "2026-12-08" ]; let _r = 12345; const rng = () => { _r = (_r * 1664525 + 1013904223) >>> 0; return _r / 4294967296; }; const pick = (a) => a[Math.floor(rng() * a.length)]; const sessions = []; let id = 1; TRAINING_CATALOG.forEach((mod, mi) => { // 2 sessions per module [0, 1].forEach(s => { const date = SESS_DAYS[(mi * 2 + s) % SESS_DAYS.length]; const city = pick(CITIES); const trainer = pick(TRAINERS); const capacity = 12 + Math.floor(rng() * 8); // 12-19 sessions.push({ id: `S${String(id).padStart(3,"0")}`, moduleCode: mod.code, moduleTitle: mod.title, date, city, format: city === "Distanciel" ? "Distanciel" : "Présentiel", trainer, capacity, enrolled: [], // filled below status: "open" }); id++; }); }); // Build enrollments — every respondent with intent matches into modules based on themes let enrId = 1; respondents.forEach(r => { Object.entries(r.trainingPlan || {}).forEach(([service, plan]) => { const h = Number(plan.headcount) || 0; if (h <= 0) return; (plan.themes || []).forEach(t => { // find the catalog item whose title best matches the theme const mod = TRAINING_CATALOG.find(c => themeMatchesModule(t, c.title)); if (!mod) return; const candidateSessions = sessions.filter(s => s.moduleCode === mod.code); if (candidateSessions.length === 0) return; // distribute h learners across both sessions, weighted toward first let remaining = h; candidateSessions.forEach((sess, si) => { if (remaining <= 0) return; const free = sess.capacity - sess.enrolled.length; if (free <= 0) return; const take = Math.min(free, si === 0 ? Math.ceil(h * 0.6) : remaining); for (let k = 0; k < take && remaining > 0; k++) { sess.enrolled.push({ id: `E${String(enrId++).padStart(4,"0")}`, company: r.company, service, respondentId: r.id, status: rng() < 0.7 ? "confirmed" : (rng() < 0.6 ? "pending" : "waiting"), registeredAt: new Date(2026, 4, 1 + Math.floor(rng() * 22)) }); remaining--; } }); }); }); }); // Mark sessions as full sessions.forEach(s => { if (s.enrolled.length >= s.capacity) s.status = "full"; else if (s.enrolled.length >= s.capacity * 0.8) s.status = "almost_full"; }); return { sessions, catalog: TRAINING_CATALOG }; } function themeMatchesModule(theme, modTitle) { const t = theme.toLowerCase(); const m = modTitle.toLowerCase(); if (t.includes("prise en main") && m.includes("prise en main")) return true; if (t.includes("création graphique") && m.includes("création")) return true; if (t.includes("marketing") && m.includes("marketing")) return true; if (t.includes("automatisation") && m.includes("automatiser")) return true; if (t.includes("juridique") && m.includes("rgpd")) return true; if (t.includes("politique") && m.includes("politique")) return true; if (t.includes("relation client") && m.includes("relation client")) return true; return false; } function getTrainingData() { if (!window.__FESPA_TRAINING__) window.__FESPA_TRAINING__ = buildSessions(); return window.__FESPA_TRAINING__; } function formatSessionDate(iso) { const d = new Date(iso); return d.toLocaleDateString("fr-FR", { day: "2-digit", month: "short", year: "numeric" }); } /* ---------- Sessions overview ---------- */ function AdminTrainingSessions({ data }) { const { sessions, catalog } = getTrainingData(); const totals = { sessions: sessions.length, seatsTotal: sessions.reduce((s, x) => s + x.capacity, 0), seatsTaken: sessions.reduce((s, x) => s + x.enrolled.length, 0), full: sessions.filter(s => s.status === "full").length, confirmed: sessions.reduce((s, x) => s + x.enrolled.filter(e => e.status === "confirmed").length, 0) }; const fillRate = totals.seatsTaken / Math.max(totals.seatsTotal, 1); return (
Sessions de formation

Calendrier 2026 — sessions ouvertes

● Inscriptions ouvertes
Taux de remplissage global {Math.round(fillRate*100)}%

{totals.seatsTaken} apprenants inscrits sur {totals.seatsTotal} places ouvertes.

Objectif comité : 75 % de remplissage.

Sessions ouvertes
{totals.sessions}
{totals.full} complètes · {sessions.filter(s=>s.status==="almost_full").length} bientôt pleines
Inscrits confirmés
{totals.confirmed}
sur {totals.seatsTaken} pré-inscrits
Modules au catalogue
{catalog.length}
2 sessions ouvertes par module
{sessions .sort((a,b) => new Date(a.date) - new Date(b.date)) .map(s => ( ))}
CodeModuleDateLieu FormateurPlacesRemplissageStatut
{s.moduleCode}
{s.id}
{s.moduleTitle} {formatSessionDate(s.date)} {s.city} {s.trainer} {s.enrolled.length} / {s.capacity}
); } function SessionStatus({ status }) { const map = { open: ["var(--success)", "Ouverte"], almost_full: ["var(--byi-orange-500)", "Bientôt pleine"], full: ["var(--byi-coral-500)", "Complète"], closed: ["var(--fg-muted)", "Clôturée"] }; const [c, l] = map[status] || map.open; return {l}; } /* ---------- Enrollments list ---------- */ function AdminTrainingEnrollments({ data }) { const { sessions } = getTrainingData(); const [search, setSearch] = useStateC(""); const [filter, setFilter] = useStateC("all"); const all = []; sessions.forEach(s => { s.enrolled.forEach(e => { all.push({ ...e, sessionId: s.id, moduleCode: s.moduleCode, moduleTitle: s.moduleTitle, date: s.date, city: s.city, trainer: s.trainer }); }); }); const rows = all.filter(e => { const matchSearch = !search || [e.company, e.moduleTitle, e.moduleCode, e.service].join(" ").toLowerCase().includes(search.toLowerCase()); const matchFilter = filter === "all" || e.status === filter; return matchSearch && matchFilter; }); return (
Inscrits aux formations

{all.length} apprenants pré-inscrits

setSearch(e.target.value)} />
{[ ["all", `Tous (${all.length})`], ["confirmed", `Confirmés (${all.filter(e=>e.status==="confirmed").length})`], ["pending", `En attente (${all.filter(e=>e.status==="pending").length})`], ["waiting", `Liste d'attente (${all.filter(e=>e.status==="waiting").length})`] ].map(([id, label]) => ( ))}
{rows.slice(0, 80).map(e => ( ))}
Inscrit leApprenant / EntrepriseService ModuleSessionLieuStatut
{new Date(e.registeredAt).toLocaleDateString("fr-FR")} {e.company}
Réf. répondant {e.respondentId}
{e.service} {e.moduleCode}
{e.moduleTitle}
{formatSessionDate(e.date)} {e.city}
); } function EnrollStatus({ status }) { const map = { confirmed: ["var(--success)", "Confirmé"], pending: ["var(--byi-orange-500)", "En attente"], waiting: ["var(--byi-yellow-500)", "Liste d'attente"], cancelled: ["var(--byi-coral-500)", "Désisté"] }; const [c, l] = map[status] || map.pending; return {l}; } /* ---------- Catalog ---------- */ function AdminTrainingCatalog({ data }) { const { sessions, catalog } = getTrainingData(); // Match with enquête data: count number of intent learners per module const respondents = data.respondents.filter(r => r.trainingPlan); const moduleIntent = catalog.map(mod => { let learners = 0; let companies = new Set(); respondents.forEach(r => { Object.values(r.trainingPlan || {}).forEach(plan => { const h = Number(plan.headcount) || 0; if (h <= 0) return; if ((plan.themes || []).some(t => themeMatchesModule(t, mod.title))) { learners += h; companies.add(r.company); } }); }); const modSessions = sessions.filter(s => s.moduleCode === mod.code); const enrolled = modSessions.reduce((s, x) => s + x.enrolled.length, 0); const seats = modSessions.reduce((s, x) => s + x.capacity, 0); return { ...mod, learners, companies: companies.size, enrolled, seats, sessions: modSessions.length }; }); return (
Catalogue des modules

{catalog.length} modules de formation IA

{moduleIntent.map(m => (
{m.code}

{m.title}

{m.level}
Durée{m.duration}h
Prix HT / pers.{m.price} €
Sessions{m.sessions}
Places{m.enrolled}/{m.seats}
Intentions issues de l'enquête
{m.learners} apprenants · {m.companies} entreprises

Couverture inscription : {Math.round(m.enrolled / Math.max(m.learners,1) * 100)}% des intentions

))}
); } /* ============================================================ PLANNING — Gantt + jalons ============================================================ */ const PLANNING_PHASES = [ { id: "P1", label: "Diffusion de l'enquête", owner: "Comm. FESPA France", start: "2026-05-05", end: "2026-06-05", status: "in_progress", color: "var(--byi-cyan-500)" }, { id: "P2", label: "Relances & consolidation", owner: "Comm. IA", start: "2026-05-26", end: "2026-06-12", status: "in_progress", color: "var(--byi-orange-500)" }, { id: "P3", label: "Analyse des résultats", owner: "BoostYourIA", start: "2026-06-08", end: "2026-06-26", status: "todo", color: "var(--byi-purple-500)" }, { id: "P4", label: "Conception du programme", owner: "BoostYourIA · FESPA France", start: "2026-06-22", end: "2026-07-17", status: "todo", color: "var(--byi-teal-500)" }, { id: "P5", label: "Validation OPCO & financement", owner: "FESPA France / OPCO", start: "2026-07-13", end: "2026-08-07", status: "todo", color: "var(--byi-navy-600)" }, { id: "P6", label: "Communication aux adhérents", owner: "Comm. FESPA France", start: "2026-08-25", end: "2026-09-11", status: "todo", color: "var(--byi-coral-500)" }, { id: "P7", label: "Inscriptions", owner: "FESPA France", start: "2026-09-15", end: "2026-10-31", status: "todo", color: "var(--byi-orange-500)" }, { id: "P8", label: "Sessions de formation", owner: "Formateurs", start: "2026-09-22", end: "2026-12-18", status: "todo", color: "var(--success)" }, { id: "P9", label: "Bilan & retour aux adhérents", owner: "Comm. IA", start: "2026-12-15", end: "2027-01-30", status: "todo", color: "var(--byi-navy-800)" } ]; const PLANNING_MILESTONES = [ { date: "2026-05-23", label: "Aujourd'hui", tone: "now" }, { date: "2026-06-12", label: "Clôture de l'enquête", tone: "key" }, { date: "2026-06-26", label: "Restitution intermédiaire — Comité FESPA France", tone: "key" }, { date: "2026-07-17", label: "Programme de formation arrêté", tone: "key" }, { date: "2026-08-07", label: "Accords OPCO obtenus", tone: "key" }, { date: "2026-09-15", label: "Ouverture des inscriptions", tone: "main" }, { date: "2026-09-22", label: "Démarrage des premières sessions", tone: "main" }, { date: "2026-12-18", label: "Dernière session de l'année", tone: "key" }, { date: "2027-01-30", label: "Restitution finale et lancement vague 2", tone: "main" } ]; function AdminPlanningGantt() { // Compute the timeline range const startBound = new Date("2026-05-01"); const endBound = new Date("2027-02-15"); const totalDays = (endBound - startBound) / (1000 * 60 * 60 * 24); // Months for header const months = []; let cur = new Date(startBound); while (cur < endBound) { months.push(new Date(cur)); cur = new Date(cur.getFullYear(), cur.getMonth() + 1, 1); } const today = new Date("2026-05-23"); const todayPct = ((today - startBound) / (1000 * 60 * 60 * 24)) / totalDays * 100; const pct = (date) => ((new Date(date) - startBound) / (1000 * 60 * 60 * 24)) / totalDays * 100; return (
Rétro-planning

Calendrier global Commission IA 2026

● En cours
{[ ["Diffusion / comm.", "var(--byi-cyan-500)"], ["Analyse & contenu", "var(--byi-purple-500)"], ["Financement OPCO", "var(--byi-navy-600)"], ["Inscriptions", "var(--byi-orange-500)"], ["Sessions", "var(--success)"] ].map(([l, c]) => (
{l}
))}
Phase
{months.map((m, i) => { const next = i+1 < months.length ? months[i+1] : endBound; const left = ((m - startBound) / (1000*60*60*24)) / totalDays * 100; const width = ((next - m) / (1000*60*60*24)) / totalDays * 100; return (
{m.toLocaleDateString("fr-FR", { month: "short", year: "2-digit" })}
); })}
{PLANNING_PHASES.map(p => { const left = pct(p.start); const right = pct(p.end); const width = right - left; return (
{p.label}
{p.owner}
{new Date(p.start).toLocaleDateString("fr-FR", { day: "2-digit", month: "short" })} {" → "} {new Date(p.end).toLocaleDateString("fr-FR", { day: "2-digit", month: "short" })}
); })}
Aujourd'hui · 23 mai
); } function AdminPlanningMilestones() { return (
Jalons clés

Les dates à ne pas manquer

    {PLANNING_MILESTONES.map((m, i) => { const d = new Date(m.date); return (
  1. {d.toLocaleDateString("fr-FR", { day: "2-digit" })} {d.toLocaleDateString("fr-FR", { month: "short" })} {d.getFullYear()}
    {m.label}
    {m.tone === "now" ? "Position actuelle" : m.tone === "main" ? "Jalon majeur" : "Jalon"}
  2. ); })}
); } Object.assign(window, { AdminTrainingSessions, AdminTrainingEnrollments, AdminTrainingCatalog, AdminPlanningGantt, AdminPlanningMilestones });