Une seule URL active toutes les fonctions :
• Chaque inscription s'enregistre automatiquement dans un tableur Google Sheets
• Un e-mail de notification est envoye automatiquement a
formation@canoe-nouvelle-aquitaine.fr
• Une copie est envoyee au candidat
•
Le calendrier des formations est synchronise pour tous les visiteurs, meme si le site change d'adresse
•
Convocations : depuis un evenement du calendrier, envoyez une convocation par e-mail a tous les inscrits correspondants (+ leur tuteur en copie)
•
Alerte automatique : si vous modifiez la date d'un evenement pour lequel des convocations ont deja ete envoyees, vous recevez un e-mail avec un lien pour renvoyer une convocation a jour en un clic
• Tout passe par votre compte Google — aucun service tiers
Mise en place (une seule fois, environ 5 minutes) :
Etape 1 — Creez un Google Sheet
Allez sur
sheets.google.com → creez un nouveau tableau → nommez-le "Inscriptions CRCK 2026"
En ligne 1, ajoutez ces en-tetes (copiez-collez) :
Date | Formation | Pole | Nom | Prenom | DDN | Sexe | Licence | Club | Email | Telephone | Tuteur | PC_EC | PC_EV | PSC1 | Natation | Manquants | Tuteur_Email
Les feuilles "Calendrier" et "Convocations" seront creees automatiquement par le script, pas besoin de les preparer.
Etape 2 — Ajoutez le script
Dans le tableau : menu
Extensions → Apps Script
Effacez tout le contenu et collez ce script (ou utilisez le fichier texte fourni pour un copier-coller fiable) :
function getInscSheet(){
return SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
}
function getCalSheet(){
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName('Calendrier');
if(!sh){
sh=ss.insertSheet('Calendrier');
sh.appendRow(['ID','Annee','Mois','Jour','Titre','Description','Couleur','Lieu','Heure']);
}
return sh;
}
function getConvocSheet(){
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName('Convocations');
if(!sh){
sh=ss.insertSheet('Convocations');
sh.appendRow(['EventID','Titre','Date session','Emails envoyes','Date envoi']);
}
return sh;
}
function findRegistrations(formationText){
var sheet=getInscSheet();
var vals=sheet.getDataRange().getValues();
var needle=(formationText||'').toLowerCase();
var out=[];
for(var i=1;i<vals.length;i++){
var row=vals[i];
var formation=String(row[1]||'').toLowerCase();
if(!needle || formation.indexOf(needle)>-1 || needle.indexOf(formation)>-1){
out.push({nom:row[3],prenom:row[4],email:row[9],tutemail:row[17]||''});
}
}
return out;
}
function doPost(e){
try{
var d=JSON.parse(e.postData.contents);
if(d.type==='calendar_add'){
var sh=getCalSheet();
var id=String(new Date().getTime())+Math.floor(Math.random()*1000);
sh.appendRow([id,d.year,d.month,d.day||'',d.title,d.desc||'',d.color||'',d.lieu||'',d.heure||'']);
return ContentService.createTextOutput(JSON.stringify({ok:true,id:id})).setMimeType(ContentService.MimeType.JSON);
}
if(d.type==='calendar_delete'){
var sh=getCalSheet();
var vals=sh.getDataRange().getValues();
for(var i=1;i<vals.length;i++){
if(String(vals[i][0])===String(d.id)){ sh.deleteRow(i+1); break; }
}
return ContentService.createTextOutput(JSON.stringify({ok:true})).setMimeType(ContentService.MimeType.JSON);
}
if(d.type==='calendar_edit'){
var sh=getCalSheet();
var vals=sh.getDataRange().getValues();
var found=false;
for(var i=1;i<vals.length;i++){
if(String(vals[i][0])===String(d.id)){
sh.getRange(i+1,2).setValue(d.year);
sh.getRange(i+1,3).setValue(d.month);
sh.getRange(i+1,4).setValue(d.day||'');
sh.getRange(i+1,5).setValue(d.title);
if(d.desc!==undefined) sh.getRange(i+1,6).setValue(d.desc);
if(d.color!==undefined) sh.getRange(i+1,7).setValue(d.color);
if(d.lieu!==undefined) sh.getRange(i+1,8).setValue(d.lieu);
if(d.heure!==undefined) sh.getRange(i+1,9).setValue(d.heure);
found=true;
break;
}
}
if(found){
var csh=getConvocSheet();
var cvals=csh.getDataRange().getValues();
for(var j=1;j<cvals.length;j++){
if(String(cvals[j][0])===String(d.id)){
var approveUrl=ScriptApp.getService().getUrl()+'?action=approve_resend&id='+encodeURIComponent(d.id);
MailApp.sendEmail('formation@canoe-nouvelle-aquitaine.fr',
'[CRCK NA] Date modifiee - validation requise',
'La date de la session "'+d.title+'" a ete modifiee.\n\n'
+'Des convocations avaient deja ete envoyees a : '+cvals[j][3]+'\n\n'
+'Nouvelle date : '+d.dateLabel+'\n\n'
+'Pour renvoyer automatiquement une convocation a jour a ces memes personnes, cliquez sur ce lien :\n'
+approveUrl);
break;
}
}
}
return ContentService.createTextOutput(JSON.stringify({ok:found})).setMimeType(ContentService.MimeType.JSON);
}
if(d.type==='send_convocation'){
var recipients=d.recipients||[];
var subject='[CRCK NA] Convocation - '+d.title+' - '+d.dateLabel;
var sentEmails=[];
recipients.forEach(function(r){
if(!r.email) return;
var body='Bonjour '+r.prenom+' '+r.nom+',\n\n'
+'Vous etes convoque(e) a la session suivante :\n\n'
+'Formation : '+d.title+'\n'
+'Date : '+d.dateLabel+'\n'
+(d.heure?('Heure : '+d.heure+'\n'):'')
+(d.lieu?('Lieu : '+d.lieu+'\n'):'')
+(d.desc?('Details : '+d.desc+'\n'):'')
+'\nMerci de confirmer votre presence aupres du CRCK Nouvelle-Aquitaine.\n\n'
+'Cordialement,\nCRCK Nouvelle-Aquitaine';
var opts={};
if(r.tutemail) opts.cc=r.tutemail;
MailApp.sendEmail(r.email,subject,body,opts);
sentEmails.push(r.email+(r.tutemail?(' (cc: '+r.tutemail+')'):''));
});
var csh=getConvocSheet();
csh.appendRow([d.eventId,d.title,d.dateLabel,sentEmails.join('; '),new Date()]);
return ContentService.createTextOutput(JSON.stringify({ok:true,count:sentEmails.length})).setMimeType(ContentService.MimeType.JSON);
}
var sheet=getInscSheet();
sheet.appendRow([
new Date(),d.formation||'',d.pole||'N/A',
d.nom,d.prenom,d.ddn,d.sexe,
d.licence,d.club,d.email,d.tel,
d.tuteur,d.pc_ec,d.pc_ev,d.psc1,d.natation,
d.manquants||'aucun',d.tutemail||''
]);
var body='Nouvelle inscription recue\n\n'
+'Formation : '+d.formation+'\n'
+'Nom : '+d.nom+' '+d.prenom+'\n'
+'Club : '+d.club+'\n'
+'E-mail : '+d.email+'\n'
+'Tel : '+d.tel+'\n'
+'Tuteur : '+d.tuteur+'\n'
+'PC EC : '+d.pc_ec+' | PC EV : '+d.pc_ev+'\n'
+'PSC1 : '+d.psc1+' | Natation : '+d.natation+'\n'
+(d.manquants?'MANQUANTS : '+d.manquants+'\n':'')
+'\nVoir le tableau : '+SpreadsheetApp.getActiveSpreadsheet().getUrl();
MailApp.sendEmail('formation@canoe-nouvelle-aquitaine.fr',
'[CRCK NA] Inscription '+d.formation+' - '+d.nom+' '+d.prenom,
body,{cc:d.email||''});
return ContentService.createTextOutput('OK');
}catch(err){
MailApp.sendEmail('formation@canoe-nouvelle-aquitaine.fr',
'[CRCK NA] Erreur',err.toString());
return ContentService.createTextOutput('ERROR');
}
}
function resendConvocation(id){
var csh=getConvocSheet();
var cvals=csh.getDataRange().getValues();
for(var i=1;i<cvals.length;i++){
if(String(cvals[i][0])===String(id)){
var emails=String(cvals[i][3]||'').split(';');
var title=cvals[i][1];
var newDateLabel=cvals[i][2];
var newHeure='', newLieu='';
var csh2=getCalSheet();
var vals2=csh2.getDataRange().getValues();
for(var k=1;k<vals2.length;k++){
if(String(vals2[k][0])===String(id)){
newDateLabel=(vals2[k][3]?vals2[k][3]+'/':'')+(parseInt(vals2[k][2],10)+1)+'/'+vals2[k][1];
newLieu=vals2[k][7]||''; newHeure=vals2[k][8]||'';
break;
}
}
emails.forEach(function(em){
var addr=em.split('(')[0].trim();
if(addr){
MailApp.sendEmail(addr,'[CRCK NA] Convocation mise a jour - '+title,
'Bonjour,\n\nLa date de la session "'+title+'" a change.\n\nNouvelle date : '+newDateLabel+'\n'
+(newHeure?('Heure : '+newHeure+'\n'):'')
+(newLieu?('Lieu : '+newLieu+'\n'):'')
+'\nCordialement,\nCRCK Nouvelle-Aquitaine');
}
});
csh.getRange(i+1,3).setValue(newDateLabel);
csh.getRange(i+1,5).setValue(new Date());
return {ok:true};
}
}
return {ok:false,error:'Aucune convocation trouvee pour cet evenement.'};
}
function doGet(e){
var action=e && e.parameter && e.parameter.action;
if(action==='calendar'){
var sh=getCalSheet();
var vals=sh.getDataRange().getValues();
var rows=[];
for(var i=1;i<vals.length;i++){
if(!vals[i][0])continue;
rows.push({id:vals[i][0],year:vals[i][1],month:vals[i][2],day:vals[i][3],title:vals[i][4],desc:vals[i][5],color:vals[i][6],lieu:vals[i][7],heure:vals[i][8]});
}
return ContentService.createTextOutput(JSON.stringify(rows)).setMimeType(ContentService.MimeType.JSON);
}
if(action==='match_registrations'){
var rows2=findRegistrations(e.parameter.formation||'');
return ContentService.createTextOutput(JSON.stringify(rows2)).setMimeType(ContentService.MimeType.JSON);
}
if(action==='approve_resend'){
var result=resendConvocation(e.parameter.id||'');
return HtmlService.createHtmlOutput('<h2>'+(result.ok?'Convocations renvoyees avec succes.':'Erreur : '+result.error)+'</h2>');
}
return ContentService.createTextOutput('CRCK NA - Actif');
}
Etape 3 — Deployez (ou re-deployez si vous l'aviez deja fait avant)
Cliquez
Deployer → Nouveau deploiement (un nouveau deploiement est necessaire pour que la mise a jour du calendrier prenne effet)
• Type :
Application Web
• Executer en tant que :
Moi (votre compte)
• Qui peut acceder :
Tout le monde
Cliquez
Deployer → autorisez → copiez l'URL (elle change a chaque nouveau deploiement, pensez a la remettre a jour ci-dessous)
Etape 4 — Collez l'URL ci-dessous et cliquez Enregistrer