1489 lines
49 KiB
JavaScript
1489 lines
49 KiB
JavaScript
const storage_price = [.0656,.0878,.1093,.1231];
|
|
|
|
const prices = {
|
|
nc: {
|
|
storage: storage_price,
|
|
users: [0.66,1.16,1.76,2.16],
|
|
options: {
|
|
lxc: {
|
|
basePrice: 17,
|
|
factor: 1.131
|
|
},
|
|
// dedicated: {
|
|
// basePrice: 64,
|
|
// factor: 1.753
|
|
// },
|
|
// onprem: {
|
|
// basePrice: 111,
|
|
// factor: 1.498
|
|
// },
|
|
vol: [45,70,95,110]
|
|
}
|
|
},
|
|
wh: {
|
|
storage: storage_price,
|
|
sites: [2.6,3.7,4.9,6.1],
|
|
options: {
|
|
preinstall: {
|
|
basePrice: 2.773,
|
|
factor: 1.168
|
|
}
|
|
}
|
|
},
|
|
mail: {
|
|
storage: storage_price,
|
|
users: [2.1,4.2,7.3,9.4],
|
|
options: {
|
|
catchall: {
|
|
basePrice: 2.787,
|
|
factor: 1.196
|
|
}
|
|
}
|
|
},
|
|
vps: {
|
|
storage: storage_price.map(x => x * 1.1297),
|
|
cpu: [6.146, 7.687, 9.218, 10.749],
|
|
ram: [1.646, 2.057, 2.468, 2.879],
|
|
options: {
|
|
ip4: {
|
|
basePrice: 4.393,
|
|
factor: 1.097
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// PHASE 1: Configuration Manager - Définition déclarative des contrôles
|
|
// ============================================================================
|
|
|
|
const ConfigurationManager = {
|
|
/**
|
|
* Configuration des sliders pour chaque service
|
|
* Structure : { id, displayId, min, max, step, restrictedLabels }
|
|
*/
|
|
sliders: {
|
|
nc: {
|
|
storage: {
|
|
id: 'nextcloud_stockage_source',
|
|
displayId: 'nextcloud_stockage_display',
|
|
min: 0,
|
|
max: 500,
|
|
step: 5,
|
|
restrictedLabels: [0, 50, 100, 200, 300, 400, 500]
|
|
},
|
|
users: {
|
|
id: 'nextcloud_users_source',
|
|
displayId: 'nextcloud_users_display',
|
|
min: 0,
|
|
max: 50,
|
|
step: 1,
|
|
restrictedLabels: [0, 5, 10, 15, 20, 30, 40, 50]
|
|
}
|
|
},
|
|
wh: {
|
|
storage: {
|
|
id: 'wh_stockage_source',
|
|
displayId: 'wh_stockage_display',
|
|
min: 0,
|
|
max: 250,
|
|
step: 1,
|
|
restrictedLabels: [0, 5, 10, 25, 50, 75, 125, 250]
|
|
},
|
|
sites: {
|
|
id: 'wh_sites_source',
|
|
displayId: 'wh_sites_display',
|
|
min: 0,
|
|
max: 20,
|
|
step: 1,
|
|
restrictedLabels: [0, 5, 10, 15, 20]
|
|
}
|
|
},
|
|
mail: {
|
|
storage: {
|
|
id: 'mail_stockage_source',
|
|
displayId: 'mail_stockage_display',
|
|
min: 0,
|
|
max: 250,
|
|
step: 1,
|
|
restrictedLabels: [0, 5, 10, 25, 50, 75, 125, 250]
|
|
},
|
|
users: {
|
|
id: 'mail_users_source',
|
|
displayId: 'mail_users_display',
|
|
min: 0,
|
|
max: 50,
|
|
step: 1,
|
|
restrictedLabels: [0, 5, 10, 15, 20, 30, 40, 50]
|
|
}
|
|
},
|
|
vps: {
|
|
storage: {
|
|
id: 'vps_stockage_source',
|
|
displayId: 'vps_stockage_display',
|
|
min: 0,
|
|
max: 500,
|
|
step: 25,
|
|
restrictedLabels: [0, 25, 50, 75, 100, 200, 300, 400, 500]
|
|
},
|
|
cpu: {
|
|
id: 'vps_cpu_source',
|
|
displayId: 'vps_cpu_display',
|
|
min: 0,
|
|
max: 32,
|
|
step: 1,
|
|
restrictedLabels: [0, 1, 2, 4, 6, 8, 12, 16, 24, 32]
|
|
},
|
|
ram: {
|
|
id: 'vps_ram_source',
|
|
displayId: 'vps_ram_display',
|
|
min: 0,
|
|
max: 64,
|
|
step: 1,
|
|
restrictedLabels: [0, 1, 2, 4, 8, 12, 16, 24, 32, 48, 64]
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Configuration des checkboxes pour chaque service
|
|
* Structure : { id, class, enables, defaultDisabled }
|
|
*/
|
|
checkboxes: {
|
|
nc: {
|
|
lxc: {
|
|
id: 'nc_opt_lxc',
|
|
class: 'nc_opt_lxc',
|
|
enables: [], // ['dedicated'],
|
|
defaultDisabled: false
|
|
},
|
|
// dedicated: {
|
|
// id: 'nc_opt_decicated',
|
|
// class: 'nc_opt_decicated',
|
|
// enables: ['onprem'],
|
|
// defaultDisabled: true
|
|
// },
|
|
// onprem: {
|
|
// id: 'nc_opt_onprem',
|
|
// class: 'nc_opt_onprem',
|
|
// enables: [],
|
|
// defaultDisabled: true
|
|
// },
|
|
vol: {
|
|
id: 'nc_opt_vol',
|
|
class: 'nc_opt_vol',
|
|
inputId: 'nc_opt_vol_val',
|
|
enables: [],
|
|
defaultDisabled: false
|
|
}
|
|
},
|
|
wh: {
|
|
preinstall: {
|
|
id: 'wh_opt_preinstall',
|
|
class: 'wh_opt_preinstall',
|
|
enables: [],
|
|
defaultDisabled: false
|
|
}
|
|
},
|
|
mail: {
|
|
catchall: {
|
|
id: 'mail_opt_catchall',
|
|
class: 'mail_opt_catchall',
|
|
enables: [],
|
|
defaultDisabled: false
|
|
}
|
|
},
|
|
vps: {
|
|
ip4: {
|
|
id: 'vps_opt_ip4',
|
|
class: 'vps_opt_ip4',
|
|
enables: [],
|
|
defaultDisabled: false
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Récupère la configuration complète d'un slider
|
|
*/
|
|
getSliderConfig(service, field) {
|
|
return this.sliders[service] && this.sliders[service][field];
|
|
},
|
|
|
|
/**
|
|
* Récupère la configuration complète d'une checkbox
|
|
*/
|
|
getCheckboxConfig(service, option) {
|
|
return this.checkboxes[service] && this.checkboxes[service][option];
|
|
},
|
|
|
|
/**
|
|
* Retourne tous les services configurés
|
|
*/
|
|
getServices() {
|
|
return Object.keys(this.sliders);
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// PHASE 1: State Manager - Gestion centralisée de l'état
|
|
// ============================================================================
|
|
|
|
const StateManager = {
|
|
/**
|
|
* État actuel de toutes les sélections
|
|
*/
|
|
state: {
|
|
nc: {
|
|
storage: 0,
|
|
users: 0,
|
|
opt_lxc: false,
|
|
// opt_dedicated: false,
|
|
// opt_onprem: false,
|
|
opt_vol: false,
|
|
opt_vol_val: 1
|
|
},
|
|
wh: {
|
|
storage: 0,
|
|
sites: 0,
|
|
opt_preinstall: false
|
|
},
|
|
mail: {
|
|
storage: 0,
|
|
users: 0,
|
|
opt_catchall: false
|
|
},
|
|
vps: {
|
|
storage: 0,
|
|
cpu: 0,
|
|
ram: 0,
|
|
opt_ip4: false
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Récupère une valeur de l'état
|
|
*/
|
|
get(service, field) {
|
|
return this.state[service] && this.state[service][field];
|
|
},
|
|
|
|
/**
|
|
* Définit une valeur dans l'état
|
|
*/
|
|
set(service, field, value) {
|
|
if (this.state[service]) {
|
|
this.state[service][field] = value;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Récupère tout l'état d'un service
|
|
*/
|
|
getServiceState(service) {
|
|
return this.state[service];
|
|
},
|
|
|
|
/**
|
|
* Récupère l'état complet
|
|
*/
|
|
getState() {
|
|
return JSON.parse(JSON.stringify(this.state)); // Deep copy
|
|
},
|
|
|
|
/**
|
|
* Définit l'état complet
|
|
*/
|
|
setState(newState) {
|
|
this.state = JSON.parse(JSON.stringify(newState)); // Deep copy
|
|
},
|
|
|
|
/**
|
|
* Réinitialise l'état aux valeurs par défaut
|
|
*/
|
|
reset() {
|
|
Object.keys(this.state).forEach(service => {
|
|
Object.keys(this.state[service]).forEach(field => {
|
|
if (typeof this.state[service][field] === 'boolean') {
|
|
this.state[service][field] = false;
|
|
} else if (field === 'opt_vol_val') {
|
|
this.state[service][field] = 1;
|
|
} else {
|
|
this.state[service][field] = 0;
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Sérialise l'état en paramètres URL
|
|
* Format compact : nc_s=100&nc_u=5&nc_lxc=1...
|
|
*/
|
|
toURLParams() {
|
|
const params = new URLSearchParams();
|
|
|
|
Object.keys(this.state).forEach(service => {
|
|
Object.keys(this.state[service]).forEach(field => {
|
|
const value = this.state[service][field];
|
|
|
|
// Ne sauvegarder que les valeurs non-nulles/non-false
|
|
if (value !== 0 && value !== false && value !== '') {
|
|
// Créer une clé courte
|
|
let key;
|
|
|
|
if (field === 'storage') {
|
|
key = service + '_s';
|
|
} else if (field === 'users') {
|
|
key = service + '_u';
|
|
} else if (field === 'sites') {
|
|
key = service + '_si';
|
|
} else if (field === 'cpu') {
|
|
key = service + '_c';
|
|
} else if (field === 'ram') {
|
|
key = service + '_r';
|
|
} else if (field.startsWith('opt_')) {
|
|
// Pour les options, enlever le préfixe opt_
|
|
const optName = field.replace('opt_', '');
|
|
key = service + '_' + optName;
|
|
} else {
|
|
key = service + '_' + field;
|
|
}
|
|
|
|
// Valeur : 1 pour boolean true, sinon la valeur
|
|
params.append(key, typeof value === 'boolean' ? '1' : value);
|
|
}
|
|
});
|
|
});
|
|
|
|
return params.toString();
|
|
},
|
|
|
|
/**
|
|
* Désérialise des paramètres URL en état
|
|
*/
|
|
fromURLParams(urlParams) {
|
|
const params = new URLSearchParams(urlParams);
|
|
|
|
// Mapping inverse pour retrouver les noms complets
|
|
const fieldMapping = {
|
|
s: 'storage',
|
|
u: 'users',
|
|
c: 'cpu',
|
|
r: 'ram',
|
|
si: 'sites'
|
|
};
|
|
|
|
params.forEach((value, key) => {
|
|
const parts = key.split('_');
|
|
if (parts.length >= 2) {
|
|
const service = parts[0];
|
|
let field = parts.slice(1).join('_'); // Gérer les cas comme vol_val
|
|
|
|
// Si c'est un raccourci d'une lettre, utiliser le mapping
|
|
if (field.length <= 2 && fieldMapping[field]) {
|
|
field = fieldMapping[field];
|
|
}
|
|
// Gérer les options (qui ne sont pas dans le mapping)
|
|
else if (['lxc', /* 'dedicated', 'onprem', */ 'vol', 'preinstall', 'catchall', 'ip4'].includes(field)) {
|
|
field = 'opt_' + field;
|
|
}
|
|
// Gérer vol_val spécifiquement
|
|
else if (field === 'vol_val') {
|
|
field = 'opt_vol_val';
|
|
}
|
|
|
|
if (this.state[service] && this.state[service].hasOwnProperty(field)) {
|
|
// Parser la valeur selon le type attendu
|
|
if (typeof this.state[service][field] === 'boolean') {
|
|
this.state[service][field] = value === '1' || value === 'true';
|
|
} else {
|
|
this.state[service][field] = parseFloat(value) || 0;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// PHASE 2: Price Engine - Logique de calcul pure (fonctions testables)
|
|
// ============================================================================
|
|
|
|
const PriceEngine = {
|
|
/**
|
|
* Calcule le prix Nextcloud pour une tranche donnée
|
|
*/
|
|
calculateNextcloudPrice(state, tierIndex) {
|
|
const ncState = state.nc;
|
|
let price = ncState.storage * prices.nc.storage[tierIndex];
|
|
|
|
// Ajouter les utilisateurs si pas en mode LXC
|
|
if (!ncState.opt_lxc) {
|
|
price += ncState.users * prices.nc.users[tierIndex];
|
|
}
|
|
|
|
// Déterminer quelle option appliquer (hiérarchique: lxc < dedicated < onprem)
|
|
let optionMultiplier = { basePrice: 0, factor: 1 };
|
|
|
|
if (ncState.opt_lxc) {
|
|
optionMultiplier = prices.nc.options.lxc;
|
|
}
|
|
// if (ncState.opt_lxc && ncState.opt_dedicated) {
|
|
// optionMultiplier = prices.nc.options.dedicated;
|
|
// }
|
|
// if (ncState.opt_lxc && ncState.opt_dedicated && ncState.opt_onprem) {
|
|
// optionMultiplier = prices.nc.options.onprem;
|
|
// }
|
|
|
|
// Appliquer le multiplicateur d'option
|
|
price = (price + optionMultiplier.basePrice) * optionMultiplier.factor;
|
|
|
|
// Ajouter l'option gros volume si activée
|
|
if (ncState.opt_vol && ncState.opt_vol_val > 0) {
|
|
const volPrice = Math.max(40, Math.pow(ncState.opt_vol_val, 0.7) * prices.nc.options.vol[tierIndex]);
|
|
price += volPrice;
|
|
}
|
|
|
|
return price;
|
|
},
|
|
|
|
/**
|
|
* Calcule le prix par To pour l'option volume Nextcloud
|
|
*/
|
|
calculateNextcloudVolumePerTB(state, tierIndex) {
|
|
const ncState = state.nc;
|
|
|
|
if (!ncState.opt_vol || ncState.opt_vol_val <= 0) {
|
|
return null;
|
|
}
|
|
|
|
const totalPrice = this.calculateNextcloudPrice(state, tierIndex);
|
|
return totalPrice / ncState.opt_vol_val;
|
|
},
|
|
|
|
/**
|
|
* Calcule le prix Hébergement Web pour une tranche donnée
|
|
*/
|
|
calculateWebHostingPrice(state, tierIndex) {
|
|
const whState = state.wh;
|
|
let price = whState.storage * prices.wh.storage[tierIndex]
|
|
+ whState.sites * prices.wh.sites[tierIndex];
|
|
|
|
// Appliquer l'option préinstallation si activée
|
|
if (whState.opt_preinstall) {
|
|
price = (price + prices.wh.options.preinstall.basePrice)
|
|
* prices.wh.options.preinstall.factor;
|
|
}
|
|
|
|
return price;
|
|
},
|
|
|
|
/**
|
|
* Calcule le prix Hébergement Mail pour une tranche donnée
|
|
*/
|
|
calculateMailPrice(state, tierIndex) {
|
|
const mailState = state.mail;
|
|
let price = mailState.storage * prices.mail.storage[tierIndex]
|
|
+ mailState.users * prices.mail.users[tierIndex];
|
|
|
|
// Appliquer l'option catch-all si activée
|
|
if (mailState.opt_catchall) {
|
|
price = (price + prices.mail.options.catchall.basePrice)
|
|
* prices.mail.options.catchall.factor;
|
|
}
|
|
|
|
return price;
|
|
},
|
|
|
|
/**
|
|
* Calcule le prix VPS pour une tranche donnée
|
|
*/
|
|
calculateVPSPrice(state, tierIndex) {
|
|
const vpsState = state.vps;
|
|
let price = vpsState.storage * prices.vps.storage[tierIndex]
|
|
+ vpsState.cpu * prices.vps.cpu[tierIndex]
|
|
+ vpsState.ram * prices.vps.ram[tierIndex];
|
|
|
|
// Appliquer l'option IPv4 si activée
|
|
if (vpsState.opt_ip4) {
|
|
price = (price + prices.vps.options.ip4.basePrice)
|
|
* prices.vps.options.ip4.factor;
|
|
}
|
|
|
|
return price;
|
|
},
|
|
|
|
/**
|
|
* Calcule le prix d'un service spécifique
|
|
*/
|
|
calculateServicePrice(service, state, tierIndex) {
|
|
switch(service) {
|
|
case 'nc':
|
|
return this.calculateNextcloudPrice(state, tierIndex);
|
|
case 'wh':
|
|
return this.calculateWebHostingPrice(state, tierIndex);
|
|
case 'mail':
|
|
return this.calculateMailPrice(state, tierIndex);
|
|
case 'vps':
|
|
return this.calculateVPSPrice(state, tierIndex);
|
|
default:
|
|
return 0;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Calcule tous les prix pour les 4 tranches
|
|
* Retourne un tableau de 4 objets avec les détails par service
|
|
*/
|
|
calculateAllPrices(state) {
|
|
const results = [];
|
|
|
|
for (let tierIndex = 0; tierIndex < 4; tierIndex++) {
|
|
const nc = this.calculateNextcloudPrice(state, tierIndex);
|
|
const wh = this.calculateWebHostingPrice(state, tierIndex);
|
|
const mail = this.calculateMailPrice(state, tierIndex);
|
|
const vps = this.calculateVPSPrice(state, tierIndex);
|
|
const total = Math.ceil(nc + wh + mail + vps);
|
|
|
|
results.push({
|
|
tierIndex,
|
|
tierName: ['minimum', 'correct', 'généreux', 'maximum'][tierIndex],
|
|
nc,
|
|
wh,
|
|
mail,
|
|
vps,
|
|
total,
|
|
// Calcul du prix par To pour Nextcloud (si volume activé)
|
|
ncVolumePerTB: this.calculateNextcloudVolumePerTB(state, tierIndex)
|
|
});
|
|
}
|
|
|
|
return results;
|
|
},
|
|
|
|
/**
|
|
* Vérifie si un service est actif (au moins une option non-nulle)
|
|
*/
|
|
isServiceActive(service, state) {
|
|
const serviceState = state[service];
|
|
if (!serviceState) return false;
|
|
|
|
return Object.values(serviceState).some(value => {
|
|
if (typeof value === 'boolean') return value;
|
|
if (typeof value === 'number') return value > 0;
|
|
return false;
|
|
});
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// PHASE 3: UI Controller - Gestion déclarative des contrôles
|
|
// ============================================================================
|
|
|
|
const UIController = {
|
|
/**
|
|
* Initialise tous les contrôles de l'interface
|
|
*/
|
|
initialize() {
|
|
this.initializeAccordion();
|
|
this.initializeSticky();
|
|
this.initializeShape();
|
|
this.initializeAllSliders();
|
|
this.initializeAllCheckboxes();
|
|
this.bindSpecialEvents();
|
|
},
|
|
|
|
/**
|
|
* Initialise l'accordéon Semantic UI
|
|
*/
|
|
initializeAccordion() {
|
|
$('.ui.accordion').accordion({
|
|
exclusive: true,
|
|
collapsible: false
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialise le sticky header
|
|
*/
|
|
initializeSticky() {
|
|
$('.ui.sticky').sticky();
|
|
},
|
|
|
|
/**
|
|
* Initialise les animations shape
|
|
*/
|
|
initializeShape() {
|
|
$('.shape').shape();
|
|
},
|
|
|
|
/**
|
|
* Factory: Crée et initialise un slider à partir de sa configuration
|
|
*/
|
|
createSlider(config) {
|
|
const self = this;
|
|
$('#' + config.id).slider({
|
|
showThumbTooltip: true,
|
|
restrictedLabels: config.restrictedLabels,
|
|
min: config.min,
|
|
max: config.max,
|
|
step: config.step,
|
|
autoAdjustLabels: false,
|
|
onMove: function(range, firstVal, secondVal) {
|
|
$('#' + config.displayId).html(firstVal);
|
|
self.syncStateFromDOM();
|
|
self.updateDisplay();
|
|
// Phase 4: Mise à jour de l'URL après chaque changement
|
|
URLSync.updateURL();
|
|
// Phase 5: Mise à jour du récapitulatif
|
|
SummaryGenerator.updateSummary();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialise tous les sliders depuis la configuration
|
|
*/
|
|
initializeAllSliders() {
|
|
const services = ConfigurationManager.getServices();
|
|
|
|
services.forEach(service => {
|
|
const sliderConfigs = ConfigurationManager.sliders[service];
|
|
Object.keys(sliderConfigs).forEach(field => {
|
|
const config = sliderConfigs[field];
|
|
this.createSlider(config);
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialise toutes les checkboxes depuis la configuration
|
|
*/
|
|
initializeAllCheckboxes() {
|
|
const self = this;
|
|
|
|
// Première passe : initialiser toutes les checkboxes Semantic UI
|
|
Object.keys(ConfigurationManager.checkboxes).forEach(service => {
|
|
const checkboxConfigs = ConfigurationManager.checkboxes[service];
|
|
Object.keys(checkboxConfigs).forEach(option => {
|
|
const config = checkboxConfigs[option];
|
|
const $checkbox = $('.ui.toggle.checkbox.' + config.class);
|
|
|
|
// Initialiser la checkbox Semantic UI
|
|
$checkbox.checkbox();
|
|
|
|
// Appliquer l'état disabled par défaut
|
|
if (config.defaultDisabled) {
|
|
$checkbox.checkbox('set disabled');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Deuxième passe : attacher les événements de dépendances et de mise à jour
|
|
Object.keys(ConfigurationManager.checkboxes).forEach(service => {
|
|
const checkboxConfigs = ConfigurationManager.checkboxes[service];
|
|
Object.keys(checkboxConfigs).forEach(option => {
|
|
const config = checkboxConfigs[option];
|
|
const $checkbox = $('.ui.toggle.checkbox.' + config.class);
|
|
const $input = $checkbox.find('input[type="checkbox"]');
|
|
|
|
$input.on('change', function() {
|
|
const isChecked = $(this).is(':checked');
|
|
|
|
// Gérer les dépendances enables
|
|
if (config.enables && config.enables.length > 0) {
|
|
if (isChecked) {
|
|
// Activer les options dépendantes
|
|
config.enables.forEach(targetOption => {
|
|
const targetConfig = ConfigurationManager.getCheckboxConfig(service, targetOption);
|
|
if (targetConfig) {
|
|
$('.ui.toggle.checkbox.' + targetConfig.class).checkbox('set enabled');
|
|
}
|
|
});
|
|
} else {
|
|
// Désactiver et décocher les options dépendantes (avec cascade)
|
|
config.enables.forEach(targetOption => {
|
|
self.disableCheckboxCascade(service, targetOption);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Synchroniser l'état et mettre à jour l'affichage
|
|
self.syncStateFromDOM();
|
|
self.updateDisplay();
|
|
// Phase 4: Mise à jour de l'URL après chaque changement
|
|
URLSync.updateURL();
|
|
// Phase 5: Mise à jour du récapitulatif
|
|
SummaryGenerator.updateSummary();
|
|
});
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Désactive récursivement une checkbox et toutes ses dépendances
|
|
*/
|
|
disableCheckboxCascade(service, option) {
|
|
const config = ConfigurationManager.getCheckboxConfig(service, option);
|
|
if (!config) {
|
|
return;
|
|
}
|
|
|
|
// Désactiver et décocher cette checkbox
|
|
const $checkbox = $('.ui.toggle.checkbox.' + config.class);
|
|
$checkbox.checkbox('uncheck').checkbox('set disabled');
|
|
|
|
// Si elle a des dépendances, les désactiver aussi récursivement
|
|
if (config.enables && config.enables.length > 0) {
|
|
config.enables.forEach(targetOption => {
|
|
this.disableCheckboxCascade(service, targetOption);
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Bind les événements spéciaux (cas particuliers)
|
|
*/
|
|
bindSpecialEvents() {
|
|
const self = this;
|
|
|
|
// Événement sur l'input du volume Nextcloud
|
|
$('#nc_opt_vol_val').on('change', function() {
|
|
StateManager.set('nc', 'opt_vol_val', parseInt($(this).val()) || 1);
|
|
self.updateDisplay();
|
|
// Phase 4: Mise à jour de l'URL après chaque changement
|
|
URLSync.updateURL();
|
|
// Phase 5: Mise à jour du récapitulatif
|
|
SummaryGenerator.updateSummary();
|
|
});
|
|
|
|
// Événement spécial pour la checkbox LXC (toggle shape)
|
|
$('.ui.toggle.checkbox.nc_opt_lxc').find('input[type="checkbox"]').on('change', function() {
|
|
$('#ncusers').shape('flip up');
|
|
$('#nextcloud_users_source').toggleClass('disabled');
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Synchronise l'état depuis le DOM (lit toutes les valeurs)
|
|
*/
|
|
syncStateFromDOM() {
|
|
// Nextcloud
|
|
StateManager.set('nc', 'storage', $('#nextcloud_stockage_source').slider('get value'));
|
|
StateManager.set('nc', 'users', $('#nextcloud_users_source').slider('get value'));
|
|
StateManager.set('nc', 'opt_lxc', $('.ui.toggle.checkbox.nc_opt_lxc').checkbox('is checked'));
|
|
// StateManager.set('nc', 'opt_dedicated', $('.ui.toggle.checkbox.nc_opt_decicated').checkbox('is checked'));
|
|
// StateManager.set('nc', 'opt_onprem', $('.ui.toggle.checkbox.nc_opt_onprem').checkbox('is checked'));
|
|
StateManager.set('nc', 'opt_vol', $('.ui.checkbox.nc_opt_vol').checkbox('is checked'));
|
|
StateManager.set('nc', 'opt_vol_val', parseInt($('#nc_opt_vol_val').val()) || 1);
|
|
|
|
// Web Hosting
|
|
StateManager.set('wh', 'storage', $('#wh_stockage_source').slider('get value'));
|
|
StateManager.set('wh', 'sites', $('#wh_sites_source').slider('get value'));
|
|
StateManager.set('wh', 'opt_preinstall', $('.ui.toggle.checkbox.wh_opt_preinstall').checkbox('is checked'));
|
|
|
|
// Mail
|
|
StateManager.set('mail', 'storage', $('#mail_stockage_source').slider('get value'));
|
|
StateManager.set('mail', 'users', $('#mail_users_source').slider('get value'));
|
|
StateManager.set('mail', 'opt_catchall', $('.ui.toggle.checkbox.mail_opt_catchall').checkbox('is checked'));
|
|
|
|
// VPS
|
|
StateManager.set('vps', 'storage', $('#vps_stockage_source').slider('get value'));
|
|
StateManager.set('vps', 'cpu', $('#vps_cpu_source').slider('get value'));
|
|
StateManager.set('vps', 'ram', $('#vps_ram_source').slider('get value'));
|
|
StateManager.set('vps', 'opt_ip4', $('.ui.toggle.checkbox.vps_opt_ip4').checkbox('is checked'));
|
|
},
|
|
|
|
/**
|
|
* Synchronise le DOM depuis l'état (écrit toutes les valeurs)
|
|
* Utilisé pour charger un état sauvegardé (URL params, localStorage, etc.)
|
|
*/
|
|
syncDOMFromState() {
|
|
const state = StateManager.getState();
|
|
|
|
// Nextcloud
|
|
$('#nextcloud_stockage_source').slider('set value', state.nc.storage);
|
|
$('#nextcloud_stockage_display').html(state.nc.storage);
|
|
$('#nextcloud_users_source').slider('set value', state.nc.users);
|
|
$('#nextcloud_users_display').html(state.nc.users);
|
|
$('#nc_opt_vol_val').val(state.nc.opt_vol_val);
|
|
|
|
this.setCheckboxState('nc_opt_lxc', state.nc.opt_lxc);
|
|
// this.setCheckboxState('nc_opt_decicated', state.nc.opt_dedicated);
|
|
// this.setCheckboxState('nc_opt_onprem', state.nc.opt_onprem);
|
|
this.setCheckboxState('nc_opt_vol', state.nc.opt_vol);
|
|
|
|
// Gérer l'état enabled/disabled des checkboxes hiérarchiques Nextcloud
|
|
// if (state.nc.opt_lxc) {
|
|
// // Si lxc est coché, activer dedicated
|
|
// $('.ui.toggle.checkbox.nc_opt_decicated').checkbox('set enabled');
|
|
// if (state.nc.opt_dedicated) {
|
|
// // Si dedicated est coché, activer onprem
|
|
// $('.ui.toggle.checkbox.nc_opt_onprem').checkbox('set enabled');
|
|
// } else {
|
|
// // Sinon, désactiver onprem
|
|
// $('.ui.toggle.checkbox.nc_opt_onprem').checkbox('set disabled');
|
|
// }
|
|
// } else {
|
|
// // Si lxc n'est pas coché, désactiver dedicated et onprem
|
|
// $('.ui.toggle.checkbox.nc_opt_decicated').checkbox('set disabled');
|
|
// $('.ui.toggle.checkbox.nc_opt_onprem').checkbox('set disabled');
|
|
// }
|
|
|
|
// Gérer l'affichage du shape pour Nextcloud users
|
|
const $ncShape = $('#ncusers');
|
|
const currentSideActive = $ncShape.find('.side.active').index();
|
|
|
|
if (state.nc.opt_lxc) {
|
|
// LXC activé : afficher "utilisateurs illimités" (premier côté du shape, index 0)
|
|
if (currentSideActive !== 0) {
|
|
$ncShape.shape('flip up');
|
|
}
|
|
$('#nextcloud_users_source').addClass('disabled');
|
|
} else {
|
|
// LXC désactivé : afficher le nombre d'utilisateurs (deuxième côté, index 1)
|
|
if (currentSideActive !== 1) {
|
|
$ncShape.shape('flip up');
|
|
}
|
|
$('#nextcloud_users_source').removeClass('disabled');
|
|
}
|
|
|
|
// Web Hosting
|
|
$('#wh_stockage_source').slider('set value', state.wh.storage);
|
|
$('#wh_stockage_display').html(state.wh.storage);
|
|
$('#wh_sites_source').slider('set value', state.wh.sites);
|
|
$('#wh_sites_display').html(state.wh.sites);
|
|
this.setCheckboxState('wh_opt_preinstall', state.wh.opt_preinstall);
|
|
|
|
// Mail
|
|
$('#mail_stockage_source').slider('set value', state.mail.storage);
|
|
$('#mail_stockage_display').html(state.mail.storage);
|
|
$('#mail_users_source').slider('set value', state.mail.users);
|
|
$('#mail_users_display').html(state.mail.users);
|
|
this.setCheckboxState('mail_opt_catchall', state.mail.opt_catchall);
|
|
|
|
// VPS
|
|
$('#vps_stockage_source').slider('set value', state.vps.storage);
|
|
$('#vps_stockage_display').html(state.vps.storage);
|
|
$('#vps_cpu_source').slider('set value', state.vps.cpu);
|
|
$('#vps_cpu_display').html(state.vps.cpu);
|
|
$('#vps_ram_source').slider('set value', state.vps.ram);
|
|
$('#vps_ram_display').html(state.vps.ram);
|
|
this.setCheckboxState('vps_opt_ip4', state.vps.opt_ip4);
|
|
|
|
// Mettre à jour l'affichage des prix
|
|
this.updateDisplay();
|
|
},
|
|
|
|
/**
|
|
* Helper pour définir l'état d'une checkbox
|
|
*/
|
|
setCheckboxState(checkboxClass, checked) {
|
|
const $checkbox = $('.ui.toggle.checkbox.' + checkboxClass);
|
|
if (checked) {
|
|
$checkbox.checkbox('set checked');
|
|
} else {
|
|
$checkbox.checkbox('set unchecked');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Met à jour l'affichage des prix
|
|
*/
|
|
updateDisplay() {
|
|
// Calculer tous les prix via le PriceEngine
|
|
const priceResults = PriceEngine.calculateAllPrices(StateManager.getState());
|
|
|
|
// Mettre à jour l'affichage des prix
|
|
priceResults.forEach((result, index) => {
|
|
$('#price_' + index).html(result.total + '€');
|
|
});
|
|
|
|
// Mettre à jour l'affichage du prix par To (Nextcloud volume)
|
|
if (priceResults[0].ncVolumePerTB !== null) {
|
|
$('#teraeurolow').html(priceResults[0].ncVolumePerTB.toPrecision(3));
|
|
$('#teraeurohigh').html(priceResults[3].ncVolumePerTB.toPrecision(3));
|
|
} else {
|
|
$('#teraeurolow').html('-');
|
|
$('#teraeurohigh').html('-');
|
|
}
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// PHASE 4: URL Sync - Sauvegarde et rechargement de l'état via URL
|
|
// ============================================================================
|
|
|
|
const URLSync = {
|
|
/**
|
|
* Met à jour l'URL avec l'état actuel (sans recharger la page)
|
|
*/
|
|
updateURL() {
|
|
const urlParams = StateManager.toURLParams();
|
|
|
|
if (urlParams) {
|
|
const newURL = window.location.pathname + '?' + urlParams;
|
|
// Utiliser replaceState pour ne pas polluer l'historique
|
|
window.history.replaceState({}, '', newURL);
|
|
} else {
|
|
// Si aucun paramètre, nettoyer l'URL
|
|
window.history.replaceState({}, '', window.location.pathname);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Charge l'état depuis l'URL au chargement de la page
|
|
*/
|
|
loadFromURL() {
|
|
const urlParams = window.location.search;
|
|
|
|
if (urlParams) {
|
|
// Charger l'état depuis l'URL
|
|
StateManager.fromURLParams(urlParams);
|
|
|
|
// Synchroniser le DOM avec cet état
|
|
UIController.syncDOMFromState();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Génère une URL complète partageable
|
|
*/
|
|
getShareableURL() {
|
|
const urlParams = StateManager.toURLParams();
|
|
const baseURL = window.location.origin + window.location.pathname;
|
|
|
|
if (urlParams) {
|
|
return baseURL + '?' + urlParams;
|
|
}
|
|
|
|
return baseURL;
|
|
},
|
|
|
|
/**
|
|
* Copie l'URL partageable dans le presse-papier
|
|
*/
|
|
copyShareableURL() {
|
|
const url = this.getShareableURL();
|
|
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
return navigator.clipboard.writeText(url).then(() => {
|
|
return true;
|
|
}).catch(err => {
|
|
console.error('Erreur lors de la copie:', err);
|
|
return false;
|
|
});
|
|
} else {
|
|
// Fallback pour les navigateurs plus anciens
|
|
const textArea = document.createElement('textarea');
|
|
textArea.value = url;
|
|
textArea.style.position = 'fixed';
|
|
textArea.style.left = '-9999px';
|
|
document.body.appendChild(textArea);
|
|
textArea.select();
|
|
try {
|
|
document.execCommand('copy');
|
|
document.body.removeChild(textArea);
|
|
return Promise.resolve(true);
|
|
} catch (err) {
|
|
console.error('Erreur lors de la copie:', err);
|
|
document.body.removeChild(textArea);
|
|
return Promise.resolve(false);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// PHASE 5: Summary Generator - Génération du récapitulatif
|
|
// ============================================================================
|
|
|
|
const SummaryGenerator = {
|
|
/**
|
|
* Labels lisibles pour chaque service
|
|
*/
|
|
serviceLabels: {
|
|
nc: 'Nextcloud',
|
|
wh: 'Hébergement Web',
|
|
mail: 'Hébergement Mail',
|
|
vps: 'VPS'
|
|
},
|
|
|
|
/**
|
|
* Génère le récapitulatif HTML d'un service Nextcloud
|
|
*/
|
|
generateNextcloudSummary(state) {
|
|
const nc = state.nc;
|
|
const items = [];
|
|
|
|
if (nc.storage > 0) {
|
|
items.push(`<span class="ui label teal">${nc.storage} Go de stockage</span>`);
|
|
}
|
|
|
|
if (!nc.opt_lxc && nc.users > 0) {
|
|
items.push(`<span class="ui label teal">${nc.users} utilisateurs</span>`);
|
|
}
|
|
|
|
if (nc.opt_lxc) {
|
|
items.push(`<span class="ui label blue">Instance privée</span>`);
|
|
items.push(`<span class="ui label">Utilisateurs illimités</span>`);
|
|
}
|
|
|
|
// if (nc.opt_dedicated) {
|
|
// items.push(`<span class="ui label blue">Serveur dédié</span>`);
|
|
// }
|
|
|
|
// if (nc.opt_onprem) {
|
|
// items.push(`<span class="ui label blue">Sur site</span>`);
|
|
// }
|
|
|
|
if (nc.opt_vol && nc.opt_vol_val > 0) {
|
|
items.push(`<span class="ui label violet">${nc.opt_vol_val} To de volume supplémentaire</span>`);
|
|
}
|
|
|
|
return items.length > 0 ? items.join(' ') : null;
|
|
},
|
|
|
|
/**
|
|
* Génère le récapitulatif texte d'un service Nextcloud (pour email)
|
|
*/
|
|
generateNextcloudTextSummary(state) {
|
|
const nc = state.nc;
|
|
const items = [];
|
|
|
|
if (nc.storage > 0) {
|
|
items.push(`${nc.storage} Go de stockage`);
|
|
}
|
|
|
|
if (!nc.opt_lxc && nc.users > 0) {
|
|
items.push(`${nc.users} utilisateurs`);
|
|
}
|
|
|
|
if (nc.opt_lxc) {
|
|
items.push(`Instance privée (utilisateurs illimités)`);
|
|
}
|
|
|
|
// if (nc.opt_dedicated) {
|
|
// items.push(`Serveur dédié`);
|
|
// }
|
|
|
|
// if (nc.opt_onprem) {
|
|
// items.push(`Sur site (on-premise)`);
|
|
// }
|
|
|
|
if (nc.opt_vol && nc.opt_vol_val > 0) {
|
|
items.push(`Gros volume: ${nc.opt_vol_val} To supplémentaires`);
|
|
}
|
|
|
|
return items.length > 0 ? items.join(', ') : null;
|
|
},
|
|
|
|
/**
|
|
* Génère le récapitulatif HTML d'un service Web Hosting
|
|
*/
|
|
generateWebHostingSummary(state) {
|
|
const wh = state.wh;
|
|
const items = [];
|
|
|
|
if (wh.storage > 0) {
|
|
items.push(`<span class="ui label teal">${wh.storage} Go de stockage</span>`);
|
|
}
|
|
|
|
if (wh.sites > 0) {
|
|
items.push(`<span class="ui label teal">${wh.sites} ${wh.sites > 1 ? 'sites' : 'site'}</span>`);
|
|
}
|
|
|
|
if (wh.opt_preinstall) {
|
|
items.push(`<span class="ui label blue">Clé-en-main</span>`);
|
|
}
|
|
|
|
return items.length > 0 ? items.join(' ') : null;
|
|
},
|
|
|
|
/**
|
|
* Génère le récapitulatif texte d'un service Web Hosting (pour email)
|
|
*/
|
|
generateWebHostingTextSummary(state) {
|
|
const wh = state.wh;
|
|
const items = [];
|
|
|
|
if (wh.storage > 0) {
|
|
items.push(`${wh.storage} Go de stockage`);
|
|
}
|
|
|
|
if (wh.sites > 0) {
|
|
items.push(`${wh.sites} ${wh.sites > 1 ? 'sites' : 'site'}`);
|
|
}
|
|
|
|
if (wh.opt_preinstall) {
|
|
items.push(`Pré-installation clé-en-main`);
|
|
}
|
|
|
|
return items.length > 0 ? items.join(', ') : null;
|
|
},
|
|
|
|
/**
|
|
* Génère le récapitulatif HTML d'un service Mail
|
|
*/
|
|
generateMailSummary(state) {
|
|
const mail = state.mail;
|
|
const items = [];
|
|
|
|
if (mail.storage > 0) {
|
|
items.push(`<span class="ui label teal">${mail.storage} Go de stockage</span>`);
|
|
}
|
|
|
|
if (mail.users > 0) {
|
|
items.push(`<span class="ui label teal">${mail.users} ${mail.users > 1 ? 'boîtes' : 'boîte'} mail</span>`);
|
|
}
|
|
|
|
if (mail.opt_catchall) {
|
|
items.push(`<span class="ui label blue">Catch-all</span>`);
|
|
}
|
|
|
|
return items.length > 0 ? items.join(' ') : null;
|
|
},
|
|
|
|
/**
|
|
* Génère le récapitulatif texte d'un service Mail (pour email)
|
|
*/
|
|
generateMailTextSummary(state) {
|
|
const mail = state.mail;
|
|
const items = [];
|
|
|
|
if (mail.storage > 0) {
|
|
items.push(`${mail.storage} Go de stockage`);
|
|
}
|
|
|
|
if (mail.users > 0) {
|
|
items.push(`${mail.users} ${mail.users > 1 ? 'boîtes' : 'boîte'} mail`);
|
|
}
|
|
|
|
if (mail.opt_catchall) {
|
|
items.push(`Catch-all activé`);
|
|
}
|
|
|
|
return items.length > 0 ? items.join(', ') : null;
|
|
},
|
|
|
|
/**
|
|
* Génère le récapitulatif HTML d'un service VPS
|
|
*/
|
|
generateVPSSummary(state) {
|
|
const vps = state.vps;
|
|
const items = [];
|
|
|
|
if (vps.storage > 0) {
|
|
items.push(`<span class="ui label teal">${vps.storage} Go HDD</span>`);
|
|
}
|
|
|
|
if (vps.cpu > 0) {
|
|
items.push(`<span class="ui label teal">${vps.cpu} vCPU</span>`);
|
|
}
|
|
|
|
if (vps.ram > 0) {
|
|
items.push(`<span class="ui label teal">${vps.ram} Go RAM</span>`);
|
|
}
|
|
|
|
if (vps.opt_ip4) {
|
|
items.push(`<span class="ui label blue">IPv4 dédiée</span>`);
|
|
}
|
|
|
|
return items.length > 0 ? items.join(' ') : null;
|
|
},
|
|
|
|
/**
|
|
* Génère le récapitulatif texte d'un service VPS (pour email)
|
|
*/
|
|
generateVPSTextSummary(state) {
|
|
const vps = state.vps;
|
|
const items = [];
|
|
|
|
if (vps.storage > 0) {
|
|
items.push(`${vps.storage} Go HDD`);
|
|
}
|
|
|
|
if (vps.cpu > 0) {
|
|
items.push(`${vps.cpu} vCPU`);
|
|
}
|
|
|
|
if (vps.ram > 0) {
|
|
items.push(`${vps.ram} Go RAM`);
|
|
}
|
|
|
|
if (vps.opt_ip4) {
|
|
items.push(`Adresse IPv4 publique dédiée`);
|
|
}
|
|
|
|
return items.length > 0 ? items.join(', ') : null;
|
|
},
|
|
|
|
/**
|
|
* Met à jour le récapitulatif complet
|
|
*/
|
|
updateSummary() {
|
|
const state = StateManager.getState();
|
|
const $container = $('#summary-container');
|
|
const $content = $('#summary-content');
|
|
|
|
// Vérifier s'il y a au moins un service actif
|
|
const hasActiveServices = PriceEngine.isServiceActive('nc', state)
|
|
|| PriceEngine.isServiceActive('wh', state)
|
|
|| PriceEngine.isServiceActive('mail', state)
|
|
|| PriceEngine.isServiceActive('vps', state);
|
|
|
|
if (!hasActiveServices) {
|
|
$container.hide();
|
|
return;
|
|
}
|
|
|
|
// Générer le HTML du récapitulatif
|
|
let html = '';
|
|
|
|
// Nextcloud
|
|
const ncSummary = this.generateNextcloudSummary(state);
|
|
if (ncSummary) {
|
|
html += `
|
|
<div class="item">
|
|
<!-- <i class="large cloud middle aligned icon"></i>-->
|
|
<div class="content">
|
|
<div class="header">Nextcloud</div>
|
|
<div class="description">${ncSummary}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Web Hosting
|
|
const whSummary = this.generateWebHostingSummary(state);
|
|
if (whSummary) {
|
|
html += `
|
|
<div class="item">
|
|
<!-- <i class="large globe middle aligned icon"></i>-->
|
|
<div class="content">
|
|
<div class="header">Hébergement Web</div>
|
|
<div class="description">${whSummary}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Mail
|
|
const mailSummary = this.generateMailSummary(state);
|
|
if (mailSummary) {
|
|
html += `
|
|
<div class="item">
|
|
<!-- <i class="large envelope middle aligned icon"></i>-->
|
|
<div class="content">
|
|
<div class="header">Hébergement Mail</div>
|
|
<div class="description">${mailSummary}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// VPS
|
|
const vpsSummary = this.generateVPSSummary(state);
|
|
if (vpsSummary) {
|
|
html += `
|
|
<div class="item">
|
|
<!-- <i class="large server middle aligned icon"></i>-->
|
|
<div class="content">
|
|
<div class="header">VPS</div>
|
|
<div class="description">${vpsSummary}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
$content.html(html);
|
|
$container.show();
|
|
},
|
|
|
|
/**
|
|
* Génère le corps de l'email de commande
|
|
*/
|
|
generateOrderEmailBody() {
|
|
const state = StateManager.getState();
|
|
const priceResults = PriceEngine.calculateAllPrices(state);
|
|
const shareURL = URLSync.getShareableURL();
|
|
|
|
let body = 'Bonjour,\n\n';
|
|
body += 'Je souhaite commander l\'offre suivante sélectionnée sur le site clawd.fr :\n\n';
|
|
|
|
// Nextcloud
|
|
const ncText = this.generateNextcloudTextSummary(state);
|
|
if (ncText) {
|
|
body += 'Nextcloud: ' + ncText + '\n';
|
|
}
|
|
|
|
// Web
|
|
const whText = this.generateWebHostingTextSummary(state);
|
|
if (whText) {
|
|
body += 'Web: ' + whText + '\n';
|
|
}
|
|
|
|
// Mail
|
|
const mailText = this.generateMailTextSummary(state);
|
|
if (mailText) {
|
|
body += 'Mail: ' + mailText + '\n';
|
|
}
|
|
|
|
// VPS
|
|
const vpsText = this.generateVPSTextSummary(state);
|
|
if (vpsText) {
|
|
body += 'VPS: ' + vpsText + '\n';
|
|
}
|
|
|
|
body += '\n';
|
|
|
|
body += 'Pour ces services, le calculateur a indiqué la fourchette suivante :\n';
|
|
body += ` - Minimum : ${priceResults[0].total}€/mois\n`;
|
|
body += ` - Correct : ${priceResults[1].total}€/mois\n`;
|
|
body += ` - Généreux : ${priceResults[2].total}€/mois\n`;
|
|
body += ` - Maximum : ${priceResults[3].total}€/mois\n\n`;
|
|
|
|
body += 'Fourchette dans laquelle je choisis le tarif suivant : [à compléter]\n\n';
|
|
|
|
body += 'Informations complémentaires :\n';
|
|
body += 'Je représente : [une entreprise/association/famille/seulement moi-même]\n';
|
|
body += 'Nom / Raison sociale : [à compléter]\n';
|
|
body += 'Je dispose déjà d\'un nom de domaine : [à compléter]\n';
|
|
body += 'Date de mise en service souhaitée : [à compléter]\n';
|
|
body += 'Commentaires ou demandes spécifiques : [à compléter]\n\n';
|
|
|
|
body += 'Lien de configuration : ' + shareURL + '\n\n'
|
|
|
|
body += 'Cordialement,\n';
|
|
body += '[Votre nom]';
|
|
|
|
return body;
|
|
},
|
|
|
|
/**
|
|
* Ouvre le client mail avec l'email pré-rempli
|
|
*/
|
|
openOrderEmail() {
|
|
const subject = encodeURIComponent('Commande depuis le calculateur clawd.fr');
|
|
const body = encodeURIComponent(this.generateOrderEmailBody());
|
|
const mailto = `mailto:contact@clawd.fr?subject=${subject}&body=${body}`;
|
|
|
|
window.location.href = mailto;
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// PHASE 6: Example Formulas - Formules d'exemples pré-définies
|
|
// ============================================================================
|
|
|
|
const ExampleFormulas = {
|
|
formulas: {
|
|
solo: {
|
|
nc: { storage: 50, users: 1, opt_lxc: false, /* opt_dedicated: false, opt_onprem: false, */ opt_vol: false, opt_vol_val: 1 },
|
|
wh: { storage: 0, sites: 0, opt_preinstall: false },
|
|
mail: { storage: 20, users: 1, opt_catchall: true },
|
|
vps: { storage: 0, cpu: 0, ram: 0, opt_ip4: false }
|
|
},
|
|
team: {
|
|
nc: { storage: 150, users: 5, opt_lxc: false, /* opt_dedicated: false, opt_onprem: false, */ opt_vol: false, opt_vol_val: 1 },
|
|
wh: { storage: 10, sites: 1, opt_preinstall: false },
|
|
mail: { storage: 25, users: 5, opt_catchall: false },
|
|
vps: { storage: 0, cpu: 0, ram: 0, opt_ip4: false }
|
|
},
|
|
company: {
|
|
nc: { storage: 0, users: 0, opt_lxc: true, /* opt_dedicated: false, opt_onprem: false, */ opt_vol: true, opt_vol_val: 2 },
|
|
wh: { storage: 50, sites: 3, opt_preinstall: false },
|
|
mail: { storage: 100, users: 20, opt_catchall: false },
|
|
vps: { storage: 0, cpu: 0, ram: 0, opt_ip4: false }
|
|
},
|
|
storage: {
|
|
nc: { storage: 0, users: 0, opt_lxc: true, /* opt_dedicated: true, opt_onprem: false, */ opt_vol: true, opt_vol_val: 10 },
|
|
wh: { storage: 0, sites: 0, opt_preinstall: false },
|
|
mail: { storage: 0, users: 0, opt_catchall: false },
|
|
vps: { storage: 0, cpu: 0, ram: 0, opt_ip4: false }
|
|
},
|
|
developer: {
|
|
nc: { storage: 0, users: 0, opt_lxc: false, /* opt_dedicated: false, opt_onprem: false, */ opt_vol: false, opt_vol_val: 1 },
|
|
wh: { storage: 0, sites: 0, opt_preinstall: false },
|
|
mail: { storage: 0, users: 0, opt_catchall: false },
|
|
vps: { storage: 50, cpu: 2, ram: 4, opt_ip4: false }
|
|
},
|
|
devteam: {
|
|
nc: { storage: 0, users: 0, opt_lxc: false, /* opt_dedicated: false, opt_onprem: false, */ opt_vol: false, opt_vol_val: 1 },
|
|
wh: { storage: 0, sites: 0, opt_preinstall: false },
|
|
mail: { storage: 0, users: 0, opt_catchall: false },
|
|
vps: { storage: 250, cpu: 8, ram: 16, opt_ip4: false }
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Charge une formule d'exemple
|
|
*/
|
|
loadFormula(formulaName) {
|
|
const formula = this.formulas[formulaName];
|
|
if (!formula) return;
|
|
|
|
// Mettre à jour l'état
|
|
StateManager.setState(formula);
|
|
|
|
// Synchroniser le DOM
|
|
UIController.syncDOMFromState();
|
|
|
|
// Mettre à jour l'URL
|
|
URLSync.updateURL();
|
|
|
|
// Mettre à jour le récapitulatif
|
|
SummaryGenerator.updateSummary();
|
|
|
|
// Scroller vers le calculateur
|
|
$('html, body').animate({
|
|
scrollTop: $('#calc').offset().top - 100
|
|
}, 500);
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// Initialisation de l'application
|
|
// ============================================================================
|
|
|
|
$(document).ready(function() {
|
|
// Initialiser tous les contrôles UI
|
|
UIController.initialize();
|
|
|
|
// Phase 4: Charger l'état depuis l'URL si présent
|
|
const loadedFromURL = URLSync.loadFromURL();
|
|
|
|
// Affichage initial des prix (seulement si pas chargé depuis URL)
|
|
if (!loadedFromURL) {
|
|
UIController.updateDisplay();
|
|
}
|
|
|
|
// Phase 5: Initialiser le récapitulatif
|
|
SummaryGenerator.updateSummary();
|
|
|
|
// Phase 5: Bouton pour copier l'URL
|
|
$('#copy-url-btn').on('click', function() {
|
|
URLSync.copyShareableURL().then(success => {
|
|
if (success) {
|
|
$('#copy-notification').fadeIn().delay(2000).fadeOut();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Phase 5: Bouton Commander
|
|
$('#order-btn').on('click', function() {
|
|
SummaryGenerator.openOrderEmail();
|
|
});
|
|
|
|
// Phase 6: Formules d'exemples
|
|
$('.example-formula').on('click', function(e) {
|
|
e.preventDefault();
|
|
const formula = $(this).data('formula');
|
|
ExampleFormulas.loadFormula(formula);
|
|
});
|
|
});
|