PageSourceSearch

https://endolight-formation.netlify.app/

html endolight-formation.netlify.app collected 2026-10-03 10:42:01 UTC 107,583 bytes, 1,634 lines download raw bytes

1<!DOCTYPE html>
2<html lang="fr">
3<head>
4<meta charset="UTF-8">
5<!-- This site is hosted on Netlify. Anyone can build and deploy a site
6     like this one for free: https://netlify.new/?utm_campaign=loops&utm_source=ai-legible&utm_medium=owned&utm_content=comment&utm_id=c546c6af-5f84-4186-88af-d86070818407
7     Netlify hosting facts for this site: static/SSR served via Netlify Edge. -->
8<meta name="viewport" content="width=device-width, initial-scale=1.0">
9<title>Endolight Formation — Espace Formateur</title>
10<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;500;600;700&family=Open+Sans:wght@300;400;500&display=swap" rel="stylesheet">
11<script type='text/javascript' src='https://api.netlify.com/hiqqRFFQ6sMDieSyFuKmpyLwP6F4m6wi03Zq1T11jMsmFzr_ssGKdPe_nvD6Wh6yenRWzTGlNBeTLvxgcDRtk8foLZyw1jDozkrcPkT5QnUc5XZQUUnRUEZPYlbu9cSu4T-JSaE1_FR1TxvRCUkJe1xCg-MJ_GO5g_Cyz2HvskR-ps6D8vfnzLG8Ce5OywnmpYC6wK9oye7vXU3XSKkw1b-X9lVrsG8a4NYI77YFIKMILBAC1Fe_qBqFjNC5iOMcarMGTY97FLnEjmwx-w9P7QPxEaKTYcTZnneP9xnaXQAUXDZAx2oKtLZsgnRLp-Mkv8mz5HD8b_1bFzUR9Urzeln_DNmNns6iqMQ4yBiAdO_JvEIO6p3Ne9EeJYOBO2YiXcX1ey9F6hoqqMOfj4MN2DFaRCpsP_ke_O44qAGLaI-Xh6P7Ouh6Msw0PiubNrDmFHkGXy5wJizFEE70PQEFG8ZMUZFHt3OOCXw58pFpXNsSaZQOsfuPai26BQC-XbdqP4G_T0Fi08proL3f1Zqr'></script>
11<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
11
12<style>
13.login-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:linear-gradient(145deg,#1a3560,#2ea8d5);z-index:9999;display:flex;align-items:center;justify-content:center;}
14.login-box{background:#fff;border-radius:16px;padding:40px 36px;width:90%;max-width:380px;box-shadow:0 20px 60px rgba(26,53,96,.35);text-align:center;}
15.login-box .logo-name{font-family:"Raleway",sans-serif;font-weight:700;font-size:1.5rem;letter-spacing:.08em;margin-bottom:4px;}
16.login-box .logo-name .endo{color:#1a3560;}.login-box .logo-name .light{color:#2ea8d5;}
17.login-box .logo-sub{font-family:"Raleway",sans-serif;font-size:.62rem;letter-spacing:.28em;color:#7a8fa6;text-transform:uppercase;margin-bottom:28px;}
18.login-box p{font-size:.82rem;color:#7a8fa6;margin-bottom:16px;}
19.login-box input{width:100%;border:1px solid #d0dde8;border-radius:8px;padding:12px 16px;font-family:"Open Sans",sans-serif;font-size:.9rem;color:#1a2b3c;outline:none;text-align:center;letter-spacing:.1em;margin-bottom:12px;transition:border .2s,box-shadow .2s;}
20.login-box input:focus{border-color:#2ea8d5;box-shadow:0 0 0 3px rgba(46,168,213,.12);}
21.login-box .login-btn{width:100%;background:#1a3560;color:#fff;border:none;border-radius:8px;padding:12px;font-family:"Raleway",sans-serif;font-size:.85rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;cursor:pointer;transition:background .2s;}
22.login-box .login-btn:hover{background:#142a50;}
23.login-box .login-error{color:#e05555;font-size:.78rem;margin-top:8px;display:none;}
24:root {
25  --navy:#1a3560; --blue:#2ea8d5; --blue-lt:#a8d8ee; --blue-xs:#e4f4fb;
26  --bg:#f2f5f8; --white:#fff; --text:#1a2b3c; --muted:#7a8fa6;
27  --border:#d0dde8; --green:#2ecc8a; --red:#e05555; --shadow:0 6px 32px rgba(26,53,96,.10);
28}
29*{box-sizing:border-box;margin:0;padding:0;}
30body{font-family:'Open Sans',sans-serif;background:linear-gradient(145deg,#eaf2f8,#f8fafc 60%,#e8f0f8);min-height:100vh;}
31header{background:var(--navy);padding:0 32px;display:flex;align-items:center;gap:18px;height:70px;box-shadow:0 3px 20px rgba(26,53,96,.25);}
32.logo-name{font-family:'Raleway',sans-serif;font-weight:700;font-size:1.25rem;letter-spacing:.08em;}
33.logo-name .endo{color:#fff;}.logo-name .light{color:var(--blue);}
34.logo-sub{font-family:'Raleway',sans-serif;font-size:.6rem;letter-spacing:.28em;color:var(--blue-lt);margin-top:3px;text-transform:uppercase;}
35.badge{margin-left:auto;background:rgba(46,168,213,.15);border:1px solid rgba(46,168,213,.4);color:var(--blue);font-family:'Raleway',sans-serif;font-size:.72rem;font-weight:600;letter-spacing:.06em;padding:6px 14px;border-radius:4px;text-transform:uppercase;}
36.accent-bar{width:100%;height:3px;background:linear-gradient(90deg,var(--navy),var(--blue) 60%,var(--blue-lt));}
37.container{max-width:980px;margin:0 auto;padding:28px 20px 60px;}
38
39/* Tabs */
40.tabs{display:flex;gap:4px;margin-bottom:24px;background:var(--white);border:1px solid var(--border);border-radius:10px;padding:5px;}
41.tab{flex:1;padding:10px;font-family:'Raleway',sans-serif;font-size:.78rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;border:none;background:transparent;color:var(--muted);border-radius:7px;cursor:pointer;transition:all .2s;}
42.tab.active{background:var(--navy);color:#fff;}
43.tab-content{display:none;}.tab-content.active{display:block;}
44
45.card{background:var(--white);border:1px solid var(--border);border-radius:10px;padding:24px 26px;box-shadow:var(--shadow);margin-bottom:20px;}
46.card-title{font-family:'Raleway',sans-serif;font-size:.85rem;font-weight:600;color:var(--navy);text-transform:uppercase;letter-spacing:.06em;margin-bottom:16px;padding-bottom:12px;border-bottom:2px solid var(--blue-xs);}
47.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:13px;}
48.form-group{display:flex;flex-direction:column;gap:5px;}
49.form-group.full{grid-column:1/-1;}
50label{font-size:.72rem;font-weight:600;color:var(--navy);letter-spacing:.03em;text-transform:uppercase;}
51input,select,textarea{border:1px solid var(--border);border-radius:6px;padding:10px 13px;font-family:'Open Sans',sans-serif;font-size:.84rem;color:var(--text);outline:none;background:#fafcfe;transition:border .2s,box-shadow .2s;}
52input:focus,select:focus,textarea:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(46,168,213,.12);}
53textarea{min-height:65px;resize:vertical;}
54select{cursor:pointer;}
55
56.docs-grid{display:flex;flex-wrap:wrap;gap:9px;margin-top:4px;}
57.doc-check{display:flex;align-items:center;gap:7px;background:var(--bg);border:1px solid var(--border);border-radius:20px;padding:7px 14px;cursor:pointer;font-size:.79rem;color:var(--navy);font-weight:500;transition:all .18s;user-select:none;}
58.doc-check.active{background:var(--navy);color:#fff;border-color:var(--navy);}
59
60.btn{border:none;border-radius:6px;font-family:'Raleway',sans-serif;font-size:.8rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;cursor:pointer;transition:all .2s;padding:10px 22px;}
61.btn-blue{background:var(--blue);color:#fff;}.btn-blue:hover{background:#2494bc;}
62.btn-navy{background:var(--navy);color:#fff;}.btn-navy:hover{background:#142a50;}
63.btn-outline{background:transparent;color:var(--navy);border:1px solid var(--border);}.btn-outline:hover{border-color:var(--blue);color:var(--blue);}
64.btn-sm{padding:7px 14px;font-size:.72rem;}
65.btn-danger{background:transparent;color:var(--red);border:1px solid var(--red);}.btn-danger:hover{background:var(--red);color:#fff;}
66
67/* Formations list */
68.formation-card{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:16px 18px;margin-bottom:10px;display:flex;align-items:center;gap:14px;cursor:pointer;transition:all .2s;}
69.formation-card:hover,.formation-card.selected{background:var(--blue-xs);border-color:var(--blue);}
70.formation-card.selected{border-width:2px;}
71.f-badge{padding:4px 11px;border-radius:20px;font-size:.7rem;font-weight:600;font-family:'Raleway',sans-serif;text-transform:uppercase;letter-spacing:.05em;}
72.f-badge.presentiel{background:#e8f5e9;color:#2e7d32;}
73.f-badge.distanciel{background:#e3f2fd;color:#1565c0;}
74.f-title{font-family:'Raleway',sans-serif;font-weight:600;font-size:.92rem;color:var(--navy);}
75.f-meta{font-size:.75rem;color:var(--muted);margin-top:2px;}
76.f-actions{margin-left:auto;display:flex;gap:7px;}
77
78/* Apprenants */
79.apprenant-row{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin-bottom:8px;display:flex;align-items:center;gap:12px;}
80.apprenant-row:hover{box-shadow:0 2px 10px rgba(26,53,96,.08);}
81.ap-name{font-family:'Raleway',sans-serif;font-weight:600;font-size:.88rem;color:var(--navy);}
82.ap-meta{font-size:.73rem;color:var(--muted);margin-top:1px;}
83.ap-actions{margin-left:auto;display:flex;gap:7px;align-items:center;}
84
85.link-box{background:var(--blue-xs);border:1px solid var(--blue-lt);border-radius:7px;padding:10px 13px;display:flex;align-items:center;gap:9px;margin-top:8px;}
86.link-box input{flex:1;background:transparent;border:none;font-size:.75rem;color:var(--navy);outline:none;}
87.copy-btn{background:var(--blue);color:#fff;border:none;border-radius:5px;padding:5px 11px;font-size:.7rem;font-family:'Raleway',sans-serif;font-weight:600;cursor:pointer;white-space:nowrap;text-transform:uppercase;transition:background .2s;}
88.copy-btn:hover{background:#2494bc;}.copy-btn.copied{background:var(--green);}
89
90.doc-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px;}
91.doc-tag{background:var(--white);border:1px solid var(--blue-lt);border-radius:11px;padding:2px 9px;font-size:.68rem;color:var(--navy);font-weight:500;}
92
93.empty-state{text-align:center;padding:40px 20px;color:var(--muted);}
94.empty-state .icon{font-size:2.2rem;margin-bottom:10px;}
95.empty-state p{font-size:.83rem;}
96
97/* Stats bar */
98.stats-bar{display:flex;gap:12px;margin-bottom:22px;flex-wrap:wrap;}
99.stat-card{background:var(--white);border:1px solid var(--border);border-radius:9px;padding:14px 18px;flex:1;min-width:130px;box-shadow:var(--shadow);}
100.stat-card .val{font-family:'Raleway',sans-serif;font-size:1.6rem;font-weight:700;color:var(--navy);}
101.stat-card .lbl{font-size:.72rem;color:var(--muted);margin-top:2px;text-transform:uppercase;letter-spacing:.04em;}
102
103/* Status dot */
104.status-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;}
105.dot-green{background:#3dd68c;}.dot-blue{background:var(--blue);}.dot-gray{background:#ccc;}
106
107/* Loader */
108.loader{display:inline-block;width:16px;height:16px;border:2px solid var(--blue-lt);border-top-color:var(--blue);border-radius:50%;animation:spin .7s linear infinite;vertical-align:middle;margin-right:6px;}
109@keyframes spin{to{transform:rotate(360deg);}}
110
111.modal-docs-formation{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(26,53,96,.45);z-index:900;display:flex;align-items:center;justify-content:center;}
112.modal-formation{background:#fff;border-radius:12px;padding:28px;width:90%;max-width:560px;max-height:85vh;overflow-y:auto;box-shadow:0 20px 60px rgba(26,53,96,.25);}
113.modal-formation h3{font-family:"Raleway",sans-serif;font-size:1rem;font-weight:600;color:var(--navy);margin-bottom:4px;}
114.modal-formation p.sub{font-size:.78rem;color:var(--muted);margin-bottom:18px;}
115.upload-zone{border:2px dashed var(--blue-lt);border-radius:8px;padding:16px;margin-bottom:12px;transition:border .2s;}
116.upload-zone:hover{border-color:var(--blue);}
117.upload-zone label{font-size:.72rem;font-weight:600;color:var(--navy);text-transform:uppercase;letter-spacing:.04em;display:block;margin-bottom:6px;}
118.upload-zone .file-info{font-size:.78rem;color:var(--muted);margin-top:4px;}
119.upload-zone .file-info.ok{color:var(--green);}
120.upload-input{display:none;}
121.upload-btn{background:var(--blue-xs);border:1px solid var(--blue-lt);color:var(--navy);border-radius:6px;padding:7px 14px;font-size:.78rem;font-family:"Raleway",sans-serif;font-weight:600;cursor:pointer;transition:all .2s;}
122.upload-btn:hover{background:var(--blue);color:#fff;border-color:var(--blue);}
123.doc-existant{display:flex;align-items:center;gap:8px;background:var(--blue-xs);border:1px solid var(--blue-lt);border-radius:6px;padding:7px 12px;margin-top:4px;font-size:.78rem;color:var(--navy);}
124.doc-existant .del-doc{margin-left:auto;background:transparent;border:none;color:var(--red);cursor:pointer;font-size:.9rem;}
125.modal-edit{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(26,53,96,.45);z-index:900;display:flex;align-items:center;justify-content:center;}
126.modal-edit-inner{background:#fff;border-radius:12px;padding:28px;width:90%;max-width:520px;max-height:88vh;overflow-y:auto;box-shadow:0 20px 60px rgba(26,53,96,.25);}
127.modal-edit-inner h3{font-family:"Raleway",sans-serif;font-size:1rem;font-weight:600;color:var(--navy);margin-bottom:4px;}
128.modal-edit-inner p.sub{font-size:.78rem;color:var(--muted);margin-bottom:18px;}
129.modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(26,53,96,.45);z-index:900;display:flex;align-items:center;justify-content:center;}
130.modal{background:#fff;border-radius:12px;padding:28px;width:90%;max-width:460px;box-shadow:0 20px 60px rgba(26,53,96,.25);}
131.modal h3{font-family:'Raleway',sans-serif;font-size:1rem;font-weight:600;color:var(--navy);margin-bottom:6px;}
132.modal p{font-size:.8rem;color:var(--muted);margin-bottom:16px;}
133.modal-docs{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px;}
134.modal-actions{display:flex;gap:9px;justify-content:flex-end;}
135.notif{position:fixed;bottom:22px;right:22px;background:var(--navy);color:#fff;border-radius:8px;padding:12px 20px;font-size:.82rem;font-family:'Raleway',sans-serif;font-weight:500;box-shadow:var(--shadow);transform:translateY(80px);opacity:0;transition:all .3s;z-index:999;}
136.notif.show{transform:translateY(0);opacity:1;}
137.divider{height:1px;background:var(--border);margin:16px 0;}
138
139@media(max-width:650px){.form-grid{grid-template-columns:1fr;}.stats-bar{flex-direction:column;}.container{padding:16px 12px 40px;}}
140</style>
141</head>
142<body>
143
144<!-- ÉCRAN LOGIN -->
145<div class="login-overlay" id="login-overlay">
146  <div class="login-box">
147    <div class="logo-name"><span class="endo">ENDO</span><span class="light">LIGHT</span></div>
148    <div class="logo-sub">Formation</div>
149    <p>Espace formateur — accès restreint</p>
150    <input type="password" id="login-input" placeholder="••••••••" onkeydown="if(event.key==='Enter') verifierMdp()" />
151    <button class="login-btn" onclick="verifierMdp()">Accéder</button>
152    <div class="login-error" id="login-error">Mot de passe incorrect</div>
153  </div>
154</div>
155
156<header>
157  <img src="data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl
157kZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAQnBCcDASIAAhEBAxEB/8QAHQABAAICAwEBAAAAAAAAAAAAAAEHBQYECAkDAv/EAE4QAQABAwIBBQkMCAQFAwUAAAABAgMEBQYRBxIhMUEIExY3UWFxgZEUIjJSU3R1k6Gxs9EjM0JVYnKywRVDY4KSosLh8CRUg0VzhMPS/8QAGwEBAQADAQEBAAAAAAAAAAAAAAEEBQYCAwf/xAA1EQEAAgEBBAcGBgIDAQAAAAAAAQIDBAURITESFTJBUXHREyIzYZGhBhZSgbHhFPAjQsFD/9oADAMBAAIRAxEAPwDruA6pxwAAAIACgAAAAACEgAACEggAAAKAAAAAAAAAAAAAAAAACAAoAAAIACgAAIBIAAAgAAAAAKAAAAAAAAACAAoAAAAAIACgAAAAAAAAAgAKAAAAACAAoAAAAAAAAAAAAAAAAAAAIACgAAAAAAAAAgAKACAAoAIACgIBIACEgAAAAgISKAAACAAoAIACgAAAAAgAKAAAAAAAAAAAAAAACAAoAAAIACgAAAAAAAAAgAAAKAAACAAAAoAAAAAAAAAIACgAgAKAgEgCAAAIFSAAAAAAAIACgAgAKhIAACAAoAAAAAAAAAIACgAgAAAKAAAAAAAAAgEgAAAAAAAAACEgACAAoAIACgAgAKAAAAAAAAAAAAACAAoAIACgAAAgAKAAAAISAAAAAhIAACAAAAoAAAIACgAAAgAKAAAAACAAoAAAAAIACgAAAAAgAKAAAAAAAAAAAAAAAAAAAAAAACAAAAoAAhIAAIACgAAAAAgAAAKAAACAAoAIAAACgAgAKAAAAACAAoAAAAAAAAAAAAAAAIAAISCgAAAAAAAAAISAAAAAgAKAAAAACAAoAIACgAgAKAAISAAAAAAAAAAAACAAoAAAIACgAgAKAAAAAAAAACAAoAIACgAgAKACAAoAAAAAIACgAAAAAgAAISKAAAAACAAoAAAIACgAAAAAAAgAAAKAAAAAAAAAAACAAoAAAAAAAIACgAAAAAgB2CgAAAAAAAgAAAKAAAAAAAAAAAAAAAAAAAAACAAoAIACghIAAAAAAAAgAAAAAKAAAAACAAoAAAAAAAAAIACgAAAAAAAAAAAAAgAKAAAAAAAAAAAAAAACAAoAIAAACgAgAKAAAAAAACAAoAIAAAAAAACgAAAgAKAAAAISAAAAAISACEgAAACAAoAIACgAAAAAAAAAgAKAAAAACAAoAAAAAAAAAAAAAAAAAAAAAAAIACgAAAAAgAKAAAAAAAAAAAAAAAAACAAAAAAoAAAIAAACgAAAAAAAAAgAAAAAAAKISAAAAAACAAoAAAAAAB2AAAAAAAAAAAACAAoAIACgAAAAAgAKAAAAACAAoAAAB2gCAAoAAAIAAACgAAAAAAAAAAAgAAAKAAAAAAACAAoAAAIACgAAAAAAAAAAAAAAAAAgAKACAAoAAAAAIACgAAAgAKAAACAAoAAAAAAAAAAAIACiEgAAgAKAAAAAAAAACAAAAoAAAICEigAAAgAKAAAAAAACAAAAoAAAAAIAAACgAAAAAAhIAAAAgAKhIAAAAAAAACAAoAAAAAICEigAAAAAAAAAAAAAgAKACAAoAAAIACgAAAgAKAAACAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAIACgIBIAAAAAAAgAAAKAAACAAAIFSAAAAAAAAAIAAAAACgAgAKAAAAAAACISAoAIACgAgAAAKAAAAAAACAAAAAAAAAAoAIACgAAAAAAAgAAAAAKAAAAAAAAAAAAAA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AAAAAAAEABRCUAkAAAAAQAFAAAAAAABAAUAAAAAAAAAAAAAAAAAEABQAAAQAAAFAAAAAAAAABAAAAAAUAAAAAAAAAEABQAQAFAAQkAAAABAAUAEABQAAAQAFAAABAAAAUAAAEABQAAAQAFABAAUAAAEABQAAAAAAAAAQAFAAAAAAAAAAABAAUAAAAAEABQAAAAAAAQAFAAAAAAABAAUAEABQAAAAAQAFAAAAAAAAAAAAAAAABCQABAAAAUAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAQAFAAAAABAAUAAAAAEAAAAABQAAAQAAAFAAAAAAABAAUAEABQAAAAAAAQAAAFAAAAAAAAABAAUAAAAAAAEABQAQAFAABCQAAAAAAABAAUAAAAAEABQAQAFAAAAABAAUAAAABAJAAAAAAAAABCQAAAAAAAAEAAAAAAAAAAABQAAAAAQAAAAAFAAAAABAAAAUAAAAAEABQAAAAAAAAAQQlAqQAAAAAEJAAAABAAUAAAAAAAEABQAAAAAAAAAQAFABAAAAAAUAAAAAEABQAAAQAFAAAAABAAUAAAAAAQkAAAAAAAAAAAAAAAAAAAAAAAAEABQAAAAAQAAAFAAABAAUAAAEAAAAABQAAAQAFAAAAAAABAAAAUAEAAAAABQAQAAAFABAAUAEAAABQAAAAAQAFABAAUAAAAAAAAAAAAAAAEAAAAAAABQAAAAAQAAAAAAAAAFABAAAAUAAAEABQAAAAAAAAAQAFABAAAAUAAAAAEABQAQAAAAAFAAAAAAAAAAAAAAABAAUAAAEAQKkAAAQAFAAAAAAABAAUAAAEAAAAABQAAAQAABAqQAAAABAAUAAAAAEAAAAABQAAAQAAAFAAABAAUAEAAABQAAAQAFABAAUAAAAAAAAAABAJAAAABAJAEABQAAAAAAAAAAAAAEJAAAAAAAQAFAAAAAAABAAUAAAEABQAAAAAQAFAAABAAUAAAAAEABQAAAAAQAFEJAABAAAECpAAAAAEAAAAABQAQAAAFAAABAAAAUAAAEABQAAAAAAAAAAAAARCQFAAABAAUAAAEABQAAAAAQAFAAAAABAAUAAAEABQAAQkAAAAAAAAAAAAQAFABAAUAAAAAEABQAQAFAAABAAUAAAAAEABQAQAFABAAUAEAAABQAQAAAAAFAAAAABAAAAAAUAAAAAAAAAAAAAEABQAAAAAAAAAAAAAAAAAQEJFAAAAAAAAAAAAABAAUAEAAABQAAAAAAAAAAAAAQAFAAAAAAABAAAAUAAAAAEABQAAABCQAAAAQAAAFAAAAABAAAAUAEAAABUJAAAAAAAAAAAQAFABAAUAEAAABQAAAAAAAQAFAAABAAUAAAAAAAAAEABQAQAFAAAAABAAAAUAAAAAAAAAEABQAAEAkAAAAAAAAAQAFAAAAABAAAAVCQAAAAAAEABQAAAAAAAQAAEJFAAAAABAAAAUAAAEABQAAAAAAAQAFAAAAQkAABAQkUAAAAAAAAAAAAAAAAAEABQAAAAAAAAAECQAAAAAAAAAAEJAAAAAQAAAFAAAAABAAUAAAAAAAAAAAAAEABQAQAFABAAAABCQUAAAAAEABQAAAQAAAAEJFBCQAAAAAAAAAAAAAAABAAUAEAAABQAAABCQAAAAAAQAAAAAFABAAUAAAAAAAEABQAQAFABAAUEJAAAAAAAAEABQAQAAAFABAAUAAEJAAEABQAAAEJAAAQAFBCQABAAUAAAAAAAAAEABQAAAAAQAFABAAUAAAAAAAAAAAAAAAAAAAAAAAAAkAABCQAAQAFABAAUAAAAAAAAAAAAAAAAAAQkAAAQkAAAAEABQAAAAAAAAEAkAAAAAQAAAFABAAAAUAAAAAAAAAEABQAAAAAAAAAAAAAAAAAQAFAAAAAAAAABAAUAAAAAAAAAAAAAEABQAAAAAAAAAAAQAFABAAUAAAAAEABQAAAAAQAFAAABAAUAEABQAAAAAAAQAAAFAAAAQkBAAUAAAAAAAAAAAAAEAAAAABQAAAAAAAQAFAAAAAAAAAAAAABAAUAEAAABQAAAAAAAAAAAAAAAAAAAAAAAQAFAAAAABAAAAUAAAAAAAAAAAAAAQAJAEABQAQAFABAAUAAAAAEAAABQAAAQAFAAAAAAAAABAAUAAAAAAAAAAAAAAAAAAAAAEABQAQAFAAAAABAAUQAJAAAAAAAAAAAAAAAAAAAEABQAAAAAAAAAAAAAQAFAAAAAAf/Z" style="height:46px;width:46px;object-fit:contain;border-radius:6px;" />
158  <div>
159    <div class="logo-name"><span class="endo">ENDO</span><span class="light">LIGHT</span></div>
160    <div class="logo-sub">Formation</div>
161  </div>
162  <div class="badge">⚙ Espace Formateur</div>
163</header>
164<div class="accent-bar"></div>
165
166<div class="container">
167
168  <!-- STATS -->
169  <div class="stats-bar" id="stats-bar">
170    <div class="stat-card"><div class="val" id="stat-formations">—</div><div class="lbl">Formations</div></div>
171    <div class="stat-card"><div class="val" id="stat-apprenants">—</div><div class="lbl">Apprenants</div></div>
172    <div class="stat-card"><div class="val" id="stat-docs">—</div><div class="lbl">Documents générés</div></div>
173    <div class="stat-card"><div class="val" id="stat-presentiel">—</div><div class="lbl">Présentiel</div></div>
174    <div class="stat-card"><div class="val" id="stat-distanciel">—</div><div class="lbl">Distanciel</div></div>
175  </div>
176
177  <!-- TABS -->
178  <div class="tabs">
179    <button class="tab active" onclick="switchTab('formations')">🎓 Formations</button>
180    <button class="tab" onclick="switchTab('apprenants')">👥 Apprenants</button>
181    <button class="tab" onclick="switchTab('nouvelle')">➕ Nouvelle formation</button>
182  </div>
183
184  <!-- TAB : FORMATIONS -->
185  <div class="tab-content active" id="tab-formations">
186    <div class="card">
187      <div class="card-title" style="display:flex;justify-content:space-between;align-items:center;">
188        <span>📋 Mes formations</span>
189        <button class="btn btn-blue btn-sm" onclick="switchTab('nouvelle')">+ Nouvelle</button>
190      </div>
191      <div id="formations-list"><div class="empty-state"><div class="loader"></div><p>Chargement…</p></div></div>
192    </div>
193  </div>
194
195  <!-- TAB : APPRENANTS -->
196  <div class="tab-content" id="tab-apprenants">
197    <div class="card">
198      <div class="card-title" style="display:flex;justify-content:space-between;align-items:center;">
199        <span>👥 Apprenants — <span id="ap-formation-title" style="color:var(--blue);font-weight:400;text-transform:none;letter-spacing:0;">Sélectionnez une formation</span></span>
200        <select id="ap-filter" onchange="loadApprenants()" style="font-size:.78rem;padding:6px 10px;max-width:220px;">
201          <option value="">Toutes les formations</option>
202        </select>
203      </div>
204      <div id="apprenants-list"><div class="empty-state"><div class="icon">👆</div><p>Sélectionnez une formation dans l'onglet Formations pour voir ses apprenants.</p></div></div>
205    </div>
206
207    <!-- Ajouter apprenant -->
208    <div class="card" id="add-apprenant-card">
209      <div class="card-title">➕ Ajouter un apprenant</div>
210      <div class="form-grid">
211        <div class="form-group"><label>Prénom</label><input type="text" id="a-prenom" placeholder="Marie" /></div>
212        <div class="form-group"><label>Nom</label><input type="text" id="a-nom" placeholder="DUPONT" /></div>
213        <div class="form-group"><label>Email</label><input type="email" id="a-email" placeholder="[email protected]" /></div>
214
215        <div class="form-group full"><label>Adresse</label><input type="text" id="a-adresse" placeholder="12 rue des Fleurs, 69001 Lyon" /></div>
216        <div class="form-group full">
217          <label>Formation</label>
218          <select id="a-formation"><option value="">— Choisir une formation —</option></select>
219        </div>
220        <div class="form-group full">
221          <label>Documents à générer</label>
222          <div class="docs-grid" id="docs-grid">
223            <label class="doc-check active" onclick="toggleDoc(this,'convocation')">📅 Convocation</label>
224            <label class="doc-check active" onclick="toggleDoc(this,'convention')">📋 Convention</label>
225            <label class="doc-check active" onclick="toggleDoc(this,'facture')">🧾 Facture</label>
226            <label class="doc-check active" onclick="toggleDoc(this,'attestation')">✅ Attestation</label>
227            <label class="doc-check active" onclick="toggleDoc(this,'certificat')">🏆 Certificat</label>
228            <label class="doc-check active" onclick="toggleDoc(this,'inscription')">📝 Attestation d'inscription</label>
229          </div>
230        </div>
231      </div>
232      <div style="display:flex;gap:9px;margin-top:16px;flex-wrap:wrap;">
233        <button class="btn btn-navy" onclick="ajouterApprenant()">Enregistrer & générer le lien</button>
234        <button class="btn btn-outline" onclick="resetApprenant()">Réinitialiser</button>
235      </div>
236    </div>
237  </div>
238
239  <!-- TAB : NOUVELLE FORMATION -->
240  <div class="tab-content" id="tab-nouvelle">
241    <div class="card">
242      <div class="card-title">🎓 Créer une formation</div>
243      <div class="form-grid">
244        <div class="form-group full"><label>Intitulé</label><input type="text" id="f-titre" placeholder="Endodontie clinique — niveau 1" /></div>
245        <div class="form-group"><label>Référence</label><input type="text" id="f-ref" placeholder="EL-2025-06" /></div>
246        <div class="form-group">
247          <label>Type</label>
248          <select id="f-type">
249            <option value="Présentiel">Présentiel</option>
250            <option value="Distanciel">Distanciel</option>
251          </select>
252        </div>
253        <div class="form-group"><label>Date(s)</label><input type="text" id="f-dates" placeholder="12 et 13 juin 2025" /></div>
254        <div class="form-group"><label>Lieu</label><input type="text" id="f-lieu" placeholder="15 av. Pasteur, 75011 Paris" /></div>
255        <div class="form-group"><label>Formateur(s)</label><input type="text" id="f-formateur" placeholder="Dr Marie Dupont" /></div>
256        <div class="form-group"><label>Montant TTC (€)</label><input type="text" id="f-montant" placeholder="890" /></div>
257        <div class="form-group">
258          <label>Statut</label>
259          <select id="f-statut">
260            <option value="À venir">À venir</option>
261            <option value="En cours">En cours</option>
262            <option value="Terminée">Terminée</option>
263          </select>
264        </div>
265        <div class="form-group"><label>Durée</label><input type="text" id="f-duree" placeholder="Ex: 6h" /></div>
266        <div class="form-group full"><label>Organisme</label><input type="text" id="f-organisme" value="Endolight Formation" /></div>
267        <div class="form-group"><label>SIRET</label><input type="text" id="f-siret" value="98532401100016" /></div>
268        <div class="form-group"><label>Signataire</label><input type="text" id="f-signataire" value="Gregory Fejoz" /></div>
269        <div class="form-group"><label>Ville</label><input type="text" id="f-ville" value="Annecy" /></div>
270        <div class="form-group"><label>Adresse organisme</label><input type="text" id="f-adresse-org" value="9bis chemin des chapelaines 74940 ANNECY LE VIEUX" /></div>
271        <div class="form-group"><label>N° d'activité</label><input type="text" id="f-nda" value="84740489274" /></div>
272      </div>
273      <div style="margin-top:16px;display:flex;gap:9px;">
274        <button class="btn btn-navy" onclick="creerFormation()">Créer la formation</button>
275        <button class="btn btn-outline" onclick="resetFormation()">Annuler</button>
276      </div>
277    </div>
278  </div>
279
280</div>
281
282<!-- MODAL DOCUMENTS FORMATION -->
283<div class="modal-docs-formation" id="modal-formation-overlay" style="display:none;" onclick="fermerModalFormation(event)">
284  <div class="modal-formation" onclick="event.stopPropagation()">
285    <h3>📎 Documents de la formation</h3>
286    <p class="sub" id="mf-titre">Uploadez les documents à partager avec vos apprenants.</p>
287
288    <div id="mf-docs-list"></div>
289    <p style="font-size:.78rem;color:var(--muted);margin-top:14px;line-height:1.6;">
290      💡 Pour ajouter des documents, uploadez-les directement dans <strong>Airtable</strong> 
291      dans le champ <strong>Docs Formation</strong> (type Attachment) de la formation concernée.<br>
292      Ils apparaîtront automatiquement dans l'espace apprenant.
293    </p>
294    <div style="display:flex;gap:9px;justify-content:flex-end;margin-top:20px;">
295      <button class="btn btn-navy" onclick="fermerModalFormation()">Fermer</button>
296    </div>
297  </div>
298</div>
299
300<!-- MODAL MODIFIER APPRENANT -->
301<div class="modal-edit" id="modal-edit-overlay" style="display:none;" onclick="fermerModalEdit(event)">
302  <div class="modal-edit-inner" onclick="event.stopPropagation()">
303    <h3>✏ Modifier l'apprenant</h3>
304    <p class="sub" id="me-titre">Modifiez les informations de l'apprenant.</p>
305    <div class="form-grid">
306      <div class="form-group"><label>Prénom</label><input type="text" id="me-prenom" /></div>
307      <div class="form-group"><label>Nom</label><input type="text" id="me-nom" /></div>
308      <div class="form-group"><label>Email</label><input type="email" id="me-email" /></div>
309      <div class="form-group"><label>Adresse</label><input type="text" id="me-adresse" /></div>
310      <div class="form-group full">
311        <label>Formation</label>
312        <select id="me-formation"></select>
313      </div>
314    </div>
315    <div style="display:flex;gap:9px;justify-content:flex-end;margin-top:20px;">
316      <button class="btn btn-outline" onclick="fermerModalEdit()">Annuler</button>
317      <button class="btn btn-navy" onclick="sauvegarderApprenant()">💾 Enregistrer</button>
318    </div>
319  </div>
320</div>
321
322<!-- MODAL AJOUT DOCUMENTS -->
323<div class="modal-overlay" id="modal-overlay" style="display:none;" onclick="fermerModal(event)">
324  <div class="modal" onclick="event.stopPropagation()">
325    <h3>➕ Ajouter des documents</h3>
326    <p id="modal-apprenant-name">Sélectionnez les documents à générer :</p>
327    <div class="modal-docs" id="modal-docs-grid"></div>
328    <div class="modal-actions">
329      <button class="btn btn-outline" onclick="fermerModal()">Annuler</button>
330      <button class="btn btn-navy" onclick="ajouterNouveauxDocs()">Générer & ajouter</button>
331    </div>
332  </div>
333</div>
334
335<div class="notif" id="notif"></div>
336
337<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
vendor: 1 bytes, line 337
337
338<script>
339const { jsPDF } = window.jspdf;
340
341// ── AIRTABLE ──
342// Les échanges passent par les fonctions Netlify /api/connexion (mot de passe) et
343// /api/admin (relais vers Airtable, qui détient le token) : aucune clé dans cette page.
344const API_CONNEXION = '/api/connexion';
345const API_ADMIN     = '/api/admin';
346const CLE_SESSION   = 'endo-session';
347
348function lireSession() {
349  try { return sessionStorage.getItem(CLE_SESSION) || ''; } catch(e) { return ''; }
350}
351function ecrireSession(valeur) {
352  try {
353    if (valeur) sessionStorage.setItem(CLE_SESSION, valeur);
354    else sessionStorage.removeItem(CLE_SESSION);
355  } catch(e) {}
356}
357
358async function appelAdmin(requete) {
359  const r = await fetch(API_ADMIN, {
360    method: 'POST',
361    headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${lireSession()}` },
362    body: JSON.stringify(requete),
363  });
364  if (r.status === 401) {
365    afficherConnexion('Session expirée : reconnectez-vous.');
366    throw new Error('Session expirée : reconnectez-vous.');
367  }
368  if (!r.ok) throw new Error(await r.text());
369  return r.json();
370}
371async function atGet(table, qs='')        { return appelAdmin({ methode:'GET',    table, qs }); }
372async function atCreate(table, fields)    { return appelAdmin({ methode:'POST',   table, fields }); }
373async function atPatch(table, id, fields) { return appelAdmin({ methode:'PATCH',  table, id, fields }); }
374async function atDelete(table, id)        { return appelAdmin({ methode:'DELETE', table, id }); }
375
376// Date du jour au format AAAA-MM-JJ pour les champs Date d'Airtable :
377// le format français JJ/MM/AAAA y est refusé (« Cannot parse date value »).
378// Calculée en heure locale : toISOString() donnerait la veille entre minuit et 2 h.
379function dateAirtable(d = new Date()) {
380  const p = n => String(n).padStart(2, '0');
381  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
382}
383
384// Journal de la table Documents. Il passe APRÈS l'enregistrement des PDF dans la
385// fiche apprenant : s'il échoue, on renvoie le message d'erreur sans tout bloquer.
386async function journaliserDocs(apprenantNom, types, formationTitre) {
387  try {
388    for (const d of types) {
389      await atCreate('Documents', {
390        Apprenant:   apprenantNom,
391        Type:        d,
392        Formation:   formationTitre,
393        'Généré le': dateAirtable(),
394        Statut:      'Généré',
395      });
396    }
397    return '';
398  } catch (e) {
399    console.error('Journal Documents :', e);
400    return e.message;
401  }
402}
403
404// ── STATE ──
405let formations = [];
406let apprenants = [];
407let selectedDocs = ['convocation','convention','facture','attestation','certificat'];
408let currentFormationId = null;
409
410// ── TABS ──
411function switchTab(name) {
412  document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
413  document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
414  document.getElementById(`tab-${name}`).classList.add('active');
415  const idx = {formations:0,apprenants:1,nouvelle:2}[name];
416  document.querySelectorAll('.tab')[idx].classList.add('active');
417  if (name === 'apprenants') loadApprenants();
418}
419
420// ── FORMATIONS ──
421async function loadFormations() {
422  try {
423    const data = await atGet('Formations', '?sort[0][field]=Titre&sort[0][direction]=asc');
424    formations = data.records || [];
425    renderFormations();
426    updateStats();
427    updateFormationSelects();
428  } catch(e) {
429    showNotif('❌ Erreur chargement formations: ' + e.message);
430  }
431}
432
433function renderFormations() {
434  const el = document.getElementById('formations-list');
435  if (!formations.length) {
436    el.innerHTML = `<div class="empty-state"><div class="icon">🎓</div><p>Aucune formation créée.<br>Cliquez sur "+ Nouvelle formation" pour commencer.</p></div>`;
437    return;
438  }
439  const statusColor = { 'À venir':'dot-blue', 'En cours':'dot-green', 'Terminée':'dot-gray' };
440  el.innerHTML = formations.map(f => {
441    const ff = f.fields;
442    const type = (ff.Type||'Présentiel').toLowerCase();
443    return `
444    <div class="formation-card ${currentFormationId===f.id?'selected':''}" onclick="selectFormation('${f.id}')">
445      <div class="status-dot ${statusColor[ff.Statut]||'dot-gray'}"></div>
446      <div style="flex:1;">
447        <div class="f-title">${ff.Titre||'Sans titre'}</div>
448        <div class="f-meta">${ff['Date(s)']||''} ${ff.Lieu ? '· '+ff.Lieu : ''}</div>
449      </div>
450      <span class="f-badge ${type}">${ff.Type||'Présentiel'}</span>
451      <div class="f-actions">
452        <button class="btn btn-blue btn-sm" onclick="event.stopPropagation();selectFormation('${f.id}');switchTab('apprenants')">👥 Apprenants</button>
453        <button class="btn btn-outline btn-sm" onclick="event.stopPropagation();ouvrirModalFormationDocs('${f.id}')">📎 Documents</button>
454        <button class="btn btn-danger btn-sm" onclick="event.stopPropagation();supprimerFormation('${f.id}')">✕</button>
455      </div>
456    </div>`;
457  }).join('');
458}
459
460function selectFormation(id) {
461  currentFormationId = id;
462  const f = formations.find(x => x.id === id);
463  if (f) document.getElementById('ap-formation-title').textContent = f.fields.Titre || '';
464  renderFormations();
465  document.getElementById('ap-filter').value = id;
466  loadApprenants();
467  switchTab('apprenants');
468}
469
470async function creerFormation() {
471  const titre = document.getElementById('f-titre').value.trim();
472  if (!titre) { showNotif('⚠ Titre obligatoire'); return; }
473  try {
474    showNotif('⏳ Création en cours…');
475    await atCreate('Formations', {
476      Titre:       titre,
477      Référence:   document.getElementById('f-ref').value,
478      Type:        document.getElementById('f-type').value,
479      'Date(s)':   document.getElementById('f-dates').value,
480      Lieu:        document.getElementById('f-lieu').value,
481      'Formateur(s)': document.getElementById('f-formateur').value,
482      'Montant TTC': parseFloat(document.getElementById('f-montant').value)||0,
483      Statut:      document.getElementById('f-statut').value,
484      Durée:       document.getElementById('f-duree').value,
485      Organisme:   document.getElementById('f-organisme').value,
486      SIRET:       document.getElementById('f-siret').value,
487      Signataire:  document.getElementById('f-signataire').value,
488      Ville:       document.getElementById('f-ville').value,
489      'Adresse organisme': document.getElementById('f-adresse-org').value,
490      NDA: document.getElementById('f-nda').value,
491    });
492    await loadFormations();
493    resetFormation();
494    switchTab('formations');
495    showNotif('✓ Formation créée avec succès');
496  } catch(e) { showNotif('❌ Erreur: ' + e.message); }
497}
498
499async function supprimerFormation(id) {
500  if (!confirm('Supprimer cette formation ? Les apprenants liés ne seront pas supprimés.')) return;
501  try {
502    await atDelete('Formations', id);
503    if (currentFormationId === id) currentFormationId = null;
504    await loadFormations();
505    showNotif('✓ Formation supprimée');
506  } catch(e) { showNotif('❌ Erreur: ' + e.message); }
507}
508
509function resetFormation() {
510  ['f-titre','f-ref','f-dates','f-lieu','f-formateur','f-montant'].forEach(id => {
511    const el = document.getElementById(id);
512    if (el) el.value = id === 'f-organisme' ? 'Endolight Formation' : '';
513  });
514}
515
516function updateFormationSelects() {
517  const sel = document.getElementById('a-formation');
518  const filter = document.getElementById('ap-filter');
519  const opts = formations.map(f => `<option value="${f.id}">${f.fields.Titre||'Sans titre'}</option>`).join('');
520  sel.innerHTML = '<option value="">— Choisir une formation —</option>' + opts;
521  filter.innerHTML = '<option value="">Toutes les formations</option>' + opts;
522  if (currentFormationId) {
523    sel.value = currentFormationId;
524    filter.value = currentFormationId;
525  }
526}
527
528// ── APPRENANTS ──
529async function loadApprenants() {
530  const filterId = document.getElementById('ap-filter').value || currentFormationId;
531  try {
532    let qs = '?sort[0][field]=Nom';
533    if (filterId) {
534      const f = formations.find(x => x.id === filterId);
535      if (f) {
536        document.getElementById('ap-formation-title').textContent = f.fields.Titre || '';
537        qs += `&filterByFormula=FIND("${filterId}",{Formation ID})`;
538      }
539    }
540    const data = await atGet('Apprenants', qs);
541    apprenants = data.records || [];
542    renderApprenants();
543    updateStats();
544  } catch(e) {
545    showNotif('❌ Erreur chargement apprenants: ' + e.message);
546  }
547}
548
549function renderApprenants() {
550  const el = document.getElementById('apprenants-list');
551  if (!apprenants.length) {
552    el.innerHTML = `<div class="empty-state"><div class="icon">👤</div><p>Aucun apprenant pour cette formation.</p></div>`;
553    return;
554  }
555  const docLabels = {convocation:'Convocation',convention:'Convention',facture:'Facture',attestation:'Attestation',certificat:'Certificat',inscription:"Attestation d'inscription"};
556  el.innerHTML = apprenants.map((a,i) => {
557    const af = a.fields;
558    const docs = (af.Documents||'').split(',').filter(Boolean);
559    const link = af['Lien magique'] || '';
560    return `
561    <div class="apprenant-row">
562      <div style="flex:1;">
563        <div class="ap-name">Dr ${af.Prénom||''} ${af.Nom||''}</div>
564        <div class="ap-meta">${af.Email||'Pas d\'email'}</div>
565        <div class="doc-tags">${docs.map(d=>`<span class="doc-tag">${docLabels[d.trim()]||d}</span>`).join('')}</div>
566        ${link ? `<div class="link-box"><input type="text" value="${link}" readonly id="link-${i}"/><button class="copy-btn" id="copy-${i}" onclick="copyLink(${i},'${link}')">Copier</button></div>` : ''}
567      </div>
568      <div class="ap-actions">
569        <button class="btn btn-blue btn-sm" onclick="telechargerDocs('${a.id}')">⬇ Docs</button>
570        <button class="btn btn-outline btn-sm" onclick="ouvrirModalDocs('${a.id}')">+ Docs</button>
571        <button class="btn btn-outline btn-sm" onclick="ouvrirModalEditApprenant('${a.id}')">✏</button>
572        <button class="btn btn-danger btn-sm" onclick="supprimerApprenant('${a.id}')">✕</button>
573      </div>
574    </div>`;
575  }).join('');
576}
577
578function toggleDoc(el, doc) {
579  el.classList.toggle('active');
580  if (selectedDocs.includes(doc)) selectedDocs = selectedDocs.filter(d => d !== doc);
581  else selectedDocs.push(doc);
582}
583
584async function ajouterApprenant() {
585  const prenom = document.getElementById('a-prenom').value.trim();
586  const nom    = document.getElementById('a-nom').value.trim();
587  const formId = document.getElementById('a-formation').value;
588  if (!prenom || !nom) { showNotif('⚠ Prénom et nom obligatoires'); return; }
589  if (!formId) { showNotif('⚠ Sélectionnez une formation'); return; }
590
591  const formation = formations.find(f => f.id === formId);
592  if (!formation) { showNotif('⚠ Formation introuvable'); return; }
593
594  const id = Math.random().toString(36).substr(2,9).toUpperCase();
595  const magicLink = `https://endolight-formation.netlify.app/endolight-apprenant.html?id=${id}`;
596
597  try {
598    showNotif('⏳ Enregistrement…');
599    const apprenant = {
600      prenom, nom,
601      email: document.getElementById('a-email').value.trim(),
602      adresse: document.getElementById('a-adresse').value.trim(),
603    };
604
605    // Générer les PDFs en base64
606    const ff = formation.fields;
607    const docsData = {};
608    selectedDocs.forEach(d => {
609      const pdf = generatePDF(d, apprenant, ff);
610      docsData[d] = pdf.output('datauristring');
611    });
612
613    await atCreate('Apprenants', {
614      Prénom:        prenom,
615      Nom:           nom,
616      Email:         apprenant.email,
617      Adresse:       apprenant.adresse,
618      'Formation ID': formId,
619      'Formation':   formation.fields.Titre,
620      Documents:     selectedDocs.join(','),
621      'ID unique':   id,
622      'Lien magique': magicLink,
623      'Docs JSON':   JSON.stringify(docsData),
624    });
625
626    // Log dans Documents (l'apprenant est déjà créé : un échec ici ne doit pas faire croire le contraire)
627    const erreurJournal = await journaliserDocs(`${prenom} ${nom}`, selectedDocs, formation.fields.Titre);
628
629    await loadApprenants();
630    resetApprenant();
631    showNotif(erreurJournal
632      ? `⚠ ${prenom} ${nom} ajouté(e), mais journal Documents non mis à jour : ${erreurJournal}`
633      : `✓ ${prenom} ${nom} ajouté(e) !`);
634  } catch(e) { showNotif('❌ Erreur: ' + e.message); }
635}
636
637async function telechargerDocs(id) {
638  const a = apprenants.find(x => x.id === id);
639  if (!a) return;
640  try {
641    const docsJson = a.fields['Docs JSON'];
642    if (!docsJson) { showNotif('⚠ Aucun document stocké'); return; }
643    const docsData = JSON.parse(docsJson);
644    Object.entries(docsData).forEach(([type, uri]) => {
645      const link = document.createElement('a');
646      link.href = uri;
647      link.download = `${type}-${a.fields.Prénom}-${a.fields.Nom}.pdf`;
648      link.click();
649    });
650  } catch(e) { showNotif('❌ Erreur téléchargement'); }
651}
652
653async function supprimerApprenant(id) {
654  if (!confirm('Supprimer cet apprenant ?')) return;
655  try {
656    await atDelete('Apprenants', id);
657    await loadApprenants();
658    showNotif('✓ Apprenant supprimé');
659  } catch(e) { showNotif('❌ Erreur: ' + e.message); }
660}
661
662function resetApprenant() {
663  ['a-prenom','a-nom','a-email','a-rpps','a-adresse'].forEach(id => {
664    const el = document.getElementById(id);
665    if (el) el.value = '';
666  });
667}
668
669function copyLink(i, link) {
670  navigator.clipboard.writeText(link).then(() => {
671    const btn = document.getElementById(`copy-${i}`);
672    btn.textContent = '✓ Copié !';
673    btn.classList.add('copied');
674    setTimeout(() => { btn.textContent = 'Copier'; btn.classList.remove('copied'); }, 2000);
675  });
676}
677
678// ── STATS ──
679async function updateStats() {
680  document.getElementById('stat-formations').textContent = formations.length;
681  document.getElementById('stat-apprenants').textContent = apprenants.length;
682  document.getElementById('stat-presentiel').textContent = formations.filter(f => f.fields.Type === 'Présentiel').length;
683  document.getElementById('stat-distanciel').textContent = formations.filter(f => f.fields.Type === 'Distanciel').length;
684  try {
685    const docs = await atGet('Documents', '');
686    document.getElementById('stat-docs').textContent = (docs.records||[]).length;
687  } catch(e) {}
688}
689
690// ── PDF GENERATORS ──
691
692
693function pdfLine(doc,y){doc.setDrawColor(208,221,232);doc.line(14,y,196,y);}
694
695function generatePDF(type, apprenant, ff) {
696  const formation = {
697    titre: ff.Titre||'', dates: ff['Date(s)']||'', lieu: ff.Lieu||'',
698    formateur: ff['Formateur(s)']||'', montant: String(ff['Montant TTC']||0),
699    organisme: ff.Organisme||'Endolight Formation', num: ff.Référence||''
700  };
701  switch(type) {
702    case 'convocation':  return makeConvocation(apprenant, formation);
703    case 'convention':   return makeConvention(apprenant, formation);
704    case 'facture':      return makeFacture(apprenant, formation);
705    case 'attestation':  return makeAttestation(apprenant, formation);
706    case 'certificat':   return makeCertificat(apprenant, formation);
707    case 'inscription':  return makeInscription(apprenant, formation);
708  }
709}
710
711
712function pdfInit() {
713  const doc = new jsPDF({ unit: 'mm', format: 'a4' });
714  return doc;
715}
716
717function pdfHeader(doc, titre) {
718  const pageW = 210;
719  doc.setFillColor(26,53,96);
720  doc.rect(0, 0, pageW, 36, 'F');
721    // Logo texte
722  doc.setFont('helvetica','bold'); doc.setFontSize(16); doc.setTextColor(255,255,255);
723  doc.text('ENDOLIGHT', pageW/2, 22, {align:'center'});
724  // Colorier LIGHT en bleu par-dessus
725  var eW = doc.getTextWidth('ENDO');
726  var fullW = doc.getTextWidth('ENDOLIGHT');
727  var sx = pageW/2 - fullW/2 + eW;
728  doc.setTextColor(46,168,213);
729  doc.text('LIGHT', sx, 22);
730  doc.setFont('helvetica','normal'); doc.setFontSize(7); doc.setTextColor(168,216,238); doc.setCharSpace(3);
731  doc.text('FORMATION', pageW/2, 30, {align:'center'});
732  doc.setCharSpace(0);
733  doc.setFillColor(46,168,213);
734  doc.rect(0, 36, pageW, 2, 'F');
735  doc.setFont('helvetica','bold');
736  doc.setFontSize(16);
737  doc.setTextColor(26,53,96);
738  doc.text(titre, pageW/2, 52, {align:'center'});
739  doc.setFillColor(46,168,213);
740  doc.rect(pageW/2 - 20, 55, 40, 1.2, 'F');
741}
742
743function pdfFooter(doc, adOrg) {
744  const pageW = 210, pageH = 297;
745  doc.setFillColor(26,53,96);
746  doc.rect(0, pageH-14, pageW, 14, 'F');
747  doc.setFont('helvetica','normal');
748  doc.setFontSize(7);
749  doc.setTextColor(168,216,238);
750  doc.text('ENDOLIGHT FORMATION  —  ' + (adOrg||'4 rue des bouvières 74940 ANNECY') + '  —  [email protected]', pageW/2, pageH-6, {align:'center'});
751}
752
753function pdfVilleDate(doc, ville) {
754  const pageW = 210;
755  const dateStr = new Date().toLocaleDateString('fr-FR', {day:'2-digit', month:'long', year:'numeric'});
756  doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.setTextColor(100,100,100);
757  doc.text(ville||'Annecy', 20, 44);
758  doc.text(dateStr, pageW-20, 44, {align:'right'});
759}
760
761
762function pdfInfoBloc(doc, rows, y) {
763  doc.setFillColor(228,244,251);
764  doc.roundedRect(16, y, 178, rows.length * 14 + 8, 3, 3, 'F');
765  doc.setDrawColor(168,216,238); doc.setLineWidth(0.3);
766  doc.roundedRect(16, y, 178, rows.length * 14 + 8, 3, 3, 'S');
767  let ry = y + 10;
768  rows.forEach(function([lbl, val]) {
769    doc.setFont('helvetica','bold'); doc.setFontSize(7); doc.setTextColor(46,168,213);
770    doc.text(lbl.toUpperCase(), 22, ry);
771    doc.setFont('helvetica','normal'); doc.setFontSize(9.5); doc.setTextColor(26,43,60);
772    doc.text(val||'—', 65, ry, {maxWidth:122});
773    ry += 14;
774  });
775  return ry + 4;
776}
777
778// Fonction utilitaire commune header/footer
779function addSignature(doc, x, y, w, h) {
780  doc.setFont('times', 'bolditalic');
781  doc.setFontSize(11);
782  doc.setTextColor(26, 53, 96);
783  doc.text('Gregory Fejoz', x + 2, y + 9);
784  doc.setDrawColor(26, 53, 96);
785  doc.setLineWidth(0.4);
786  doc.line(x, y + 12, x + (w||70), y + 12);
787  doc.setFont('helvetica', 'normal');
788  doc.setFontSize(10);
789  doc.setTextColor(26, 43, 60);
790  doc.setLineWidth(0.4);
791}
792
793function pdfHeader(doc, title) {
794  const pageW = doc.internal.pageSize.width;
795  // Header marine
796  doc.setFillColor(26,53,96);
797  doc.rect(0, 0, pageW, 38, 'F');
798  // Logo texte centré
799  doc.setFont('helvetica','bold'); doc.setFontSize(18); doc.setTextColor(255,255,255);
800  doc.text('ENDO', pageW/2 - 1, 22, {align:'right'});
801  doc.setTextColor(46,168,213);
802  doc.text('LIGHT', pageW/2 + 1, 22, {align:'left'});
803  doc.setFont('helvetica','normal'); doc.setFontSize(7); doc.setTextColor(168,216,238); doc.setCharSpace(3);
804  doc.text('FORMATION', pageW/2, 30, {align:'center'});
805  doc.setCharSpace(0);
806  // Ligne bleue
807  doc.setFillColor(46,168,213); doc.rect(0, 38, pageW, 2.5, 'F');
808  // Titre document
809  if (title) {
810    doc.setFont('helvetica','bold'); doc.setFontSize(20); doc.setTextColor(26,53,96);
811    doc.text(title, pageW/2, 70, {align:'center'});
812    doc.setFillColor(46,168,213); doc.rect(pageW/2-30, 75, 60, 2, 'F');
813  }
814}
815
816function pdfFooter(doc, adOrg) {
817  const pageW = doc.internal.pageSize.width;
818  const pageH = doc.internal.pageSize.height;
819  doc.setFillColor(26,53,96); doc.rect(0, pageH-18, pageW, 18, 'F');
820  doc.setFont('helvetica','normal'); doc.setFontSize(7); doc.setTextColor(168,216,238);
821  doc.text('ENDOLIGHT FORMATION  —  ' + (adOrg||'4 rue des bouvières 74940 ANNECY') + '  —  [email protected]', pageW/2, pageH-8, {align:'center'});
822}
823
824function pdfVilleDate(doc, ville, dateStr) {
825  const pageW = doc.internal.pageSize.width;
826  doc.setFont('helvetica','normal'); doc.setFontSize(9.5); doc.setTextColor(100,100,100);
827  doc.text(ville||'Annecy', 20, 52);
828  doc.text(dateStr, pageW-20, 52, {align:'right'});
829}
830
831
832function pdfInfoBloc(doc, rows, y) {
833  const pageW = doc.internal.pageSize.width;
834  const h = rows.length * 16 + 12;
835  doc.setFillColor(228,244,251); doc.roundedRect(16, y, pageW-32, h, 4, 4, 'F');
836  doc.setDrawColor(168,216,238); doc.setLineWidth(0.4); doc.roundedRect(16, y, pageW-32, h, 4, 4, 'S');
837  let ry = y + 12;
838  rows.forEach(function([lbl, val], i) {
839    if (i > 0) { doc.setDrawColor(200,232,244); doc.setLineWidth(0.3); doc.line(24, ry-4, pageW-24, ry-4); }
840    doc.setFont('helvetica','bold'); doc.setFontSize(8); doc.setTextColor(46,168,213);
841    doc.text(lbl.toUpperCase(), 24, ry);
842    doc.setFont('helvetica','normal'); doc.setFontSize(10); doc.setTextColor(26,43,60);
843    doc.text(String(val||'—'), 95, ry, {maxWidth: pageW-115});
844    ry += 16;
845  });
846  return y + h + 6;
847}
848
849function makeConvocation(a, f) {
850  const doc = new jsPDF();
851  const pageW = doc.internal.pageSize.width;
852  const adOrg = f.adresseOrg || '4 rue des bouvières 74940 ANNECY';
853  const ville  = f.ville || 'Annecy';
854  const dateStr = new Date().toLocaleDateString('fr-FR', {day:'2-digit', month:'long', year:'numeric'});
855
856  pdfHeader(doc, 'CONVOCATION');
857  pdfVilleDate(doc, ville, dateStr);
858
859  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
860  doc.text('Madame, Monsieur,', 20, 90);
861  doc.setFont('helvetica','bold'); doc.setFontSize(13); doc.setTextColor(26,53,96);
862  doc.text('Dr ' + a.nom + ' ' + a.prenom, 20, 100);
863  doc.setFont('helvetica','normal'); doc.setFontSize(10.5); doc.setTextColor(60,60,60);
864  doc.text("Nous avons le plaisir de vous convoquer à la formation dont les détails figurent ci-dessous.", 20, 114, {maxWidth:170});
865
866  pdfInfoBloc(doc, [
867    ['Formation', f.titre||''],
868    ['Date(s)',   f.dates||''],
869    ['Lieu',      f.lieu||''],
870    ['Formateur', f.formateur||''],
871  ], 124);
872
873  doc.setFont('helvetica','italic'); doc.setFontSize(9); doc.setTextColor(140,140,140);
874  doc.text('Merci de vous présenter 15 minutes avant le début de la séance avec ce document.', 20, 222, {maxWidth:170});
875
876  pdfFooter(doc, adOrg);
877  return doc;
878}
879
880function makeConvention(a, f) {
881  const doc = new jsPDF();
882  const pageW = doc.internal.pageSize.width;
883  const pageH = doc.internal.pageSize.height;
884  const adOrg  = f.adresseOrg || '9bis chemin des chapelaines 74940 ANNECY LE VIEUX';
885  const siret  = f.siret  || '98532401100016';
886  const nda    = f.nda    || '84740489274';
887  const ville  = f.ville  || 'Annecy le Vieux';
888  const sign   = f.signataire || 'Gregory Fejoz';
889  const dateStr = new Date().toLocaleDateString('fr-FR');
890
891  function pageFooter() {
892    doc.setFillColor(245,246,248); doc.rect(0, pageH-22, pageW, 22, 'F');
893    doc.setFont('helvetica','bold'); doc.setFontSize(8); doc.setTextColor(26,53,96);
894    doc.text('ENDOLIGHT FORMATION', pageW/2, pageH-14, {align:'center'});
895    doc.setFont('helvetica','normal'); doc.setFontSize(7); doc.setTextColor(100,100,100);
896    doc.text(adOrg, pageW/2, pageH-9, {align:'center'});
897    doc.text("N° SIRET : " + siret + "    N° D'Activité : " + nda, pageW/2, pageH-4, {align:'center'});
898  }
899
900  // ── PAGE 1 : Page de garde ──
901  doc.setFillColor(26,53,96); doc.rect(0, 0, pageW, pageH, 'F');
902  // ENDOLIGHT FORMATION
903  doc.setFont('helvetica','bold'); doc.setFontSize(42); doc.setTextColor(46,168,213);
904  doc.text('ENDOLIGHT', pageW/2, 115, {align:'center'});
905  doc.setTextColor(255,255,255);
906  doc.text('FORMATION', pageW/2, 148, {align:'center'});
907  // Titre formation
908  doc.setFont('helvetica','bold'); doc.setFontSize(11); doc.setTextColor(46,168,213); doc.setCharSpace(1);
909  const titreUp = (f.titre||'').toUpperCase();
910  const titreLines = doc.splitTextToSize(titreUp, 160);
911  doc.text(titreLines, pageW/2, 168, {align:'center'});
912  doc.setCharSpace(0);
913  // CONVENTION DE FORMATION
914  doc.setFont('helvetica','bold'); doc.setFontSize(22); doc.setTextColor(46,168,213);
915  doc.text('CONVENTION DE FORMATION', pageW/2, 220, {align:'center'});
916  // Pied
917  doc.setFont('helvetica','normal'); doc.setFontSize(7); doc.setTextColor(100,130,180);
918  doc.text('ENDOLIGHT FORMATION  —  ' + adOrg, pageW/2, pageH-10, {align:'center'});
919  doc.text("N° SIRET : " + siret + "  —  N° D'Activité : " + nda, pageW/2, pageH-4, {align:'center'});
920
921  // ── PAGE 2 ──
922  doc.addPage();
923  pdfHeader(doc, null);
924  let y = 50;
925
926  doc.setFont('helvetica','bold'); doc.setFontSize(11); doc.setTextColor(26,53,96);
927  doc.text('CONVENTION DE FORMATION PROFESSIONNELLE', pageW/2, y, {align:'center'}); y+=6;
928  doc.setFont('helvetica','normal'); doc.setFontSize(8.5); doc.setTextColor(80,80,80);
929  doc.text('(ARTICLE L.6353-1 ET ARTICLE D6353-1 DU CODE DU TRAVAIL)', pageW/2, y, {align:'center'}); y+=12;
930
931  doc.setFont('helvetica','bold'); doc.setFontSize(10); doc.setTextColor(26,53,96);
932  doc.text('CONVENTION N°1', 16, y); y+=10;
933
934  doc.setFont('helvetica','normal'); doc.setFontSize(9.5); doc.setTextColor(40,40,40);
935  doc.text('Entre les soussigné(e)s :', 16, y); y+=8;
936  doc.text('1. Dr ' + a.nom + ' ' + a.prenom + ', situé ' + (a.adresse||''), 16, y, {maxWidth:178}); y+=7;
937  doc.text('Ci-après dénommé(e) le Bénéficiaire,', 16, y); y+=10;
938
939  const lignes = [
940    "2. ENDOLIGHT FORMATION (en cours d'enregistrement auprès du préfet de Haute Savoie),",
941    "Organisme Prestataire d'Actions concourant au développement des Compétences (OPAC),",
942    "situé au " + adOrg + ","
943  ];
944  lignes.forEach(function(l) { doc.text(l, 16, y, {maxWidth:178}); y+=6; });
945  doc.text("Ci-après dénommé(e) l'OPAC, ou l'Organisme de formation,", 16, y); y+=8;
946  const intro = "est conclue la convention suivante, en application des dispositions du Livre III de la Sixième partie du Code du travail portant organisation de la formation professionnelle continue.";
947  const introLines = doc.splitTextToSize(intro, 178);
948  doc.text(introLines, 16, y); y += introLines.length * 5.5 + 10;
949
950  // Art 1
951  doc.setDrawColor(208,221,232); doc.setLineWidth(0.3); doc.line(16, y, 194, y); y+=6;
952  doc.setFont('helvetica','bold'); doc.setFontSize(10); doc.setTextColor(26,53,96);
953  doc.text('Article 1 : Objet de la convention', 16, y); y+=2;
954  doc.setDrawColor(46,168,213); doc.setLineWidth(0.5); doc.line(16, y, 101, y); y+=6;
955
956  doc.setFont('helvetica','normal'); doc.setFontSize(9.5); doc.setTextColor(40,40,40);
957  doc.text("L'OPAC ENDOLIGHT FORMATION organisera l'action de formation suivante :", 16, y, {maxWidth:178}); y+=8;
958  doc.setFont('helvetica','bold'); doc.text('- Intitulé de l\'action de formation :', 16, y); y+=5;
959  doc.setFont('helvetica','normal'); doc.text(f.titre||'', 20, y, {maxWidth:174}); y+=8;
960
961  doc.setFont('helvetica','bold'); doc.text('- Objectifs généraux :', 16, y); y+=5;
962  doc.setFont('helvetica','normal');
963  const obj1 = "Diagnostic de l'état pulpaire et choix de la thérapie de maintien de la vitalité pulpaire adaptée à la situation clinique sur dents matures permanentes.";
964  const obj2 = "Obtenir les conditions optimales de réalisation de la pulpotomie partielle et totale, et connaître les facteurs influençant leur succès.";
965  const obj3 = "Gestion clinique des différentes techniques de préservation pulpaire. Choix du biomatériau et de la restauration coronaire associée. Suivi et gestion des échecs.";
966  [obj1, obj2, obj3].forEach(function(o) {
967    const ls = doc.splitTextToSize(o, 174);
968    doc.text(ls, 20, y); y += ls.length * 5.2 + 3;
969  });
970
971  doc.setFont('helvetica','bold'); doc.text("- Catégorie d'action concourant au développement des compétences (article L.6313-1) :", 16, y, {maxWidth:178}); y+=8;
972  doc.setFont('helvetica','normal');
973  const cat = "Cette action est une action de formation au regard de la définition donnée par le code du travail (article L.6313-2) : \u00ab L'action de formation mentionn\u00e9e au 1\u00b0 de l'article L. 6313-1 se d\u00e9finit comme un parcours p\u00e9dagogique permettant d'atteindre un objectif professionnel. \u00bb La pr\u00e9sente action vise \u00e0 \u00ab favoriser l'adaptation des travailleurs \u00e0 leur poste de travail, \u00e0 l'\u00e9volution des emplois ainsi que leur maintien dans l'emploi et de participer au d\u00e9veloppement de leurs comp\u00e9tences en lien ou non avec leur poste de travail. \u00bb (article L.6313-3 du Code du travail).";
974  const catLines = doc.splitTextToSize(cat, 178);
975  doc.text(catLines, 16, y); y += catLines.length * 5 + 4;
976
977  pageFooter();
978
979  // ── PAGE 3 ──
980  doc.addPage();
981  pdfHeader(doc, null);
982  y = 50;
983
984  const articles = [
985    {
986      titre: 'Article 2 : Modalités de déroulement (suivi et sanction)',
987      texte: '- Programme et méthodes :\nCf. programme de formation ci joint.'
988    },
989    {
990      titre: 'Article 3 : Dates et lieu',
991      texte: '- Durée : ' + (f.dates||'') + '\n- Lieu : ' + (f.lieu||'')
992    },
993    {
994      titre: 'Article 3 : Formateurs et Effectif formé',
995      texte: "L'action de formation sera dispensée par : le DR " + sign
996    },
997    {
998      titre: 'Article 4 : Dispositions financières',
999      texte: "En contrepartie de cette action de formation, le Bénéficiaire s'acquittera des coûts suivants : " + (f.montant||'') + " euros."
1000    },
1001    {
1002      titre: 'Article 5 : Modalités de règlement',
1003      texte: "Une facture sera envoyée au bénéficiaire.\n\nLe paiement sera dû au moment de l'inscription."
1004    },
1005    {
1006      titre: 'Article 6 : Dédit ou abandon',
1007      texte: "Tout module commencé est dû.\n\nEn cas de dédit par le Bénéficiaire à moins de 2 jours francs avant le début de l'action mentionnée à l'article 1, l'organisme remboursera sur le coût total."
1008    },
1009    {
1010      titre: 'Article 8 : Différends éventuels',
1011      texte: "Dans l'hypothèse où un différend surviendrait portant sur la validité, l'interprétation, l'exécution ou la fin pour quelle que cause que ce soit des présentes dispositions, les parties conviennent de rechercher toute solution amiable.\n\nSi une contestation ou un différend ne peuvent être réglés à l'amiable, le Tribunal de Versailles sera seul compétent pour régler le litige."
1012    }
1013  ];
1014
1015  articles.forEach(function(art) {
1016    if (y > pageH - 55) {
1017      pageFooter();
1018      doc.addPage();
1019      pdfHeader(doc, null);
1020      y = 50;
1021    }
1022    doc.setDrawColor(208,221,232); doc.setLineWidth(0.3); doc.line(16, y, 194, y); y+=6;
1023    doc.setFont('helvetica','bold'); doc.setFontSize(10); doc.setTextColor(26,53,96);
1024    doc.text(art.titre, 16, y); y+=2;
1025    doc.setDrawColor(46,168,213); doc.setLineWidth(0.5);
1026    doc.line(16, y, Math.min(16 + doc.getTextWidth(art.titre), 194), y); y+=6;
1027    doc.setFont('helvetica','normal'); doc.setFontSize(9.5); doc.setTextColor(40,40,40);
1028    const ls = doc.splitTextToSize(art.texte, 178);
1029    doc.text(ls, 16, y); y += ls.length * 5.5 + 10;
1030  });
1031
1032  // Signatures
1033  y += 4;
1034  doc.setFont('helvetica','normal'); doc.setFontSize(9.5); doc.setTextColor(40,40,40);
1035  doc.text('Fait en double exemplaire,', 16, y); y+=8;
1036  doc.text('A ' + ville + ' ………………………………….., le ' + dateStr, 16, y); y+=12;
1037  doc.setFont('helvetica','bold'); doc.setFontSize(9.5); doc.setTextColor(26,53,96);
1038  doc.text('Pour Le Bénéficiaire', 46, y, {align:'center'});
1039  doc.text("Pour l'Organisme de formation", 160, y, {align:'center'}); y+=5;
1040  doc.setDrawColor(180,180,180); doc.setLineWidth(0.3);
1041  doc.rect(16, y, 60, 30); doc.rect(110, y, 80, 30);
1042  addSignature(doc, 112, y+4, 76, 20);
1043
1044  pageFooter();
1045  return doc;
1046}
1047
1048
1049function makeFacture(a, f) {
1050  const doc = new jsPDF();
1051  const pageW = doc.internal.pageSize.width;
1052  const adOrg  = f.adresseOrg || '4 rue des bouvières 74940 ANNECY';
1053  const siret  = f.siret  || '98532401100016';
1054  const ville  = f.ville  || 'Annecy';
1055  const sign   = f.signataire || 'Gregory Fejoz';
1056  const dateStr = new Date().toLocaleDateString('fr-FR', {day:'2-digit', month:'long', year:'numeric'});
1057  const factureNum = 'F-' + (f.num||'EL') + '-' + Math.random().toString(36).substr(2,4).toUpperCase();
1058
1059  pdfHeader(doc, 'FACTURE');
1060  pdfVilleDate(doc, ville, dateStr);
1061
1062  // Numéro facture
1063  doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.setTextColor(100,100,100);
1064  doc.text('N° ' + factureNum, 20, 88);
1065
1066  // Bloc émetteur / destinataire
1067  doc.setFillColor(228,244,251); doc.roundedRect(16, 96, 82, 38, 3, 3, 'F');
1068  doc.setFillColor(245,246,248); doc.roundedRect(106, 96, 88, 38, 3, 3, 'F');
1069
1070  doc.setFont('helvetica','bold'); doc.setFontSize(7.5); doc.setTextColor(46,168,213);
1071  doc.text('ÉMETTEUR', 22, 104);
1072  doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.setTextColor(26,43,60);
1073  doc.text('Endolight Formation', 22, 111);
1074  doc.text(adOrg, 22, 118, {maxWidth:72});
1075  doc.text('[email protected]', 22, 126);
1076
1077  doc.setFont('helvetica','bold'); doc.setFontSize(7.5); doc.setTextColor(46,168,213);
1078  doc.text('DESTINATAIRE', 112, 104);
1079  doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.setTextColor(26,43,60);
1080  doc.text('Dr ' + a.nom + ' ' + a.prenom, 112, 111);
1081  doc.text(a.adresse||'', 112, 118, {maxWidth:76});
1082
1083  // Tableau
1084  let y = 152;
1085  doc.setFillColor(26,53,96); doc.rect(16, y, 178, 11, 'F');
1086  doc.setFont('helvetica','bold'); doc.setFontSize(8.5); doc.setTextColor(255,255,255);
1087  doc.text('DÉSIGNATION', 22, y+7.5);
1088  doc.text('QTÉ', 138, y+7.5, {align:'center'});
1089  doc.text('P.U. TTC', 165, y+7.5, {align:'center'});
1090  doc.text('TOTAL TTC', 192, y+7.5, {align:'right'});
1091  y += 11;
1092
1093  doc.setFillColor(248,250,252); doc.rect(16, y, 178, 13, 'F');
1094  doc.setFont('helvetica','normal'); doc.setFontSize(9.5); doc.setTextColor(26,43,60);
1095  doc.text(f.titre||'', 22, y+9, {maxWidth:108});
1096  doc.text('1', 138, y+9, {align:'center'});
1097  doc.text(String(f.montant||0) + ' €', 165, y+9, {align:'center'});
1098  doc.text(String(f.montant||0) + ' €', 192, y+9, {align:'right'});
1099  y += 18;
1100
1101  doc.setDrawColor(208,221,232); doc.line(16, y, 194, y); y+=8;
1102  const ttc = parseFloat(f.montant)||0;
1103  const ht = ttc / 1.2;
1104  const tva = ttc - ht;
1105
1106  doc.setFont('helvetica','normal'); doc.setFontSize(9.5);
1107  doc.setFillColor(26,53,96); doc.rect(132, y-2, 62, 12, 'F');
1108  doc.setFont('helvetica','bold'); doc.setTextColor(255,255,255);
1109  doc.text('TOTAL TTC :', 148, y+6.5); doc.text(ttc.toFixed(2) + ' €', 192, y+6.5, {align:'right'});
1110  y += 20;
1111
1112  // Corps texte
1113  doc.setFont('helvetica','normal'); doc.setFontSize(10.5); doc.setTextColor(40,40,40);
1114  const corps = 'Je, soussigné ' + sign + ", représentant d'ENDOLIGHT FORMATION, numéro SIRET " + siret + ', domicilié au ' + adOrg + ',';
1115  doc.text(corps, 20, y+14, {maxWidth:170});
1116  doc.setFont('helvetica','normal'); doc.text('Atteste que le ', 20, y+28);
1117  const atW = doc.getTextWidth('Atteste que le ');
1118  doc.setFont('helvetica','bold'); doc.setTextColor(26,53,96);
1119  doc.text('Dr ' + a.nom + ' ' + a.prenom, 20 + atW, y+28);
1120  doc.setFont('helvetica','normal'); doc.setTextColor(40,40,40);
1121  doc.text('a réglé sa cotisation de ' + (f.montant||'') + ' euros TTC pour la formation « ' + (f.titre||'') + ' » du ' + (f.dates||''), 20, y+38, {maxWidth:170});
1122  doc.text('Fait à ' + ville, 20, y+52);
1123  // Cadre signature bleu clair
1124  doc.setFillColor(248,252,255); doc.roundedRect(18, y+56, 68, 16, 2, 2, 'F');
1125  doc.setDrawColor(168,216,238); doc.setLineWidth(0.4); doc.roundedRect(18, y+56, 68, 16, 2, 2, 'S');
1126  addSignature(doc, 21, y+58, 62, 14);
1127
1128  pdfFooter(doc, adOrg);
1129  return doc;
1130}
1131
1132function makeAttestation(a, f) {
1133  const doc = new jsPDF();
1134  const pageW = doc.internal.pageSize.width;
1135  const adOrg  = f.adresseOrg || '4 rue des bouvières 74940 ANNECY';
1136  const ville  = f.ville  || 'Annecy';
1137  const sign   = f.signataire || 'Gregory Fejoz';
1138  const dateStr = new Date().toLocaleDateString('fr-FR', {day:'2-digit', month:'long', year:'numeric'});
1139
1140  pdfHeader(doc, 'ATTESTATION DE PRÉSENCE');
1141  pdfVilleDate(doc, ville, dateStr);
1142
1143  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1144  doc.text("Je, soussigné(e) " + sign + ", représentant(e) d'ENDOLIGHT FORMATION,", 20, 92, {maxWidth:170});
1145  doc.text("atteste que le/la stagiaire :", 20, 103);
1146
1147  // Nom encadré
1148  doc.setFillColor(26,53,96); doc.roundedRect(50, 110, 110, 16, 4, 4, 'F');
1149  doc.setFont('helvetica','bold'); doc.setFontSize(13); doc.setTextColor(255,255,255);
1150  doc.text('Dr ' + a.nom + ' ' + a.prenom, pageW/2, 120.5, {align:'center'});
1151
1152  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1153  doc.text("a bien participé à la totalité de la formation :", 20, 136);
1154  doc.setFont('helvetica','bold'); doc.setFontSize(12); doc.setTextColor(46,168,213);
1155  doc.text('« ' + (f.titre||'') + ' »', 20, 148, {maxWidth:170});
1156
1157  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1158  doc.text("qui s'est déroulée le " + (f.dates||'') + " à " + (f.lieu||'') + ".", 20, 160, {maxWidth:170});
1159
1160  doc.setDrawColor(208,221,232); doc.setLineWidth(0.4); doc.line(20, 176, pageW-20, 176);
1161
1162  doc.text('Fait à ' + ville + ', le ' + dateStr, 20, 190);
1163  doc.setFont('helvetica','bold'); doc.setFontSize(9); doc.setTextColor(26,53,96);
1164  doc.text('Pour le Bénéficiaire', 48, 202, {align:'center'});
1165  doc.text("Pour l'Organisme", 162, 202, {align:'center'});
1166  doc.setDrawColor(180,180,180); doc.rect(16, 205, 60, 30); doc.rect(112, 205, 78, 30);
1167  addSignature(doc, 114, 207, 74, 20);
1168
1169  pdfFooter(doc, adOrg);
1170  return doc;
1171}
1172
1173function makeCertificat(a, f) {
1174  const doc = new jsPDF();
1175  const pageW = doc.internal.pageSize.width;
1176  const pageH = doc.internal.pageSize.height;
1177  const adOrg  = f.adresseOrg || '4 rue des bouvières 74940 ANNECY';
1178  const ville  = f.ville  || 'Annecy';
1179  const dateStr = new Date().toLocaleDateString('fr-FR', {day:'2-digit', month:'long', year:'numeric'});
1180
1181  // Header marine étendu
1182  doc.setFillColor(26,53,96); doc.rect(0, 0, pageW, 38, 'F');
1183  doc.setFont('helvetica','bold'); doc.setFontSize(18); doc.setTextColor(255,255,255);
1184  doc.text('ENDO', pageW/2 - 2, 22, {align:'right'});
1185  doc.setTextColor(46,168,213); doc.text('LIGHT', pageW/2 + 2, 22, {align:'left'});
1186  doc.setFont('helvetica','normal'); doc.setFontSize(7); doc.setTextColor(168,216,238); doc.setCharSpace(3);
1187  doc.text('FORMATION', pageW/2, 30, {align:'center'});
1188  doc.setCharSpace(0);
1189  doc.setFillColor(46,168,213); doc.rect(0, 38, pageW, 2.5, 'F');
1190
1191  // Titre centré
1192  doc.setFont('helvetica','bold'); doc.setFontSize(18); doc.setTextColor(26,53,96);
1193  doc.text('CERTIFICAT DE RÉALISATION', pageW/2, 68, {align:'center'});
1194  doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.setTextColor(120,120,120);
1195  doc.text('de formation professionnelle continue', pageW/2, 77, {align:'center'});
1196  doc.setFillColor(46,168,213); doc.rect(pageW/2-35, 82, 70, 2, 'F');
1197
1198  // Destinataire
1199  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1200  doc.text('Ce certificat est remis à', pageW/2, 98, {align:'center'});
1201  doc.setFont('helvetica','bold'); doc.setFontSize(18); doc.setTextColor(26,53,96);
1202  doc.text('Dr ' + a.nom + ' ' + a.prenom, pageW/2, 112, {align:'center'});
1203  doc.setFillColor(46,168,213); doc.rect(pageW/2-35, 116, 70, 1.5, 'F');
1204
1205  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1206  doc.text('en reconnaissance de sa participation complète à', pageW/2, 130, {align:'center'});
1207  doc.setFont('helvetica','bold'); doc.setFontSize(13); doc.setTextColor(46,168,213);
1208  doc.text('« ' + (f.titre||'') + ' »', pageW/2, 142, {align:'center', maxWidth:170});
1209
1210  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1211  doc.text('organisée par Endolight Formation', pageW/2, 155, {align:'center'});
1212  doc.text('les ' + (f.dates||'') + ' — ' + (f.lieu||''), pageW/2, 165, {align:'center'});
1213
1214  // Médaillon décoratif
1215  doc.setFillColor(228,244,251); doc.circle(pageW/2, 200, 22, 'F');
1216  doc.setDrawColor(46,168,213); doc.setLineWidth(1.5); doc.circle(pageW/2, 200, 22, 'S');
1217  doc.setFont('helvetica','bold'); doc.setFontSize(8); doc.setTextColor(26,53,96);
1218  doc.text('CERTIFIÉ', pageW/2, 197, {align:'center'});
1219  doc.text('CONFORME', pageW/2, 204, {align:'center'});
1220
1221  doc.setDrawColor(208,221,232); doc.setLineWidth(0.4); doc.line(20, 234, pageW-20, 234);
1222  doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.setTextColor(60,60,60);
1223  doc.text('Fait à ' + ville + ', le ' + dateStr, 20, 246);
1224  doc.setFont('helvetica','bold'); doc.setFontSize(9); doc.setTextColor(26,53,96);
1225  doc.text('Signature du responsable :', 20, 258);
1226  doc.setDrawColor(180,180,180); doc.rect(20, 261, 72, 30);
1227  addSignature(doc, 22, 263, 68, 22);
1228
1229  pdfFooter(doc, adOrg);
1230  return doc;
1231}
1232
1233
1234
1235function makeInscription(a, f) {
1236  const doc = new jsPDF();
1237  const pageW = doc.internal.pageSize.width;
1238  const adOrg  = f.adresseOrg || '4 rue des bouvières 74940 ANNECY';
1239  const ville  = f.ville  || 'Annecy';
1240  const dateStr = new Date().toLocaleDateString('fr-FR', {day:'2-digit', month:'long', year:'numeric'});
1241
1242  pdfHeader(doc, "ATTESTATION D'INSCRIPTION");
1243  pdfVilleDate(doc, ville, dateStr);
1244
1245  let y = 92;
1246  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1247  doc.text("Je soussigné, Directeur d'Endolight Formation, certifie que", 20, y, {maxWidth:170}); y+=18;
1248
1249  // Nom apprenant
1250  doc.setFont('helvetica','bold'); doc.setFontSize(13); doc.setTextColor(26,53,96);
1251  doc.text('Dr ' + a.nom + ' ' + a.prenom, pageW/2, y, {align:'center'}); y+=8;
1252
1253  // Adresse
1254  if (a.adresse) {
1255    doc.setFont('helvetica','bold'); doc.setFontSize(11); doc.setTextColor(26,53,96);
1256    const addrLines = doc.splitTextToSize(a.adresse, 160);
1257    doc.text(addrLines, pageW/2, y, {align:'center'}); y += addrLines.length * 6 + 4;
1258  }
1259  y += 10;
1260
1261  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1262  doc.text("Est inscrit(e) dans notre établissement pour y suivre", pageW/2, y, {align:'center'}); y+=8;
1263
1264  doc.setFont('helvetica','bold'); doc.setFontSize(12); doc.setTextColor(26,53,96);
1265  const titreLines = doc.splitTextToSize(f.titre||'', 160);
1266  doc.text(titreLines, pageW/2, y, {align:'center'}); y += titreLines.length * 7 + 4;
1267
1268  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1269  if (f.duree) {
1270    doc.text('De ' + f.duree + ' en ' + (f.type||'présentiel').toLowerCase(), pageW/2, y, {align:'center'}); y+=8;
1271  }
1272  doc.setFont('helvetica','normal'); doc.text('Le ', pageW/2 - doc.getTextWidth('Le ')/2 - doc.getTextWidth(f.dates||'')/2, y);
1273  doc.setFont('helvetica','bold'); doc.setTextColor(26,53,96);
1274  doc.text(f.dates||'', pageW/2 + doc.getTextWidth('Le ')/2, y, {align:'left'}); y+=18;
1275
1276  doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(60,60,60);
1277  doc.text("En foi de quoi, je lui délivre la présente attestation.", pageW/2, y, {align:'center'}); y+=18;
1278
1279  doc.setFont('helvetica','normal'); doc.text('Fait à ' + ville + ', ', 20, y);
1280  const faitW = doc.getTextWidth('Fait à ' + ville + ', ');
1281  doc.setFont('helvetica','bold'); doc.setTextColor(26,53,96);
1282  doc.text('le ' + dateStr, 20 + faitW, y); y+=16;
1283  addSignature(doc, 20, y, 80, 22);
1284
1285  pdfFooter(doc, adOrg);
1286  return doc;
1287}
1288
1289// ── UTILS ──
1290function showNotif(msg) {
1291  const n = document.getElementById('notif');
1292  n.textContent = msg; n.classList.add('show');
1293  setTimeout(() => n.classList.remove('show'), 3500);
1294}
1295
1296// ── MODAL MODIFIER APPRENANT ──
1297let modalEditApprenantId = null;
1298
1299function ouvrirModalEditApprenant(id) {
1300  const a = apprenants.find(x => x.id === id);
1301  if (!a) return;
1302  modalEditApprenantId = id;
1303  const af = a.fields;
1304
1305  document.getElementById('me-titre').textContent =
1306    `Dr ${af.Prénom||''} ${af.Nom||''}`;
1307  document.getElementById('me-prenom').value  = af.Prénom  || '';
1308  document.getElementById('me-nom').value     = af.Nom     || '';
1309  document.getElementById('me-email').value   = af.Email   || '';
1310  document.getElementById('me-adresse').value = af.Adresse || '';
1311
1312  // Remplir le select formations
1313  const sel = document.getElementById('me-formation');
1314  sel.innerHTML = formations.map(f =>
1315    `<option value="${f.id}" ${f.id === af['Formation ID'] ? 'selected' : ''}>${f.fields.Titre||'Sans titre'}</option>`
1316  ).join('');
1317
1318  document.getElementById('modal-edit-overlay').style.display = 'flex';
1319}
1320
1321async function sauvegarderApprenant() {
1322  const prenom  = document.getElementById('me-prenom').value.trim();
1323  const nom     = document.getElementById('me-nom').value.trim();
1324  if (!prenom || !nom) { showNotif('⚠ Prénom et nom obligatoires'); return; }
1325
1326  const formId  = document.getElementById('me-formation').value;
1327  const formation = formations.find(f => f.id === formId);
1328
1329  try {
1330    showNotif('⏳ Enregistrement…');
1331    await atPatch('Apprenants', modalEditApprenantId, {
1332      Prénom:        prenom,
1333      Nom:           nom,
1334      Email:         document.getElementById('me-email').value.trim(),
1335      Adresse:       document.getElementById('me-adresse').value.trim(),
1336      'Formation ID': formId,
1337      Formation:     formation ? formation.fields.Titre : '',
1338    });
1339    await loadApprenants();
1340    fermerModalEdit();
1341    showNotif('✓ Apprenant mis à jour !');
1342  } catch(e) { showNotif('❌ Erreur: ' + e.message); }
1343}
1344
1345function fermerModalEdit(e) {
1346  if (!e || e.target === document.getElementById('modal-edit-overlay')) {
1347    document.getElementById('modal-edit-overlay').style.display = 'none';
1348    modalEditApprenantId = null;
1349  }
1350}
1351
1352// ── MODAL DOCUMENTS FORMATION ──
1353let modalFormationId = null;
1354
1355
1356const DOC_FORMATION_LABELS = {
1357  livret:        "📚 Livret d'accueil",
1358  reglement:     "📜 Règlement intérieur",
1359  programme:     "📋 Programme de formation",
1360  cgv:           "📄 Conditions générales",
1361  questionnaire: "📝 Questionnaire pré-formation",
1362};
1363
1364function ouvrirModalFormationDocs(id) {
1365  modalFormationId = id;
1366  pendingUploads = {};
1367  const f = formations.find(x => x.id === id);
1368  if (!f) return;
1369
1370  document.getElementById('mf-titre').textContent = f.fields.Titre || '';
1371
1372  // Reset inputs
1373  ['livret','reglement','programme','cgv','questionnaire'].forEach(k => {
1374    document.getElementById(`up-${k}`).value = '';
1375    document.getElementById(`info-${k}`).textContent = 'Aucun fichier sélectionné';
1376    document.getElementById(`info-${k}`).classList.remove('ok');
1377  });
1378
1379  // Afficher docs existants
1380  afficherDocsExistants(f);
1381  document.getElementById('modal-formation-overlay').style.display = 'flex';
1382}
1383
1384function afficherDocsExistants(f) {
1385  let docsJson = {};
1386  try { docsJson = JSON.parse(f.fields['Docs JSON Formation'] || '{}'); } catch(e) {}
1387  const keys = Object.keys(docsJson);
1388  const el = document.getElementById('mf-docs-list');
1389  if (!keys.length) { el.innerHTML = ''; return; }
1390
1391  let html = '<div class="card-title" style="font-size:.75rem;margin-bottom:10px;border:none;padding:0;">Documents déjà uploadés</div>';
1392  keys.forEach(key => {
1393    const doc = docsJson[key];
1394    const label = DOC_FORMATION_LABELS[key] || key;
1395    html += '<div class="doc-existant">'
1396      + '<span>' + label + '</span>'
1397      + '<button class="upload-btn" style="padding:4px 10px;font-size:.7rem;" onclick="telechargerDocLocal(\"' + key + '\")">⬇</button>'
1398      + '<button class="del-doc" onclick="supprimerDocFormation(\"' + key + '\")">✕</button>'
1399      + '</div>';
1400  });
1401  html += '<div style="height:1px;background:var(--border);margin:14px 0;"></div>';
1402  el.innerHTML = html;
1403}
1404
1405function telechargerDocLocal(key) {
1406  const f = formations.find(x => x.id === modalFormationId);
1407  let docsJson = {};
1408  try { docsJson = JSON.parse(f.fields['Docs JSON Formation'] || '{}'); } catch(e) {}
1409  const doc = docsJson[key];
1410  if (!doc) return;
1411  const a = document.createElement('a');
1412  a.href = doc.data;
1413  a.download = doc.name || key + '.pdf';
1414  a.click();
1415}
1416
1417
1418
1419async function sauvegarderDocsFormation() {
1420  if (!Object.keys(pendingUploads).length) { showNotif('⚠ Aucun fichier sélectionné'); return; }
1421  
1422  const btn = document.getElementById('btn-save-docs');
1423  btn.disabled = true;
1424  btn.textContent = '⏳ Envoi…';
1425
1426  try {
1427    const f = formations.find(x => x.id === modalFormationId);
1428    
1429    // Récupérer les docs existants depuis le champ JSON
1430    let docsJson = {};
1431    try { docsJson = JSON.parse(f.fields['Docs JSON Formation'] || '{}'); } catch(e) {}
1432    
1433    // Fusionner avec les nouveaux (écraser si même clé)
1434    Object.entries(pendingUploads).forEach(([key, val]) => {
1435      docsJson[key] = { data: val.data, name: val.name };
1436    });
1437
1438    await atPatch('Formations', modalFormationId, {
1439      'Docs JSON Formation': JSON.stringify(docsJson)
1440    });
1441
1442    await loadFormations();
1443    const updatedF = formations.find(x => x.id === modalFormationId);
1444    if (updatedF) afficherDocsExistants(updatedF);
1445    
1446    pendingUploads = {};
1447    ['livret','reglement','programme','cgv','questionnaire'].forEach(k => {
1448      document.getElementById('up-' + k).value = '';
1449      document.getElementById('info-' + k).textContent = 'Aucun fichier sélectionné';
1450      document.getElementById('info-' + k).classList.remove('ok');
1451    });
1452    showNotif('✓ Documents enregistrés !');
1453  } catch(e) { 
1454    showNotif('❌ Erreur: ' + e.message); 
1455  } finally {
1456    btn.disabled = false;
1457    btn.textContent = '💾 Enregistrer';
1458  }
1459}
1460
1461
1462
1463async function supprimerDocFormation(key) {
1464  if (!confirm('Supprimer ce document ?')) return;
1465  const f = formations.find(x => x.id === modalFormationId);
1466  let docsJson = {};
1467  try { docsJson = JSON.parse(f.fields['Docs JSON Formation'] || '{}'); } catch(e) {}
1468  delete docsJson[key];
1469  try {
1470    await atPatch('Formations', modalFormationId, { 'Docs JSON Formation': JSON.stringify(docsJson) });
1471    await loadFormations();
1472    const updatedF = formations.find(x => x.id === modalFormationId);
1473    if (updatedF) afficherDocsExistants(updatedF);
1474    showNotif('✓ Document supprimé');
1475  } catch(e) { showNotif('❌ Erreur: ' + e.message); }
1476}
1477
1478function fermerModalFormation(e) {
1479  if (!e || e.target === document.getElementById('modal-formation-overlay')) {
1480    document.getElementById('modal-formation-overlay').style.display = 'none';
1481    modalFormationId = null;
1482    pendingUploads = {};
1483  }
1484}
1485
1486// ── MODAL AJOUT DOCS ──
1487const DOC_LABELS = {convocation:'📅 Convocation', convention:'📋 Convention', facture:'🧾 Facture', attestation:'✅ Attestation', certificat:'🏆 Certificat', inscription:"📝 Attestation d'inscription"};
1488let modalApprenantId = null;
1489let modalSelectedDocs = [];
1490
1491function ouvrirModalDocs(id) {
1492  const a = apprenants.find(x => x.id === id);
1493  if (!a) return;
1494  modalApprenantId = id;
1495  modalSelectedDocs = [];
1496
1497  // Docs déjà générés
1498  let docsData = {};
1499  try { docsData = JSON.parse(a.fields['Docs JSON'] || '{}'); } catch(e) {}
1500  const dejaDocs = Object.keys(docsData);
1501
1502  // Afficher uniquement les docs pas encore générés
1503  const tousLesDocs = ['convocation','convention','facture','attestation','certificat'];
1504  const restants = tousLesDocs.filter(d => !dejaDocs.includes(d));
1505
1506  document.getElementById('modal-apprenant-name').textContent =
1507    `Dr ${a.fields.Prénom||''} ${a.fields.Nom||''} — choisissez les documents à ajouter :`;
1508
1509  const grid = document.getElementById('modal-docs-grid');
1510  if (restants.length === 0) {
1511    grid.innerHTML = '<p style="color:var(--muted);font-size:.82rem;">Tous les documents ont déjà été générés.</p>';
1512  } else {
1513    grid.innerHTML = restants.map(d => `
1514      <label class="doc-check" id="mc-${d}" onclick="toggleModalDoc('${d}',this)">
1515        ${DOC_LABELS[d]}
1516      </label>`).join('');
1517  }
1518
1519  document.getElementById('modal-overlay').style.display = 'flex';
1520}
1521
1522function toggleModalDoc(doc, el) {
1523  el.classList.toggle('active');
1524  if (modalSelectedDocs.includes(doc)) modalSelectedDocs = modalSelectedDocs.filter(d => d !== doc);
1525  else modalSelectedDocs.push(doc);
1526}
1527
1528function fermerModal(e) {
1529  if (!e || e.target === document.getElementById('modal-overlay')) {
1530    document.getElementById('modal-overlay').style.display = 'none';
1531    modalApprenantId = null;
1532    modalSelectedDocs = [];
1533  }
1534}
1535
1536async function ajouterNouveauxDocs() {
1537  if (!modalSelectedDocs.length) { showNotif('⚠ Sélectionnez au moins un document'); return; }
1538  const a = apprenants.find(x => x.id === modalApprenantId);
1539  if (!a) return;
1540
1541  const formation = formations.find(f => f.id === a.fields['Formation ID']);
1542  if (!formation) { showNotif('⚠ Formation introuvable'); return; }
1543
1544  showNotif('⏳ Génération en cours…');
1545
1546  // Récupérer les docs existants
1547  let docsData = {};
1548  try { docsData = JSON.parse(a.fields['Docs JSON'] || '{}'); } catch(e) {}
1549  const docsExistants = (a.fields.Documents || '').split(',').filter(Boolean);
1550
1551  // Générer les nouveaux PDFs
1552  const apprenant = {
1553    prenom: a.fields.Prénom || '',
1554    nom:    a.fields.Nom    || '',
1555    email:  a.fields.Email  || '',
1556    adresse:a.fields.Adresse|| '',
1557  };
1558  const ff = formation.fields;
1559
1560  modalSelectedDocs.forEach(d => {
1561    const pdf = generatePDF(d, apprenant, ff);
1562    docsData[d] = pdf.output('datauristring');
1563  });
1564
1565  // Mettre à jour dans Airtable
1566  const tousLesDocs = [...new Set([...docsExistants, ...modalSelectedDocs])];
1567  try {
1568    await atPatch('Apprenants', a.id, {
1569      Documents:   tousLesDocs.join(','),
1570      'Docs JSON': JSON.stringify(docsData),
1571    });
1572
1573    // Log dans Documents (les PDF sont déjà dans la fiche : un échec ici ne doit pas faire croire le contraire)
1574    const erreurJournal = await journaliserDocs(`${apprenant.prenom} ${apprenant.nom}`, modalSelectedDocs, ff.Titre || '');
1575
1576    const nbDocs = modalSelectedDocs.length; // à lire avant fermerModal(), qui vide la sélection
1577    await loadApprenants();
1578    fermerModal();
1579    showNotif(erreurJournal
1580      ? `⚠ ${nbDocs} document(s) ajouté(s), mais journal Documents non mis à jour : ${erreurJournal}`
1581      : `✓ ${nbDocs} document(s) ajouté(s) avec succès !`);
1582  } catch(e) {
1583    showNotif('❌ Erreur: ' + e.message);
1584  }
1585}
1586
1587// ── AUTHENTIFICATION ──
1588// Le mot de passe est vérifié par le serveur (variable ADMIN_PASSWORD sur Netlify),
1589// qui renvoie une session signée valable 12 h, gardée le temps de l'onglet.
1590function montrerErreurConnexion(message) {
1591  const err = document.getElementById('login-error');
1592  err.textContent = message;
1593  err.style.display = 'block';
1594  setTimeout(() => err.style.display = 'none', 4000);
1595}
1596
1597function afficherConnexion(message) {
1598  ecrireSession('');
1599  document.getElementById('login-overlay').style.display = '';
1600  if (message) montrerErreurConnexion(message);
1601}
1602
1603async function verifierMdp() {
1604  const champ = document.getElementById('login-input');
1605  try {
1606    const r = await fetch(API_CONNEXION, {
1607      method: 'POST',
1608      headers: { 'Content-Type': 'application/json' },
1609      body: JSON.stringify({ motDePasse: champ.value }),
1610    });
1611    const data = await r.json().catch(() => ({}));
1612    if (!r.ok || !data.session) {
1613      throw new Error(r.status === 401 ? 'Mot de passe incorrect'
1614        : r.status === 429 ? 'Trop de tentatives : réessayez dans une minute'
1615        : ((data.error && data.error.message) || `Serveur indisponible (${r.status})`));
1616    }
1617    ecrireSession(data.session);
1618    champ.value = '';
1619    document.getElementById('login-overlay').style.display = 'none';
1620    loadFormations();
1621  } catch(e) {
1622    champ.value = '';
1623    montrerErreurConnexion(e.message);
1624  }
1625}
1626
1627// ── INIT ──
1628if (lireSession()) {
1629  document.getElementById('login-overlay').style.display = 'none';
1630  loadFormations();
1631}
1632</script>
1632
1633</body>
1634</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.