code
<!doctype html>
<html lang= »fr »>
<head>
<meta charset= »UTF-8″>
<meta name= »viewport » content= »width=device-width, initial-scale=1.0″>
<title>Inscription Réunion Parents</title>
<script src= »/_sdk/data_sdk.js »></script>
<script src= »/_sdk/element_sdk.js »></script>
<script src= »https://cdn.tailwindcss.com »></script>
<style>
body {
box-sizing: border-box;
height: 100%;
}
html {
height: 100%;
}
.form-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.loading-spinner {
border: 3px solid #f3f4f6;
border-top: 3px solid #3b82f6;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.toast {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
}
.toast.show {
transform: translateX(0);
}
</style>
<style>@view-transition { navigation: auto; }</style>
</head>
<body>
<main class= »form-container min-h-full py-8 px-4″>
<div class= »max-w-2xl mx-auto »>
<div class= »bg-white rounded-lg shadow-xl p-8″>
<header class= »text-center mb-8″>
<h1 id= »titre-formulaire » class= »text-3xl font-bold text-gray-800 mb-4″>Inscription à la réunion des parents</h1>
<div class= »bg-blue-50 border-l-4 border-blue-400 p-4 mb-6″>
<div class= »flex »>
<div class= »ml-3″>
<p class= »text-sm text-blue-700″><strong>Date :</strong> <span id= »date-reunion »>Mardi 10 décembre 2024</span><br><strong>Lieu :</strong> <span id= »lieu-reunion »>Salle polyvalente de l’école</span></p>
</div>
</div>
</div>
</header>
<div id= »no-slots-message » class= »hidden bg-red-50 border border-red-200 rounded-md p-6 text-center »>
<div class= »flex flex-col items-center »>
<svg class= »h-12 w-12 text-red-400 mb-4″ fill= »none » viewbox= »0 0 24 24″ stroke= »currentColor »><path stroke-linecap= »round » stroke-linejoin= »round » stroke-width= »2″ d= »M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z » />
</svg>
<h2 class= »text-xl font-bold text-red-800 mb-2″>Inscriptions complètes</h2>
<p class= »text-red-700″>Tous les créneaux de rendez-vous sont désormais complets.</p>
<p class= »text-red-700 mt-2″>Il n’est plus possible de s’inscrire pour cette réunion.</p>
</div>
</div>
<form id= »inscription-form » class= »space-y-6″>
<div class= »grid grid-cols-1 md:grid-cols-2 gap-6″>
<div><label for= »nom-parent » class= »block text-sm font-medium text-gray-700 mb-2″>Nom du parent *</label> <input type= »text » id= »nom-parent » name= »nom-parent » required class= »w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent »>
</div>
<div><label for= »prenom-parent » class= »block text-sm font-medium text-gray-700 mb-2″>Prénom du parent *</label> <input type= »text » id= »prenom-parent » name= »prenom-parent » required class= »w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent »>
</div>
</div>
<div class= »grid grid-cols-1 md:grid-cols-2 gap-6″>
<div><label for= »nom-enfant » class= »block text-sm font-medium text-gray-700 mb-2″>Nom de l’enfant *</label> <input type= »text » id= »nom-enfant » name= »nom-enfant » required class= »w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent »>
</div>
<div><label for= »classe-enfant » class= »block text-sm font-medium text-gray-700 mb-2″>Classe *</label> <input type= »text » id= »classe-enfant » name= »classe-enfant » value= »P4A » readonly class= »w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-100 text-gray-600 cursor-not-allowed »>
</div>
</div>
<div><label for= »creneau-prefere » class= »block text-sm font-medium text-gray-700 mb-2″>Créneau préféré *</label> <select id= »creneau-prefere » name= »creneau-prefere » required class= »w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent »> <option value= » »>Sélectionnez un créneau disponible</option> </select>
<p class= »text-sm text-gray-600 mt-1″>Les créneaux déjà réservés ne sont pas affichés</p>
</div>
<div class= »bg-gray-50 p-4 rounded-md »>
<div class= »flex items-start »><input type= »checkbox » id= »conditions » name= »conditions » required class= »mt-1 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded »> <label for= »conditions » class= »ml-3 text-sm text-gray-700″> J’accepte que mes données personnelles soient utilisées dans le cadre de l’organisation de cette réunion et je confirme ma participation. * </label>
</div>
</div>
<div class= »text-center »><button type= »submit » id= »submit-btn » class= »bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg transition duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center mx-auto »> <span id= »submit-text »>Confirmer mon inscription</span>
<div id= »submit-spinner » class= »loading-spinner ml-2 hidden »></div></button>
</div>
</form>
<div id= »success-message » class= »hidden mt-8 bg-green-50 border border-green-200 rounded-md p-4″>
<div class= »flex »>
<div class= »flex-shrink-0″>
<svg class= »h-5 w-5 text-green-400″ viewbox= »0 0 20 20″ fill= »currentColor »><path fill-rule= »evenodd » d= »M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z » clip-rule= »evenodd » />
</svg>
</div>
<div class= »ml-3″>
<p id= »message-confirmation » class= »text-sm font-medium text-green-800″>Votre inscription a été enregistrée avec succès !</p>
<p id= »rdv-details » class= »text-sm text-green-700 mt-1 font-medium »>Rendez-vous confirmé le jeudi 18 décembre 2025</p>
</div>
</div>
</div>
</div>
</div>
</main>
<div id= »toast » class= »toast bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg »><span id= »toast-message »>Une erreur est survenue</span>
</div>
<script>
let currentRecordCount = 0;
let reservedSlots = new Set();
// Génération des 15 créneaux de 10 minutes avec pauses
const generateTimeSlots = () => {
const slots = [];
let startHour = 15;
let startMinute = 0;
for (let i = 0; i < 15; i++) {
// Ajouter une pause de 10 minutes après chaque 5 rendez-vous
if (i > 0 && i % 5 === 0) {
startMinute += 10;
if (startMinute >= 60) {
startHour += 1;
startMinute -= 60;
}
}
const endMinute = startMinute + 10;
const endHour = endMinute >= 60 ? startHour + 1 : startHour;
const finalEndMinute = endMinute >= 60 ? endMinute – 60 : endMinute;
const startTime = `${startHour}h${startMinute.toString().padStart(2, ‘0’)}`;
const endTime = `${endHour}h${finalEndMinute.toString().padStart(2, ‘0’)}`;
slots.push(`${startTime} – ${endTime}`);
startMinute += 10;
if (startMinute >= 60) {
startHour += 1;
startMinute -= 60;
}
}
return slots;
};
const allTimeSlots = generateTimeSlots();
const defaultConfig = {
titre_formulaire: « Inscription à la réunion des parents »,
date_reunion: « Jeudi 18 décembre 2025 »,
lieu_reunion: « En classe »,
message_confirmation: « Votre inscription a été enregistrée avec succès ! »,
background_color: « #667eea »,
surface_color: « #ffffff »,
text_color: « #374151 »,
primary_action_color: « #2563eb »,
secondary_action_color: « #6b7280 »,
font_family: « system-ui »,
font_size: 16
};
const dataHandler = {
onDataChanged(data) {
currentRecordCount = data.length;
// Mettre à jour la liste des créneaux réservés
reservedSlots.clear();
data.forEach(inscription => {
if (inscription.creneau_prefere) {
reservedSlots.add(inscription.creneau_prefere);
}
});
// Mettre à jour les options disponibles
updateAvailableSlots();
}
};
function updateAvailableSlots() {
const select = document.getElementById(‘creneau-prefere’);
const currentValue = select.value;
const form = document.getElementById(‘inscription-form’);
const noSlotsMessage = document.getElementById(‘no-slots-message’);
// Vider les options existantes sauf la première
select.innerHTML = ‘<option value= » »>Sélectionnez un créneau disponible</option>’;
// Ajouter seulement les créneaux disponibles
allTimeSlots.forEach(slot => {
if (!reservedSlots.has(slot)) {
const option = document.createElement(‘option’);
option.value = slot;
option.textContent = slot;
select.appendChild(option);
}
});
// Restaurer la valeur si elle est encore disponible
if (currentValue && !reservedSlots.has(currentValue)) {
select.value = currentValue;
}
// Afficher un message si tous les créneaux sont pris
const availableCount = allTimeSlots.length – reservedSlots.size;
const messageElement = select.nextElementSibling;
if (availableCount === 0) {
// Masquer le formulaire et afficher le message d’inscriptions complètes
form.style.display = ‘none’;
noSlotsMessage.classList.remove(‘hidden’);
} else {
// Afficher le formulaire et masquer le message d’inscriptions complètes
form.style.display = ‘block’;
noSlotsMessage.classList.add(‘hidden’);
messageElement.textContent = `${availableCount} créneaux disponibles sur ${allTimeSlots.length}`;
messageElement.className = « text-sm text-gray-600 mt-1 »;
select.disabled = false;
}
}
function showToast(message, isError = true) {
const toast = document.getElementById(‘toast’);
const toastMessage = document.getElementById(‘toast-message’);
toastMessage.textContent = message;
toast.className = `toast px-6 py-3 rounded-lg shadow-lg ${isError ? ‘bg-red-500’ : ‘bg-green-500’} text-white`;
toast.classList.add(‘show’);
setTimeout(() => {
toast.classList.remove(‘show’);
}, 4000);
}
async function handleFormSubmit(event) {
event.preventDefault();
if (currentRecordCount >= 999) {
showToast(« Limite maximale de 999 inscriptions atteinte. Veuillez contacter l’administration. »);
return;
}
const submitBtn = document.getElementById(‘submit-btn’);
const submitText = document.getElementById(‘submit-text’);
const submitSpinner = document.getElementById(‘submit-spinner’);
// Show loading state
submitBtn.disabled = true;
submitText.textContent = ‘Inscription en cours…’;
submitSpinner.classList.remove(‘hidden’);
const formData = new FormData(event.target);
const inscriptionData = {
nom_parent: formData.get(‘nom-parent’),
prenom_parent: formData.get(‘prenom-parent’),
nom_enfant: formData.get(‘nom-enfant’),
classe_enfant: ‘P4A’,
creneau_prefere: formData.get(‘creneau-prefere’),
conditions_acceptees: formData.get(‘conditions’) === ‘on’,
date_inscription: new Date().toISOString()
};
try {
const result = await window.dataSdk.create(inscriptionData);
if (result.isOk) {
// Update success message with selected time slot
const selectedSlot = formData.get(‘creneau-prefere’);
document.getElementById(‘rdv-details’).textContent = `Rendez-vous confirmé le jeudi 18 décembre 2025 de ${selectedSlot}`;
// Show success message
document.getElementById(‘inscription-form’).style.display = ‘none’;
document.getElementById(‘success-message’).classList.remove(‘hidden’);
} else {
showToast(‘Erreur lors de l\’inscription. Veuillez réessayer.’);
}
} catch (error) {
showToast(‘Erreur lors de l\’inscription. Veuillez réessayer.’);
} finally {
// Reset loading state
submitBtn.disabled = false;
submitText.textContent = ‘Confirmer mon inscription’;
submitSpinner.classList.add(‘hidden’);
}
}
async function onConfigChange(config) {
const customFont = config.font_family || defaultConfig.font_family;
const baseSize = config.font_size || defaultConfig.font_size;
const baseFontStack = ‘system-ui, -apple-system, sans-serif’;
// Update text content
document.getElementById(‘titre-formulaire’).textContent = config.titre_formulaire || defaultConfig.titre_formulaire;
document.getElementById(‘date-reunion’).textContent = config.date_reunion || defaultConfig.date_reunion;
document.getElementById(‘lieu-reunion’).textContent = config.lieu_reunion || defaultConfig.lieu_reunion;
document.getElementById(‘message-confirmation’).textContent = config.message_confirmation || defaultConfig.message_confirmation;
// Update colors
const backgroundColor = config.background_color || defaultConfig.background_color;
const surfaceColor = config.surface_color || defaultConfig.surface_color;
const textColor = config.text_color || defaultConfig.text_color;
const primaryActionColor = config.primary_action_color || defaultConfig.primary_action_color;
const secondaryActionColor = config.secondary_action_color || defaultConfig.secondary_action_color;
document.querySelector(‘.form-container’).style.background = `linear-gradient(135deg, ${backgroundColor} 0%, ${secondaryActionColor} 100%)`;
document.querySelector(‘.bg-white’).style.backgroundColor = surfaceColor;
// Update text colors
const textElements = document.querySelectorAll(‘h1, label, p, .text-gray-700, .text-gray-800’);
textElements.forEach(el => {
el.style.color = textColor;
});
// Update button colors
const submitButton = document.getElementById(‘submit-btn’);
submitButton.style.backgroundColor = primaryActionColor;
submitButton.style.borderColor = primaryActionColor;
// Update fonts
document.body.style.fontFamily = `${customFont}, ${baseFontStack}`;
// Update font sizes
document.getElementById(‘titre-formulaire’).style.fontSize = `${baseSize * 1.875}px`;
document.querySelectorAll(‘label’).forEach(el => {
el.style.fontSize = `${baseSize * 0.875}px`;
});
document.querySelectorAll(‘input, select, textarea’).forEach(el => {
el.style.fontSize = `${baseSize}px`;
});
document.getElementById(‘submit-btn’).style.fontSize = `${baseSize}px`;
}
// Initialize SDKs
async function initializeApp() {
try {
if (window.dataSdk) {
const initResult = await window.dataSdk.init(dataHandler);
if (!initResult.isOk) {
console.error(« Failed to initialize data SDK »);
}
}
if (window.elementSdk) {
window.elementSdk.init({
defaultConfig,
onConfigChange,
mapToCapabilities: (config) => ({
recolorables: [
{
get: () => config.background_color || defaultConfig.background_color,
set: (value) => {
config.background_color = value;
window.elementSdk.setConfig({ background_color: value });
}
},
{
get: () => config.surface_color || defaultConfig.surface_color,
set: (value) => {
config.surface_color = value;
window.elementSdk.setConfig({ surface_color: value });
}
},
{
get: () => config.text_color || defaultConfig.text_color,
set: (value) => {
config.text_color = value;
window.elementSdk.setConfig({ text_color: value });
}
},
{
get: () => config.primary_action_color || defaultConfig.primary_action_color,
set: (value) => {
config.primary_action_color = value;
window.elementSdk.setConfig({ primary_action_color: value });
}
},
{
get: () => config.secondary_action_color || defaultConfig.secondary_action_color,
set: (value) => {
config.secondary_action_color = value;
window.elementSdk.setConfig({ secondary_action_color: value });
}
}
],
borderables: [],
fontEditable: {
get: () => config.font_family || defaultConfig.font_family,
set: (value) => {
config.font_family = value;
window.elementSdk.setConfig({ font_family: value });
}
},
fontSizeable: {
get: () => config.font_size || defaultConfig.font_size,
set: (value) => {
config.font_size = value;
window.elementSdk.setConfig({ font_size: value });
}
}
}),
mapToEditPanelValues: (config) => new Map([
[« titre_formulaire », config.titre_formulaire || defaultConfig.titre_formulaire],
[« date_reunion », config.date_reunion || defaultConfig.date_reunion],
[« lieu_reunion », config.lieu_reunion || defaultConfig.lieu_reunion],
[« message_confirmation », config.message_confirmation || defaultConfig.message_confirmation]
])
});
}
// Set up form submission
document.getElementById(‘inscription-form’).addEventListener(‘submit’, handleFormSubmit);
// Initialiser les créneaux disponibles
updateAvailableSlots();
} catch (error) {
console.error(« Initialization error: », error);
}
}
// Initialize when page loads
initializeApp();
</script>
<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement(‘script’);d.innerHTML= »window.__CF$cv$params={r:’9a8a3ef0721670e9′,t:’MTc2NDg0MDIxNS4wMDAwMDA=’};var a=document.createElement(‘script’);a.nonce= »;a.src=’/cdn-cgi/challenge-platform/scripts/jsd/main.js’;document.getElementsByTagName(‘head’)[0].appendChild(a); »;b.getElementsByTagName(‘head’)[0].appendChild(d)}}if(document.body){var a=document.createElement(‘iframe’);a.height=1;a.width=1;a.style.position=’absolute’;a.style.top=0;a.style.left=0;a.style.border=’none’;a.style.visibility=’hidden’;document.body.appendChild(a);if(‘loading’!==document.readyState)c();else if(window.addEventListener)document.addEventListener(‘DOMContentLoaded’,c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);’loading’!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>





