89b4107266
CI - Web (Laravel) / quality-and-tests (pull_request) Failing after 4m4s
- docs/Dossier_Deploiement_Securisation_CESIZen.docx (15-20 pages) - presentation_bloc3.js -> CESIZen_Presentation_Bloc3.pptx (10 slides) - diagrammes architecture et CI/CD (docs/img) - CHANGELOG.md (versionnage semantique) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
863 lines
28 KiB
JavaScript
863 lines
28 KiB
JavaScript
const pptxgen = require("pptxgenjs");
|
|
|
|
const pres = new pptxgen();
|
|
pres.layout = "LAYOUT_16x9"; // 10" x 5.625"
|
|
pres.author = "Sam DEPARDIEU";
|
|
pres.title = "CESIZen — Présentation CDA CESI";
|
|
|
|
// ─── Palette (strictement calquée sur la réf) ───────────────
|
|
const C = {
|
|
darkBg: "0D1B2A",
|
|
lightBg: "F7F7F2",
|
|
teal: "00BF63",
|
|
yellow: "FFDE59",
|
|
dark: "2D2D2D",
|
|
navy: "1B2838",
|
|
white: "FFFFFF",
|
|
gray: "6B7280",
|
|
border: "E5E7EB",
|
|
offWhiteAlt: "F0F0EC",
|
|
};
|
|
|
|
// ─── Helpers ────────────────────────────────────────────────
|
|
|
|
function topBar(s) {
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 0, y: 0, w: 10, h: 0.09,
|
|
fill: { color: C.teal }, line: { color: C.teal },
|
|
});
|
|
}
|
|
|
|
function vLine(s, x, y, h, color) {
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x, y, w: 0.07, h,
|
|
fill: { color: color || C.teal },
|
|
line: { color: color || C.teal },
|
|
});
|
|
}
|
|
|
|
function hLine(s, x, y, w, color) {
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x, y, w, h: 0.02,
|
|
fill: { color: color || C.border },
|
|
line: { color: color || C.border },
|
|
});
|
|
}
|
|
|
|
function tealBar(s, x, y, w) {
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x, y, w, h: 0.07,
|
|
fill: { color: C.teal }, line: { color: C.teal },
|
|
});
|
|
}
|
|
|
|
// Entête standard (slides claires)
|
|
function header(s, num, section, title) {
|
|
topBar(s);
|
|
// numéro de slide
|
|
s.addText(String(num).padStart(2, "0"), {
|
|
x: 9.0, y: 0.11, w: 0.82, h: 0.38,
|
|
fontSize: 14, color: C.teal, fontFace: "Calibri",
|
|
bold: true, align: "right", margin: 0,
|
|
});
|
|
// section label
|
|
s.addText(section.toUpperCase(), {
|
|
x: 0.65, y: 0.13, w: 7, h: 0.26,
|
|
fontSize: 9, color: C.gray, fontFace: "Calibri",
|
|
charSpacing: 2, margin: 0,
|
|
});
|
|
// ligne jaune accent
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 0.65, y: 0.44, w: 0.55, h: 0.07,
|
|
fill: { color: C.yellow }, line: { color: C.yellow },
|
|
});
|
|
// titre
|
|
s.addText(title, {
|
|
x: 0.65, y: 0.52, w: 8.7, h: 0.7,
|
|
fontSize: 26, color: C.darkBg, fontFace: "Calibri",
|
|
bold: false, margin: 0,
|
|
});
|
|
hLine(s, 0.65, 1.25, 8.8);
|
|
}
|
|
|
|
// Entête slides sombres
|
|
function darkHeader(s, num, section, title) {
|
|
topBar(s);
|
|
s.addText(String(num).padStart(2, "0"), {
|
|
x: 9.0, y: 0.11, w: 0.82, h: 0.38,
|
|
fontSize: 14, color: C.teal, fontFace: "Calibri",
|
|
bold: true, align: "right", margin: 0,
|
|
});
|
|
s.addText(section.toUpperCase(), {
|
|
x: 0.65, y: 0.13, w: 7, h: 0.26,
|
|
fontSize: 9, color: "4B5563", fontFace: "Calibri",
|
|
charSpacing: 2, margin: 0,
|
|
});
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 0.65, y: 0.44, w: 0.55, h: 0.07,
|
|
fill: { color: C.yellow }, line: { color: C.yellow },
|
|
});
|
|
s.addText(title, {
|
|
x: 0.65, y: 0.52, w: 8.7, h: 0.7,
|
|
fontSize: 26, color: C.white, fontFace: "Calibri",
|
|
bold: false, margin: 0,
|
|
});
|
|
hLine(s, 0.65, 1.25, 8.8, "374151");
|
|
}
|
|
|
|
// ─── SLIDE 01 — Couverture ──────────────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.darkBg };
|
|
topBar(s);
|
|
|
|
// Accent jaune
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 0.7, y: 0.72, w: 0.55, h: 0.09,
|
|
fill: { color: C.yellow }, line: { color: C.yellow },
|
|
});
|
|
|
|
// Titre principal
|
|
s.addText("CESIZEN", {
|
|
x: 0.7, y: 0.83, w: 8.5, h: 1.45,
|
|
fontSize: 56, bold: false, color: C.white,
|
|
fontFace: "Calibri", margin: 0,
|
|
});
|
|
|
|
// Sous-titre
|
|
s.addText("L'application de votre santé mentale", {
|
|
x: 0.7, y: 2.35, w: 8.5, h: 0.5,
|
|
fontSize: 18, color: C.teal, fontFace: "Calibri",
|
|
italic: false, margin: 0,
|
|
});
|
|
|
|
hLine(s, 0.7, 2.98, 8.6, "374151");
|
|
|
|
s.addText("Projet CDA CESI — BLOC 2 · Concevoir les solutions logicielles", {
|
|
x: 0.7, y: 3.1, w: 8.5, h: 0.38,
|
|
fontSize: 12, color: "D1D5DB", fontFace: "Calibri", margin: 0,
|
|
});
|
|
s.addText("Sam DEPARDIEU", {
|
|
x: 0.7, y: 3.52, w: 5, h: 0.38,
|
|
fontSize: 13, color: C.yellow, fontFace: "Calibri", margin: 0,
|
|
});
|
|
s.addText("Mai 2026", {
|
|
x: 0.7, y: 3.92, w: 4, h: 0.35,
|
|
fontSize: 12, color: C.gray, fontFace: "Calibri", margin: 0,
|
|
});
|
|
|
|
// Panel droit (teal)
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 6.95, y: 0.09, w: 3.05, h: 5.535,
|
|
fill: { color: C.teal }, line: { color: C.teal },
|
|
});
|
|
s.addText("Périmètre", {
|
|
x: 7.1, y: 0.28, w: 2.75, h: 0.44,
|
|
fontSize: 13, bold: true, color: C.white, fontFace: "Calibri", margin: 0,
|
|
});
|
|
hLine(s, 7.1, 0.74, 2.7, "00995A");
|
|
|
|
[
|
|
{ label: "WEB", teal: false },
|
|
{ label: "Mobile (iOS & Android)",teal: false },
|
|
{ label: "API REST", teal: false },
|
|
{ label: "Admin dashboard", teal: false },
|
|
{ label: "⌚ Surprise...", teal: true },
|
|
].forEach((item, i) => {
|
|
vLine(s, 7.1, 0.92 + i * 0.82, 0.6, item.teal ? C.yellow : C.white);
|
|
s.addText(item.label, {
|
|
x: 7.28, y: 1.0 + i * 0.82, w: 2.55, h: 0.44,
|
|
fontSize: 12, color: item.teal ? C.yellow : C.white,
|
|
fontFace: "Calibri", italic: item.teal, margin: 0,
|
|
});
|
|
});
|
|
}
|
|
|
|
// ─── SLIDE 02 — Sommaire ────────────────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.lightBg };
|
|
topBar(s);
|
|
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 0.65, y: 0.37, w: 0.55, h: 0.09,
|
|
fill: { color: C.yellow }, line: { color: C.yellow },
|
|
});
|
|
s.addText("SOMMAIRE", {
|
|
x: 0.65, y: 0.47, w: 8, h: 0.88,
|
|
fontSize: 34, color: C.darkBg, fontFace: "Calibri", bold: false, margin: 0,
|
|
});
|
|
hLine(s, 0.65, 1.38, 8.8);
|
|
|
|
const toc = [
|
|
"Contexte & objectifs",
|
|
"L'écosystème CESIZen",
|
|
"Plateforme WEB (Laravel 12)",
|
|
"Application Mobile (Flutter)",
|
|
"Architecture technique",
|
|
"Modèle de données",
|
|
"Tests, sécurité & validation",
|
|
"Surprise",
|
|
"Bilan & livrables",
|
|
];
|
|
|
|
toc.forEach((item, i) => {
|
|
s.addText(String(i + 1).padStart(2, "0"), {
|
|
x: 0.65, y: 1.5 + i * 0.45, w: 0.65, h: 0.38,
|
|
fontSize: 16, color: C.teal, fontFace: "Calibri", bold: false, margin: 0,
|
|
});
|
|
s.addText(item, {
|
|
x: 1.38, y: 1.53 + i * 0.45, w: 5.0, h: 0.36,
|
|
fontSize: 13.5, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
// Panel teal droit
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 6.95, y: 0.09, w: 3.05, h: 5.535,
|
|
fill: { color: C.teal }, line: { color: C.teal },
|
|
});
|
|
s.addText("3 applications", {
|
|
x: 7.1, y: 0.3, w: 2.75, h: 0.44,
|
|
fontSize: 13, bold: true, color: C.white, fontFace: "Calibri", margin: 0,
|
|
});
|
|
["WEB", "Mobile", "Wear OS"].forEach((item, i) => {
|
|
s.addText("— " + item, {
|
|
x: 7.1, y: 0.88 + i * 0.5, w: 2.75, h: 0.4,
|
|
fontSize: 12, color: C.white, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
hLine(s, 7.1, 2.62, 2.7, "00995A");
|
|
|
|
s.addText("Évaluation", {
|
|
x: 7.1, y: 2.75, w: 2.75, h: 0.38,
|
|
fontSize: 12, bold: true, color: C.white, fontFace: "Calibri", margin: 0,
|
|
});
|
|
[
|
|
"20 min de présentation",
|
|
"Rapport 15-20 pages",
|
|
"30 pts — Grille A/B/C/D",
|
|
].forEach((line, i) => {
|
|
s.addText(line, {
|
|
x: 7.1, y: 3.2 + i * 0.45, w: 2.75, h: 0.38,
|
|
fontSize: 11, color: "D1D5DB", fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
}
|
|
|
|
// ─── SLIDE 03 — Contexte ────────────────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.lightBg };
|
|
header(s, "03", "Contexte du projet", "Contexte & objectifs");
|
|
|
|
// Bloc gauche : description
|
|
vLine(s, 0.55, 1.38, 3.75);
|
|
s.addText(
|
|
"Commanditaire : Ministère de la Santé et de la Prévention\n" +
|
|
"\n" +
|
|
"Application dédiée à la gestion du stress et du bien-être mental pour le grand public.\n" +
|
|
"\n" +
|
|
"Durée du projet : 3 mois de développement individuel\n" +
|
|
"\n" +
|
|
"Conformité RGPD & protection des données personnelles exigées.",
|
|
{
|
|
x: 0.78, y: 1.4, w: 5.2, h: 3.7,
|
|
fontSize: 12.5, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
}
|
|
);
|
|
|
|
// Bloc droit : fonctionnalités
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 6.3, y: 1.38, w: 3.45, h: 4.0,
|
|
fill: { color: C.white }, line: { color: C.border, width: 1 },
|
|
});
|
|
tealBar(s, 6.3, 1.38, 3.45);
|
|
|
|
s.addText("Fonctionnalités requises", {
|
|
x: 6.45, y: 1.55, w: 3.15, h: 0.38,
|
|
fontSize: 11.5, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
hLine(s, 6.35, 1.94, 3.3);
|
|
|
|
s.addText("OBLIGATOIRES", {
|
|
x: 6.45, y: 2.02, w: 3.15, h: 0.28,
|
|
fontSize: 9, bold: true, color: C.teal, fontFace: "Calibri",
|
|
charSpacing: 1, margin: 0,
|
|
});
|
|
[
|
|
"Questionnaire de stress",
|
|
"Exercices de respiration",
|
|
"Tracker d'émotions",
|
|
].forEach((item, i) => {
|
|
s.addText("— " + item, {
|
|
x: 6.5, y: 2.34 + i * 0.38, w: 3.05, h: 0.33,
|
|
fontSize: 11.5, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
hLine(s, 6.35, 3.5, 3.3);
|
|
|
|
s.addText("OPTIONNEL", {
|
|
x: 6.45, y: 3.58, w: 3.15, h: 0.28,
|
|
fontSize: 9, bold: true, color: C.teal, fontFace: "Calibri",
|
|
charSpacing: 1, margin: 0,
|
|
});
|
|
s.addText("— Module complémentaire", {
|
|
x: 6.5, y: 3.9, w: 3.05, h: 0.33,
|
|
fontSize: 11.5, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
|
|
// Barre bas
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 0.55, y: 5.22, w: 9.2, h: 0.52,
|
|
fill: { color: C.darkBg }, line: { color: C.darkBg },
|
|
});
|
|
s.addText(
|
|
"Rapport écrit (15-20 pages) · Présentation orale 20 min · Notation sur 30 points",
|
|
{
|
|
x: 0.7, y: 5.24, w: 9.0, h: 0.46,
|
|
fontSize: 11, color: "D1D5DB", align: "center",
|
|
fontFace: "Calibri", margin: 0, valign: "middle",
|
|
}
|
|
);
|
|
}
|
|
|
|
// ─── SLIDE 04 — L'écosystème ────────────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.lightBg };
|
|
header(s, "04", "Périmètre", "L'écosystème CESIZen");
|
|
|
|
// 3 colonnes
|
|
const cols = [
|
|
{
|
|
title: "WEB", sub: "Plateforme & API",
|
|
items: ["Laravel 12 — MVC", "FilamentPHP (admin)", "API REST Sanctum", "Tailwind CSS + DSFR", "Docker + Nginx"],
|
|
mystery: false,
|
|
},
|
|
{
|
|
title: "Mobile", sub: "iOS & Android",
|
|
items: ["Flutter (Dart)", "7 modules fonctionnels", "Provider + GoRouter", "DIO + Secure Storage", "Synchronisation montre"],
|
|
mystery: false,
|
|
},
|
|
{
|
|
title: "???", sub: "À venir...",
|
|
items: ["?", "?", "?", "?", "?"],
|
|
mystery: true,
|
|
},
|
|
];
|
|
|
|
const cW = 2.9;
|
|
cols.forEach((col, i) => {
|
|
const cx = 0.55 + i * (cW + 0.2);
|
|
|
|
tealBar(s, cx, 1.38, cW);
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: cx, y: 1.45, w: cW, h: 3.95,
|
|
fill: { color: col.mystery ? C.lightBg : C.white },
|
|
line: { color: col.mystery ? "D1D5DB" : C.border, width: 1 },
|
|
});
|
|
|
|
s.addText(col.title, {
|
|
x: cx + 0.15, y: 1.55, w: cW - 0.3, h: 0.5,
|
|
fontSize: 20, bold: true,
|
|
color: col.mystery ? "9CA3AF" : C.darkBg,
|
|
fontFace: "Calibri", margin: 0,
|
|
});
|
|
s.addText(col.sub, {
|
|
x: cx + 0.15, y: 2.05, w: cW - 0.3, h: 0.3,
|
|
fontSize: 11, color: col.mystery ? "9CA3AF" : C.teal,
|
|
fontFace: "Calibri", italic: col.mystery, margin: 0,
|
|
});
|
|
hLine(s, cx + 0.1, 2.4, cW - 0.2);
|
|
|
|
col.items.forEach((item, ii) => {
|
|
s.addText(col.mystery ? "· · · · ·" : "— " + item, {
|
|
x: cx + 0.15, y: 2.5 + ii * 0.47, w: cW - 0.3, h: 0.4,
|
|
fontSize: 11.5, color: col.mystery ? "D1D5DB" : C.dark,
|
|
fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
s.addText(
|
|
"Les 3 composantes communiquent via l'API REST — Laravel Sanctum + HTTPS",
|
|
{
|
|
x: 0.65, y: 5.3, w: 9.0, h: 0.24,
|
|
fontSize: 10, color: C.gray, align: "center",
|
|
fontFace: "Calibri", margin: 0,
|
|
}
|
|
);
|
|
}
|
|
|
|
// ─── SLIDE 05 — Plateforme WEB ──────────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.lightBg };
|
|
header(s, "05", "Plateforme WEB", "Laravel 12 — Architecture & fonctionnalités");
|
|
|
|
// Gauche : stack
|
|
tealBar(s, 0.55, 1.38, 4.45);
|
|
s.addText("Stack technique", {
|
|
x: 0.55, y: 1.52, w: 4.45, h: 0.38,
|
|
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
hLine(s, 0.55, 1.92, 4.45);
|
|
|
|
[
|
|
["Framework", "Laravel 12 — PHP 8.2"],
|
|
["Admin", "FilamentPHP 3.2"],
|
|
["Auth API", "Laravel Sanctum"],
|
|
["Frontend", "Tailwind CSS 4 + Alpine.js"],
|
|
["Charte UI", "DSFR (Design Système État)"],
|
|
["Base de données", "MySQL 8.0"],
|
|
["Déploiement", "Docker + Nginx (Alpine)"],
|
|
].forEach(([label, val], i) => {
|
|
const ry = 2.0 + i * 0.47;
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 0.55, y: ry, w: 4.45, h: 0.44,
|
|
fill: { color: i % 2 === 0 ? C.white : C.offWhiteAlt },
|
|
line: { color: C.border, width: 0 },
|
|
});
|
|
s.addText(label, {
|
|
x: 0.7, y: ry + 0.06, w: 1.5, h: 0.3,
|
|
fontSize: 11, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
s.addText(val, {
|
|
x: 2.25, y: ry + 0.06, w: 2.7, h: 0.3,
|
|
fontSize: 11, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
// Droite : fonctionnalités
|
|
tealBar(s, 5.3, 1.38, 4.4);
|
|
s.addText("Fonctionnalités", {
|
|
x: 5.3, y: 1.52, w: 4.4, h: 0.38,
|
|
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
hLine(s, 5.3, 1.92, 4.4);
|
|
|
|
[
|
|
"Authentification sécurisée (Sanctum)",
|
|
"Dashboard de visualisation du stress",
|
|
"Exercices de respiration (3 modes DSFR)",
|
|
"Journal des émotions quotidien",
|
|
"Questionnaire diagnostique de stress",
|
|
"Articles & informations santé",
|
|
"Panel admin — CRUD complet",
|
|
].forEach((item, i) => {
|
|
vLine(s, 5.3, 2.06 + i * 0.48, 0.36);
|
|
s.addText(item, {
|
|
x: 5.52, y: 2.09 + i * 0.48, w: 4.1, h: 0.36,
|
|
fontSize: 12, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
}
|
|
|
|
// ─── SLIDE 06 — Application Mobile ─────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.lightBg };
|
|
header(s, "06", "Application Mobile", "Flutter — 7 modules fonctionnels");
|
|
|
|
const modules = [
|
|
{ n: "01", title: "Authentification", sub: "Token persistant (Secure Storage)" },
|
|
{ n: "02", title: "Tracker émotionnel", sub: "5 niveaux · Journal quotidien" },
|
|
{ n: "03", title: "Diagnostic stress", sub: "Questionnaire + historique des résultats" },
|
|
{ n: "04", title: "Respiration guidée", sub: "Animation 60 FPS · Cohérence cardiaque" },
|
|
{ n: "05", title: "Relaxation", sub: "Bibliothèque d'activités filtrables" },
|
|
{ n: "06", title: "Informations santé", sub: "Articles & conseils bien-être" },
|
|
{ n: "07", title: "Sync montre", sub: "Wearable Data Layer (bonus)", bonus: true },
|
|
];
|
|
|
|
// 4 gauche + 3 droite
|
|
modules.slice(0, 4).forEach((m, i) => {
|
|
const my = 1.38 + i * 0.99;
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 0.55, y: my, w: 4.45, h: 0.88,
|
|
fill: { color: C.white }, line: { color: C.border, width: 1 },
|
|
});
|
|
vLine(s, 0.55, my, 0.88);
|
|
s.addText(m.n, {
|
|
x: 0.72, y: my + 0.07, w: 0.55, h: 0.35,
|
|
fontSize: 15, color: C.teal, fontFace: "Calibri", bold: false, margin: 0,
|
|
});
|
|
s.addText(m.title, {
|
|
x: 1.3, y: my + 0.09, w: 3.5, h: 0.35,
|
|
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
s.addText(m.sub, {
|
|
x: 1.3, y: my + 0.49, w: 3.5, h: 0.3,
|
|
fontSize: 10.5, color: C.gray, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
modules.slice(4).forEach((m, i) => {
|
|
const my = 1.38 + i * 0.99;
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 5.25, y: my, w: 4.45, h: 0.88,
|
|
fill: { color: m.bonus ? "F7F3E0" : C.white },
|
|
line: { color: m.bonus ? "E8DFA0" : C.border, width: 1 },
|
|
});
|
|
vLine(s, 5.25, my, 0.88, m.bonus ? C.yellow : C.teal);
|
|
s.addText(m.n, {
|
|
x: 5.42, y: my + 0.07, w: 0.55, h: 0.35,
|
|
fontSize: 15, color: m.bonus ? C.yellow : C.teal,
|
|
fontFace: "Calibri", bold: false, margin: 0,
|
|
});
|
|
s.addText(m.title, {
|
|
x: 6.0, y: my + 0.09, w: 3.5, h: 0.35,
|
|
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
s.addText(m.sub, {
|
|
x: 6.0, y: my + 0.49, w: 3.5, h: 0.3,
|
|
fontSize: 10.5, color: m.bonus ? C.teal : C.gray,
|
|
fontFace: "Calibri", italic: m.bonus, margin: 0,
|
|
});
|
|
});
|
|
}
|
|
|
|
// ─── SLIDE 07 — Architecture technique ─────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.lightBg };
|
|
header(s, "07", "Architecture technique", "Architecture 3-tiers & choix techniques");
|
|
|
|
// 3 colonnes
|
|
const tiers = [
|
|
{ name: "Clients", items: ["Navigateur WEB", "App Mobile Flutter", "Wear OS (Kotlin)"] },
|
|
{ name: "Serveur Docker", items: ["Nginx (reverse proxy)", "Laravel 12 / PHP 8.2", "Sanctum (auth tokens)", "FilamentPHP (admin)"] },
|
|
{ name: "Données", items: ["MySQL 8.0", "12 modèles Eloquent", "Migrations + Seeders"] },
|
|
];
|
|
const widths = [2.55, 3.55, 2.95];
|
|
|
|
let cx = 0.5;
|
|
tiers.forEach((tier, i) => {
|
|
const tw = widths[i];
|
|
tealBar(s, cx, 1.38, tw);
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: cx, y: 1.45, w: tw, h: 3.85,
|
|
fill: { color: C.white }, line: { color: C.border, width: 1 },
|
|
});
|
|
s.addText(tier.name, {
|
|
x: cx + 0.15, y: 1.55, w: tw - 0.3, h: 0.42,
|
|
fontSize: 14, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
hLine(s, cx + 0.1, 1.98, tw - 0.2);
|
|
|
|
tier.items.forEach((item, ii) => {
|
|
s.addText("— " + item, {
|
|
x: cx + 0.15, y: 2.08 + ii * 0.62, w: tw - 0.3, h: 0.52,
|
|
fontSize: 12, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
if (i < tiers.length - 1) {
|
|
s.addText("→", {
|
|
x: cx + tw + 0.04, y: 2.75, w: 0.2, h: 0.38,
|
|
fontSize: 15, color: C.teal, fontFace: "Calibri", align: "center", margin: 0,
|
|
});
|
|
}
|
|
cx += tw + 0.22;
|
|
});
|
|
|
|
// Note bas
|
|
s.addText(
|
|
"Pattern MVC · API RESTful · Merise / UML · Ports : 8090 (WEB) · 8091 (API)",
|
|
{
|
|
x: 0.65, y: 5.33, w: 9.0, h: 0.22,
|
|
fontSize: 9.5, color: C.gray, align: "center",
|
|
fontFace: "Calibri", margin: 0,
|
|
}
|
|
);
|
|
}
|
|
|
|
// ─── SLIDE 08 — Modèle de données ──────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.lightBg };
|
|
header(s, "08", "Modèle de données", "12 entités — Modèle Logique de Données");
|
|
|
|
const groupsLeft = [
|
|
{ domain: "Utilisateurs", models: ["User", "Role"] },
|
|
{ domain: "Suivi émotionnel", models: ["EmotionRecord", "StressEvent", "EvenementStress"] },
|
|
{ domain: "Diagnostics", models: ["ResultatDiag", "DetailResultat"] },
|
|
];
|
|
const groupsRight = [
|
|
{ domain: "Activités & Relaxation", models: ["RelaxationActivity", "Activite", "Categorie", "Favorite"] },
|
|
{ domain: "Informations santé", models: ["Information"] },
|
|
];
|
|
|
|
let y = 1.38;
|
|
groupsLeft.forEach((g) => {
|
|
const h = 0.44 + g.models.length * 0.36;
|
|
vLine(s, 0.55, y, h);
|
|
s.addText(g.domain, {
|
|
x: 0.77, y: y + 0.05, w: 4.1, h: 0.32,
|
|
fontSize: 12, bold: true, color: C.teal, fontFace: "Calibri", margin: 0,
|
|
});
|
|
g.models.forEach((m, mi) => {
|
|
s.addText(m, {
|
|
x: 0.77, y: y + 0.4 + mi * 0.35, w: 4.1, h: 0.3,
|
|
fontSize: 11.5, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
y += h + 0.22;
|
|
});
|
|
|
|
let y2 = 1.38;
|
|
groupsRight.forEach((g) => {
|
|
const h = 0.44 + g.models.length * 0.36;
|
|
vLine(s, 5.25, y2, h);
|
|
s.addText(g.domain, {
|
|
x: 5.47, y: y2 + 0.05, w: 4.2, h: 0.32,
|
|
fontSize: 12, bold: true, color: C.teal, fontFace: "Calibri", margin: 0,
|
|
});
|
|
g.models.forEach((m, mi) => {
|
|
s.addText(m, {
|
|
x: 5.47, y: y2 + 0.4 + mi * 0.35, w: 4.2, h: 0.3,
|
|
fontSize: 11.5, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
y2 += h + 0.22;
|
|
});
|
|
|
|
s.addText(
|
|
"12 migrations Eloquent · Associations (hasMany, belongsTo, belongsToMany) · Seeders & Factories",
|
|
{
|
|
x: 0.65, y: 5.33, w: 9.0, h: 0.22,
|
|
fontSize: 9.5, color: C.gray, align: "center",
|
|
fontFace: "Calibri", margin: 0,
|
|
}
|
|
);
|
|
}
|
|
|
|
// ─── SLIDE 09 — Tests & Sécurité ───────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.lightBg };
|
|
header(s, "09", "Tests & Sécurité", "Validation & conformité");
|
|
|
|
// Gauche : tests
|
|
tealBar(s, 0.55, 1.38, 4.35);
|
|
s.addText("Stratégie de tests", {
|
|
x: 0.55, y: 1.52, w: 4.35, h: 0.38,
|
|
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
hLine(s, 0.55, 1.92, 4.35);
|
|
|
|
[
|
|
{ type: "Tests unitaires", tool: "PHPUnit", note: "Controllers & services" },
|
|
{ type: "Tests fonctionnels", tool: "PHPUnit", note: "Scénarios end-to-end" },
|
|
{ type: "Non-régression", tool: "PHPUnit", note: "Après chaque évolution" },
|
|
{ type: "Cahier de tests", tool: "Document", note: "Scénarios détaillés" },
|
|
{ type: "PV de recette", tool: "Document", note: "Validation finale" },
|
|
].forEach((t, i) => {
|
|
const ty = 2.02 + i * 0.62;
|
|
vLine(s, 0.55, ty, 0.52);
|
|
s.addText(t.type, {
|
|
x: 0.77, y: ty + 0.04, w: 2.5, h: 0.28,
|
|
fontSize: 12, bold: true, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
s.addText("[" + t.tool + "] " + t.note, {
|
|
x: 0.77, y: ty + 0.3, w: 4.0, h: 0.22,
|
|
fontSize: 10, color: C.gray, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
// Droite : sécurité
|
|
tealBar(s, 5.25, 1.38, 4.45);
|
|
s.addText("Sécurité & conformité", {
|
|
x: 5.25, y: 1.52, w: 4.45, h: 0.38,
|
|
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
hLine(s, 5.25, 1.92, 4.45);
|
|
|
|
[
|
|
{ label: "Authentification", val: "Laravel Sanctum — Tokens API" },
|
|
{ label: "Chiffrement", val: "Bcrypt (mots de passe)" },
|
|
{ label: "CSRF", val: "Middleware Laravel natif" },
|
|
{ label: "RGPD", val: "Protection données personnelles" },
|
|
{ label: "Stockage mobile", val: "Flutter Secure Storage" },
|
|
{ label: "Transport", val: "HTTPS obligatoire en production" },
|
|
].forEach((sec, i) => {
|
|
const ty = 2.02 + i * 0.55;
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 5.25, y: ty, w: 4.45, h: 0.48,
|
|
fill: { color: i % 2 === 0 ? C.white : C.offWhiteAlt },
|
|
line: { color: C.border, width: 0 },
|
|
});
|
|
s.addText(sec.label, {
|
|
x: 5.4, y: ty + 0.07, w: 1.7, h: 0.3,
|
|
fontSize: 11, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
s.addText(sec.val, {
|
|
x: 7.15, y: ty + 0.07, w: 2.5, h: 0.3,
|
|
fontSize: 11, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
}
|
|
|
|
// ─── SLIDE 10 — SURPRISE: Wear OS ──────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.darkBg };
|
|
darkHeader(s, "10", "Bonus", "Application Wear OS");
|
|
|
|
s.addText("Un module non demandé — développé en supplément", {
|
|
x: 0.65, y: 1.32, w: 9.1, h: 0.35,
|
|
fontSize: 13, color: C.teal, fontFace: "Calibri", italic: true, margin: 0,
|
|
});
|
|
|
|
// 4 cartes 2x2
|
|
const feats = [
|
|
{ title: "Sélection d'humeur", desc: "5 niveaux en 1 clic\nTrès bien → Stressé" },
|
|
{ title: "Synchronisation bidirect.", desc: "Temps réel via\nGoogle Wearable Data Layer" },
|
|
{ title: "Réinitialisation automatique",desc: "Reset quotidien à minuit\nSuivi journalier indépendant" },
|
|
{ title: "Connectivité", desc: "Wi-Fi + Bluetooth\nWear OS 3.0+ · API 30+" },
|
|
];
|
|
|
|
feats.forEach((f, i) => {
|
|
const col = i % 2, row = Math.floor(i / 2);
|
|
const fx = 0.6 + col * 4.85;
|
|
const fy = 1.78 + row * 1.7;
|
|
const fw = 4.55;
|
|
|
|
tealBar(s, fx, fy, fw);
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: fx, y: fy + 0.07, w: fw, h: 1.5,
|
|
fill: { color: C.navy }, line: { color: "374151", width: 1 },
|
|
});
|
|
s.addText(f.title, {
|
|
x: fx + 0.2, y: fy + 0.17, w: fw - 0.4, h: 0.42,
|
|
fontSize: 13.5, bold: true, color: C.white, fontFace: "Calibri", margin: 0,
|
|
});
|
|
s.addText(f.desc, {
|
|
x: fx + 0.2, y: fy + 0.64, w: fw - 0.4, h: 0.82,
|
|
fontSize: 12, color: "9CA3AF", fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
s.addText(
|
|
"Kotlin · Jetpack Compose for Wear OS · DataClient / MessageClient / NodeClient",
|
|
{
|
|
x: 0.65, y: 5.35, w: 9.0, h: 0.22,
|
|
fontSize: 9.5, color: "374151", align: "center",
|
|
fontFace: "Calibri", margin: 0,
|
|
}
|
|
);
|
|
}
|
|
|
|
// ─── SLIDE 11 — Bilan & Livrables ──────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.lightBg };
|
|
header(s, "11", "Bilan", "Livrables & chiffres clés");
|
|
|
|
// Gauche : checklist
|
|
tealBar(s, 0.55, 1.38, 5.55);
|
|
s.addText("Livrables réalisés", {
|
|
x: 0.55, y: 1.52, w: 5.55, h: 0.38,
|
|
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
hLine(s, 0.55, 1.92, 5.55);
|
|
|
|
[
|
|
[false, "Plateforme WEB — Laravel 12 + FilamentPHP"],
|
|
[false, "API REST complète (Sanctum + 4 controllers)"],
|
|
[false, "Application Mobile Flutter (iOS & Android)"],
|
|
[false, "Base de données — 12 modèles Eloquent"],
|
|
[false, "Dockerisation (Nginx + PHP + MySQL)"],
|
|
[false, "Cahier de tests (unitaires, fonctionnels, NR)"],
|
|
[false, "Documentation technique complète"],
|
|
[false, "Procès-Verbal de validation (PV)"],
|
|
[true, "Application Wear OS — Bonus non demandé"],
|
|
].forEach(([isBonus, text], i) => {
|
|
s.addText((isBonus ? "⭐" : "✓") + " " + text, {
|
|
x: 0.7, y: 2.02 + i * 0.36, w: 5.25, h: 0.32,
|
|
fontSize: 11.5,
|
|
color: isBonus ? C.teal : C.dark,
|
|
bold: isBonus,
|
|
fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
// Droite : chiffres
|
|
tealBar(s, 6.35, 1.38, 3.3);
|
|
s.addText("Chiffres clés", {
|
|
x: 6.35, y: 1.52, w: 3.3, h: 0.38,
|
|
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
|
|
});
|
|
hLine(s, 6.35, 1.92, 3.3);
|
|
|
|
[
|
|
{ n: "3", l: "Applications\ndéveloppées" },
|
|
{ n: "12", l: "Modèles de\nbase de données" },
|
|
{ n: "7", l: "Modules\nmobiles" },
|
|
{ n: "4", l: "Documents\nlivrés" },
|
|
].forEach((st, i) => {
|
|
const sy = 2.05 + i * 0.88;
|
|
vLine(s, 6.35, sy, 0.74);
|
|
s.addText(st.n, {
|
|
x: 6.6, y: sy + 0.02, w: 0.85, h: 0.7,
|
|
fontSize: 32, bold: false, color: C.teal, fontFace: "Calibri",
|
|
margin: 0, valign: "middle",
|
|
});
|
|
s.addText(st.l, {
|
|
x: 7.52, y: sy + 0.1, w: 2.05, h: 0.5,
|
|
fontSize: 11, color: C.dark, fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
}
|
|
|
|
// ─── SLIDE 12 — Conclusion ──────────────────────────────────
|
|
{
|
|
const s = pres.addSlide();
|
|
s.background = { color: C.darkBg };
|
|
topBar(s);
|
|
|
|
s.addShape(pres.shapes.RECTANGLE, {
|
|
x: 0.7, y: 0.73, w: 0.55, h: 0.09,
|
|
fill: { color: C.yellow }, line: { color: C.yellow },
|
|
});
|
|
|
|
s.addText("Merci.", {
|
|
x: 0.7, y: 0.84, w: 9, h: 1.45,
|
|
fontSize: 56, bold: false, color: C.white,
|
|
fontFace: "Calibri", margin: 0,
|
|
});
|
|
|
|
hLine(s, 0.7, 2.4, 8.6, "374151");
|
|
|
|
[
|
|
{ line: "WEB : Laravel 12 + FilamentPHP + API REST + Docker", col: false },
|
|
{ line: "Mobile : Flutter — 7 modules complets (iOS & Android)", col: false },
|
|
{ line: "Wear OS : synchronisation bidirectionnelle — Bonus", col: true },
|
|
].forEach((item, i) => {
|
|
vLine(s, 0.7, 2.52 + i * 0.68, 0.5, item.col ? C.yellow : C.teal);
|
|
s.addText(item.line, {
|
|
x: 0.93, y: 2.59 + i * 0.68, w: 8.8, h: 0.38,
|
|
fontSize: 13, color: item.col ? C.yellow : "D1D5DB",
|
|
fontFace: "Calibri", margin: 0,
|
|
});
|
|
});
|
|
|
|
s.addText("Questions ?", {
|
|
x: 0.7, y: 4.3, w: 9, h: 0.7,
|
|
fontSize: 30, bold: false, color: C.teal,
|
|
fontFace: "Calibri", margin: 0,
|
|
});
|
|
|
|
s.addText("CESIZen · CDA CESI BLOC 2 · Sam DEPARDIEU · Mai 2026", {
|
|
x: 0.7, y: 5.2, w: 9, h: 0.28,
|
|
fontSize: 10, color: "374151", fontFace: "Calibri", margin: 0,
|
|
});
|
|
}
|
|
|
|
// ─── Save ───────────────────────────────────────────────────
|
|
pres.writeFile({ fileName: "CESIZen_Presentation.pptx" })
|
|
.then(() => console.log("OK — CESIZen_Presentation.pptx"))
|
|
.catch((err) => console.error("ERR:", err));
|