Réservation d’espace

Calculateur de Location de Salles

/* Styles CSS encapsulés pour éviter les conflits avec WordPress */
#calc-salle-container {
font-family: -apple-system, BlinkMacSystemFont, « Segoe UI », Roboto, Oxygen-Sans, Ubuntu, Cantarell, « Helvetica Neue », sans-serif;
max-width: 600px;
margin: 20px auto;
padding: 20px;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}

#calc-salle-container h2 {
margin-top: 0;
color: #333;
text-align: center;
}

#calc-salle-container .form-group {
margin-bottom: 15px;
}

#calc-salle-container label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #444;
}

#calc-salle-container input[type= »text »],
#calc-salle-container input[type= »email »],
#calc-salle-container input[type= »date »],
#calc-salle-container input[type= »number »],
#calc-salle-container select {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box; /* Important pour le padding */
}

#calc-salle-container .hidden {
display: none;
}

#calc-salle-container .result-box {
margin-top: 20px;
padding: 15px;
background: #fff;
border-left: 5px solid #0073aa; /* WordPress Blue */
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

#calc-salle-container .error-msg {
color: #d63638;
background: #fbeaea;
border: 1px solid #d63638;
padding: 10px;
border-radius: 4px;
margin-top: 10px;
display: none; /* Caché par défaut */
}

#calc-salle-container .price-display {
font-size: 1.4em;
font-weight: bold;
color: #0073aa;
text-align: right;
margin-top: 10px;
}

#calc-salle-container .details {
font-size: 0.9em;
color: #666;
margin-top: 5px;
border-top: 1px solid #eee;
padding-top: 5px;
}

#calc-salle-container button.submit-btn {
background-color: #0073aa;
color: white;
border: none;
padding: 12px 20px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
width: 100%;
margin-top: 15px;
transition: background-color 0.3s;
}

#calc-salle-container button.submit-btn:hover {
background-color: #005177;
}

/* Petit ajustement pour les petits écrans */
@media (max-width: 480px) {
#calc-salle-container {
padding: 15px;
}
}

Demande de Tarif Salle

Particulier / Standard
Copropriété (+20%)
Tournage de film (+250%)
Concert et/ou association (+5%)
Institution chrétienne (-15%)

Nov-Mars: +20% | Avril-Oct: +0%


— Choisir une salle —
Salle Jérusalem (30€)
Salle Jérusalem + Samarie (45€)
Salle Jérusalem + Samarie + cuisine (80€)
Temple (60€)
Temple + cuisine (90€)
Tout (Temple+Cuis+Jér+Sam) (120€)

Demi-journée (Matin, Aprèm ou Soir)
Journée (+80%)
Plusieurs journées (+80% / jour)

Une rencontre unique
Plusieurs fois une rencontre (-5%)

0-19
20-49
50-99
100-199
200+ (max 240)


<!– –>

document.addEventListener(‘DOMContentLoaded’, function() {
const form = document.getElementById(‘salleForm’);
const structureSelect = document.getElementById(‘structure_salle’);
const dureeSelect = document.getElementById(‘duree’);
const nbJoursDiv = document.getElementById(‘div_nb_jours’);
const nbJoursInput = document.getElementById(‘nb_jours’);
const participantsSelect = document.getElementById(‘participants’);
const typeSelect = document.getElementById(‘type_demandeur’);
const regulariteSelect = document.getElementById(‘regularite’);
const dateInput = document.getElementById(‘date_event’);
const errorBox = document.getElementById(‘error_box’);
const resultBox = document.getElementById(‘result_box’);
const priceDisplay = document.getElementById(‘final_price’);
const breakdownDisplay = document.getElementById(‘price_breakdown’);

// Écouter tous les changements
form.addEventListener(‘change’, calculate);
form.addEventListener(‘input’, calculate);

function calculate() {
// 1. Réinitialisation
errorBox.style.display = ‘none’;
errorBox.innerText =  »;
resultBox.classList.add(‘hidden’);

// 2. Récupération des données de base
const basePrice = parseFloat(structureSelect.value);
if (!basePrice) return; // Pas de salle choisie encore

const selectedOption = structureSelect.options[structureSelect.selectedIndex];
const maxCapacity = parseInt(selectedOption.getAttribute(‘data-max’));
const structureName = selectedOption.getAttribute(‘data-name’);
const participantsMaxRange = parseInt(participantsSelect.value); // Valeur haute de la tranche (ex: 49 pour 20-49)

// 3. Gestion de l’affichage « Nombre de jours »
let durationFactor = 1;
let isMultiDay = false;
let dayCount = 1;

if (dureeSelect.value === ‘multi’) {
nbJoursDiv.classList.remove(‘hidden’);
isMultiDay = true;
dayCount = parseInt(nbJoursInput.value) || 2;
// Formule : Prix Journée (Base * 1.8) * Nombre de jours
durationFactor = 1.8 * dayCount;
} else {
nbJoursDiv.classList.add(‘hidden’);
durationFactor = parseFloat(dureeSelect.value);
}

// 4. Vérification des INCOMPATIBILITÉS (Logique stricte)
// La valeur du select participants est le max de la tranche (ex: 19, 49, 99…)
// Si le bas de la tranche dépasse la capacité, c’est bloquant.
// Simplification : On compare la borne sup de la sélection utilisateur avec le max de la salle.

let incompatible = false;
let errorMsg = «  »;

// Logique spécifique demandée :
// « Si Salle Samarie ET plus de 49 pers. » -> Concerne les packs incluant Samarie mais limités (ex: Jeru+Sam)
// « Si Salle Jérusalem ET plus de 19 pers. »
// « Si Temple ET plus de 199 pers. »
// « Si Temple et Jérusalem et plus de 240 »

// Ma méthode basée sur data-max gère tout cela automatiquement :
// Si je choisis « Jerusalem » (data-max=19) et participants « 20-49 » (valeur 49), 49 > 19 -> Erreur.

// Cependant, l’utilisateur a des tranches : 0-19, 20-49.
// Si max salle est 19, et user choisit « 20-49 », c’est KO.
// Si max salle est 49, et user choisit « 50-99 », c’est KO.

// On utilise un mapping inverse simple pour vérifier le minimum de la tranche choisie
let minParticipants = 0;
if (participantsMaxRange === 19) minParticipants = 0;
if (participantsMaxRange === 49) minParticipants = 20;
if (participantsMaxRange === 99) minParticipants = 50;
if (participantsMaxRange === 199) minParticipants = 100;
if (participantsMaxRange === 240) minParticipants = 200;

if (minParticipants > maxCapacity) {
incompatible = true;
errorMsg = `Incompatibilité : La structure « ${structureName} » est limitée à ${maxCapacity} personnes. Vous avez sélectionné une tranche démarrant à ${minParticipants}.`;
}

if (incompatible) {
errorBox.innerText = errorMsg;
errorBox.style.display = ‘block’;
return; // On arrête le calcul
}

// 5. Calculs Financiers

// Prix lié à la durée
let timePrice = basePrice * durationFactor;

// Saisonnalité
let seasonPercent = 0;
let seasonName = « Standard (Avril-Oct) »;
const dateVal = dateInput.value;
if (dateVal) {
const dateObj = new Date(dateVal);
const month = dateObj.getMonth(); // 0 = Janvier, 11 = Décembre
// Novembre (10) à Mars (2) -> Hiver +20%
if (month >= 10 || month <= 2) {
seasonPercent = 0.20;
seasonName = "Hiver (Nov-Mars, +20%)";
}
}

// Type Demandeur
const typePercent = parseFloat(typeSelect.value);
const typeLabel = typeSelect.options[typeSelect.selectedIndex].getAttribute('data-label');

// Régularité
const regPercent = parseFloat(regulariteSelect.value);

// — FORMULE DE CALCUL —
// Hypothèse : Les pourcentages s'additionnent ou s'appliquent en cascade ?
// Vu les gros chiffres (+250%), il est plus prudent d'appliquer les majorations sur le "Prix Temps".
// Total = PrixTemps + (PrixTemps * Saison) + (PrixTemps * Type) + (PrixTemps * Regularité)

const seasonAmount = timePrice * seasonPercent;
const typeAmount = timePrice * typePercent;
const regAmount = timePrice * regPercent; // Sera négatif si réduction

let total = timePrice + seasonAmount + typeAmount + regAmount;

// Affichage Détails
let detailsHtml = `Base structure : ${basePrice} €
`;
if (isMultiDay) {
detailsHtml += `Durée : ${dayCount} jours (Coeff x${durationFactor.toFixed(1)}) = ${timePrice.toFixed(2)} €
`;
} else {
detailsHtml += `Durée : ${dureeSelect.options[dureeSelect.selectedIndex].text} = ${timePrice.toFixed(2)} €
`;
}

if (seasonPercent !== 0) detailsHtml += `Saison : ${seasonName} = ${seasonAmount > 0 ? ‘+’ :  »}${seasonAmount.toFixed(2)} €
`;
if (typePercent !== 0) detailsHtml += `Type : ${typeLabel} (${(typePercent*100).toFixed(0)}%) = ${typeAmount > 0 ? ‘+’ :  »}${typeAmount.toFixed(2)} €
`;
if (regPercent !== 0) detailsHtml += `Régularité : ${(regPercent*100).toFixed(0)}% = ${regAmount.toFixed(2)} €
`;

breakdownDisplay.innerHTML = detailsHtml;
priceDisplay.innerText = total.toFixed(2) +  » € »;
resultBox.classList.remove(‘hidden’);
}
});