RC UI: uniformiser labels flottants et fiabiliser blocages projet/tarif

This commit is contained in:
Alexis Burnaz 2026-04-21 15:24:37 +02:00
parent 4927d22387
commit 5338f682af
7 changed files with 693 additions and 19 deletions

View File

@ -458,6 +458,37 @@ a.grille-garanties:hover{
text-align: left; text-align: left;
} }
.rc-has-floating-label {
position: relative;
margin-top: 1.8rem;
}
.rc-has-floating-label .rc-field-label.rc-floating-label {
position: absolute;
top: 0.95rem;
left: 0.75rem;
margin: 0;
font-size: 1rem;
font-weight: 500;
color: #7f8c8d;
pointer-events: none;
transition: top .18s ease, font-size .18s ease, color .18s ease;
background: #fff;
padding: 0 4px;
z-index: 2;
}
.rc-has-floating-label .rc-field-label.rc-floating-label.active {
top: -0.55rem;
font-size: .78rem;
font-weight: 700;
color: #1a237e;
}
.rc-tarifettes-hidden {
display: none !important;
}
.rc-three-col-grid { .rc-three-col-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));

View File

@ -13,6 +13,12 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
let hasSavedGrilleData = false; // évite d'écraser une grille déjà enregistrée let hasSavedGrilleData = false; // évite d'écraser une grille déjà enregistrée
let rcProjetGuard = null; let rcProjetGuard = null;
function syncRCFloatingLabels() {
if (window.RCValidationUtils && typeof window.RCValidationUtils.syncFloatingLabels === 'function') {
window.RCValidationUtils.syncFloatingLabels(document);
}
}
// Initialisation des tag pour select // Initialisation des tag pour select
var tagAnimauxVivants = false; var tagAnimauxVivants = false;
var tagMultimodal = false; var tagMultimodal = false;
@ -90,11 +96,13 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
} }
updatePrimeReferenceRow(); updatePrimeReferenceRow();
setupRCSafeValidation(); setupRCSafeValidation();
syncRCFloatingLabels();
updateSubmitButtonState('projetForm'); updateSubmitButtonState('projetForm');
setTimeout(() => { setTimeout(() => {
updatePrimeReferenceRow(); updatePrimeReferenceRow();
if (rcProjetGuard) rcProjetGuard.refresh(); if (rcProjetGuard) rcProjetGuard.refresh();
syncRCFloatingLabels();
}, 350); }, 350);
} }
@ -251,7 +259,13 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
rcProjetGuard = window.RCValidationUtils.createGuard({ rcProjetGuard = window.RCValidationUtils.createGuard({
summaryId: 'rcProjetBlockingSummary', summaryId: 'rcProjetBlockingSummary',
summaryTitle: 'Impossible d\'enregistrer ou de continuer car :', summaryTitle: 'Impossible d\'enregistrer ou de continuer car :',
blockTargets: ['#projetFormBtn', '#generateDeclinaison', '#generateProject'] blockTargets: ['#projetFormBtn', '#generateDeclinaison', '#generateProject'],
onChange: function (messages) {
const actionsRow = document.getElementById('projetActionsRow');
if (actionsRow) {
actionsRow.classList.toggle('rc-tarifettes-hidden', messages.length > 0);
}
}
}); });
rcProjetGuard.registerField('#CA', { rcProjetGuard.registerField('#CA', {
@ -379,6 +393,21 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
required: false required: false
}); });
rcProjetGuard.registerExternal('marchandises-valides', function () {
const marchandiseSelector = document.getElementById('marchandise-selector');
if (!marchandiseSelector) {
return { valid: true };
}
const selectedCount = Array.from(marchandiseSelector.selectedOptions || []).length;
if (selectedCount > 0) {
return { valid: true };
}
return {
valid: false,
message: 'Marchandises invalides : sélectionnez au moins une marchandise.'
};
});
rcProjetGuard.registerExternal('prime-reference', function () { rcProjetGuard.registerExternal('prime-reference', function () {
const state = getPrimeReferenceState(); const state = getPrimeReferenceState();
if (state.valid) { if (state.valid) {
@ -2105,6 +2134,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
// Réactiver la détection juste après les dispatchs pour conserver le contrôle. // Réactiver la détection juste après les dispatchs pour conserver le contrôle.
setTimeout(() => { setTimeout(() => {
isRestoringValue = false; isRestoringValue = false;
syncRCFloatingLabels();
}, 0); }, 0);
} }
@ -2118,18 +2148,26 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
document.getElementById('activity-selector').addEventListener('change', function () { document.getElementById('activity-selector').addEventListener('change', function () {
handleActivitySelection(); handleActivitySelection();
if (rcProjetGuard) rcProjetGuard.refresh();
syncRCFloatingLabels();
}); });
document.getElementById('marchandise-selector').addEventListener('change', function () { document.getElementById('marchandise-selector').addEventListener('change', function () {
handleMarchandiseSelection(); handleMarchandiseSelection();
if (rcProjetGuard) rcProjetGuard.refresh();
syncRCFloatingLabels();
}); });
document.getElementById('garantieRCC-selector').addEventListener('change', function () { document.getElementById('garantieRCC-selector').addEventListener('change', function () {
handleGarantieRCCSelection(); handleGarantieRCCSelection();
if (rcProjetGuard) rcProjetGuard.refresh();
syncRCFloatingLabels();
}); });
document.getElementById('garantieRCE-selector').addEventListener('change', function () { document.getElementById('garantieRCE-selector').addEventListener('change', function () {
handleGarantieRCESelection(); handleGarantieRCESelection();
if (rcProjetGuard) rcProjetGuard.refresh();
syncRCFloatingLabels();
}); });
document.getElementById('choixRCE').addEventListener('change', function () { document.getElementById('choixRCE').addEventListener('change', function () {
@ -3192,6 +3230,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
updatePrimeReferenceRow(); updatePrimeReferenceRow();
if (rcProjetGuard) rcProjetGuard.refresh(); if (rcProjetGuard) rcProjetGuard.refresh();
syncRCFloatingLabels();
} }
function populateGrAdvalo(jsonData, tableID) { function populateGrAdvalo(jsonData, tableID) {

View File

@ -0,0 +1,552 @@
(function () {
function normalizeNumericInput(raw) {
if (raw == null) return '';
return String(raw).trim().replace(/\s+/g, '');
}
function parseLooseNumber(raw) {
const normalized = normalizeNumericInput(raw).replace(',', '.');
if (!normalized) return NaN;
const parsed = Number(normalized);
return Number.isFinite(parsed) ? parsed : NaN;
}
function formatFrenchAmount(value, digits) {
const number = Number(value);
if (!Number.isFinite(number)) return '0.00';
return number.toLocaleString('fr-FR', {
minimumFractionDigits: digits,
maximumFractionDigits: digits
});
}
function syncFloatingLabels(root) {
const scope = root && root.querySelectorAll ? root : document;
const labels = scope.querySelectorAll('.rc-field-label[for]');
labels.forEach(function (label) {
const fieldId = label.getAttribute('for');
if (!fieldId) return;
const field = document.getElementById(fieldId);
if (!field) return;
const inputField = label.closest('.input-field');
const container = inputField || label.parentElement;
if (container) {
container.classList.add('rc-has-floating-label');
}
label.classList.add('rc-floating-label');
if (label.dataset.rcFloatBound === 'true') {
// Déjà lié: on force juste un resync visuel.
const hasValue = String(field.value || '').trim() !== '';
label.classList.toggle('active', hasValue || document.activeElement === field);
return;
}
const hasPrefix = Boolean(inputField && inputField.querySelector('.prefix'));
label.style.left = hasPrefix ? '3rem' : '0.75rem';
const syncState = function () {
const hasValue = String(field.value || '').trim() !== '';
label.classList.toggle('active', hasValue || document.activeElement === field);
};
field.addEventListener('focus', syncState);
field.addEventListener('blur', syncState);
field.addEventListener('input', syncState);
field.addEventListener('change', syncState);
label.dataset.rcFloatBound = 'true';
syncState();
});
if (window.M && typeof window.M.updateTextFields === 'function') {
window.M.updateTextFields();
}
}
function ensureErrorSlot(field, customErrorId) {
if (!field) return null;
if (customErrorId) {
const found = document.getElementById(customErrorId);
if (found) return found;
}
if (field.id) {
const byConvention = document.getElementById(field.id + '-error');
if (byConvention) return byConvention;
}
if (field.dataset && field.dataset.errorTarget) {
const byData = document.getElementById(field.dataset.errorTarget);
if (byData) return byData;
}
let sibling = field.parentElement ? field.parentElement.querySelector('.rc-inline-error') : null;
if (sibling) return sibling;
sibling = document.createElement('span');
sibling.className = 'helper-text red-text rc-inline-error';
sibling.style.display = 'none';
field.insertAdjacentElement('afterend', sibling);
return sibling;
}
function pickFieldLabel(field, fallbackLabel) {
if (fallbackLabel) return fallbackLabel;
if (!field) return 'Champ';
if (field.dataset && field.dataset.rcLabel) return field.dataset.rcLabel;
const directLabel = field.id ? document.querySelector('label[for="' + field.id + '"]') : null;
if (directLabel && directLabel.textContent.trim()) return directLabel.textContent.trim();
const cardLabel = field.closest('.row, .input-field, td, .card-content')?.querySelector('.rc-field-label');
if (cardLabel && cardLabel.textContent.trim()) return cardLabel.textContent.trim();
const th = field.closest('td')?.parentElement?.querySelector('th');
if (th && th.textContent.trim()) return th.textContent.trim();
return field.name || field.id || 'Champ';
}
function setFieldError(field, errorSlot, message) {
if (!errorSlot) return;
errorSlot.textContent = message || '';
errorSlot.style.display = message ? 'block' : 'none';
if (field) {
if (message) {
field.classList.add('invalid');
} else {
field.classList.remove('invalid');
}
}
}
function hasForbiddenNumericChars(value) {
return /[A-Za-z€$£¥]/.test(value);
}
function validateNumeric(value, options) {
const raw = normalizeNumericInput(value);
const decimals = Number.isInteger(options.decimals) ? options.decimals : 2;
const label = options.label || 'Champ';
const required = Boolean(options.required);
if (!raw) {
if (required) {
return { valid: false, message: label + ' est obligatoire.' };
}
return { valid: true };
}
if (hasForbiddenNumericChars(raw)) {
return { valid: false, message: label + ' doit contenir uniquement des chiffres, avec virgule ou point décimal.' };
}
if (/[^0-9.,-]/.test(raw)) {
return { valid: false, message: label + ' contient des caractères interdits.' };
}
if ((raw.match(/,/g) || []).length > 1 || (raw.match(/\./g) || []).length > 1) {
return { valid: false, message: label + ' a un format invalide.' };
}
if (raw.includes('-') && raw.indexOf('-') !== 0) {
return { valid: false, message: label + ' a un format invalide.' };
}
const decimalRegex = new RegExp('^-?\\d+(?:[.,]\\d{1,' + decimals + '})?$');
if (!decimalRegex.test(raw)) {
return { valid: false, message: label + ' doit être un nombre valide (max ' + decimals + ' décimales).' };
}
const number = parseLooseNumber(raw);
if (!Number.isFinite(number)) {
return { valid: false, message: label + ' doit être un nombre valide.' };
}
if (typeof options.min === 'number' && number < options.min) {
return { valid: false, message: label + ' doit être supérieur ou égal à ' + options.min + '.' };
}
if (typeof options.max === 'number' && number > options.max) {
return { valid: false, message: label + ' doit être inférieur ou égal à ' + options.max + '.' };
}
if (options.positive && number <= 0) {
return { valid: false, message: label + ' doit être strictement supérieur à 0.' };
}
return { valid: true, normalized: raw.replace(',', '.') };
}
function validateInteger(value, options) {
const raw = normalizeNumericInput(value);
const label = options.label || 'Champ';
const required = Boolean(options.required);
if (!raw) {
if (required) {
return { valid: false, message: label + ' est obligatoire.' };
}
return { valid: true };
}
if (!/^\d+$/.test(raw)) {
return { valid: false, message: label + ' doit contenir uniquement des chiffres entiers.' };
}
const number = Number(raw);
if (typeof options.min === 'number' && number < options.min) {
return { valid: false, message: label + ' doit être supérieur ou égal à ' + options.min + '.' };
}
if (typeof options.max === 'number' && number > options.max) {
return { valid: false, message: label + ' doit être inférieur ou égal à ' + options.max + '.' };
}
return { valid: true };
}
function validateImmat(value, options) {
const raw = String(value || '').trim().toUpperCase();
const label = options.label || 'Immatriculation';
if (!raw) {
if (options.required) {
return { valid: false, message: label + ' est obligatoire.' };
}
return { valid: true, normalized: '' };
}
if (!/^[A-Z0-9-]+$/.test(raw)) {
return { valid: false, message: label + ' doit contenir uniquement des lettres majuscules, chiffres et tirets.' };
}
return { valid: true, normalized: raw };
}
function validateTextSafe(value, options) {
const raw = String(value || '').trim();
const label = options.label || 'Champ';
if (!raw) {
if (options.required) {
return { valid: false, message: label + ' est obligatoire.' };
}
return { valid: true };
}
if (/[<>;&"]/g.test(raw)) {
return { valid: false, message: label + ' contient des caractères interdits (<, >, &, ;, ").' };
}
const pattern = options.pattern || /^[A-Za-zÀ-ÖØ-öø-ÿ0-9\s'.,:/()\-_]+$/;
if (!pattern.test(raw)) {
return { valid: false, message: label + ' contient des caractères non autorisés.' };
}
return { valid: true };
}
function validateNumberOrConsult(raw, options) {
const value = String(raw || '').trim();
const lower = value.toLowerCase();
if (!value) {
if (options.required) {
return { valid: false, message: (options.label || 'Champ') + ' est obligatoire.' };
}
return { valid: true };
}
if (lower === 'nous consulter') {
return { valid: true };
}
return validateNumeric(value, options);
}
function evaluateRule(field, config) {
const profile = config.profile;
const label = pickFieldLabel(field, config.label);
const required = typeof config.requiredWhen === 'function' ? Boolean(config.requiredWhen(field)) : Boolean(config.required);
let result;
switch (profile) {
case 'numeric':
result = validateNumeric(field.value, {
label: label,
required: required,
decimals: config.decimals,
min: config.min,
max: config.max,
positive: config.positive
});
break;
case 'integer':
result = validateInteger(field.value, {
label: label,
required: required,
min: config.min,
max: config.max
});
break;
case 'immat':
result = validateImmat(field.value, {
label: label,
required: required
});
break;
case 'text':
result = validateTextSafe(field.value, {
label: label,
required: required,
pattern: config.pattern
});
break;
case 'number_or_consulter':
result = validateNumberOrConsult(field.value, {
label: label,
required: required,
decimals: config.decimals,
min: config.min,
max: config.max,
positive: config.positive
});
break;
default:
result = { valid: true };
}
if (result.normalized != null && config.normalize !== false && field.value !== result.normalized) {
field.value = result.normalized;
}
return {
valid: result.valid,
message: result.message || '',
label: label
};
}
function createSummaryElement(summaryId) {
if (!summaryId) return null;
return document.getElementById(summaryId);
}
function createGuard(options) {
const summary = createSummaryElement(options.summaryId);
const summaryTitle = String(options.summaryTitle || 'Impossible de continuer car :');
const state = {
fieldRules: new Map(),
fieldErrors: new Map(),
externalChecks: new Map(),
targetButtons: [],
onChange: typeof options.onChange === 'function' ? options.onChange : null
};
const targetSelectors = Array.isArray(options.blockTargets) ? options.blockTargets : [];
targetSelectors.forEach(function (selector) {
document.querySelectorAll(selector).forEach(function (button) {
if (!button.dataset.rcOriginalDisabled) {
button.dataset.rcOriginalDisabled = button.disabled ? 'true' : 'false';
}
state.targetButtons.push(button);
});
});
function applySummary(messages) {
if (!summary) return;
if (!messages.length) {
summary.style.display = 'none';
summary.innerHTML = '';
return;
}
const uniqueMessages = Array.from(new Set(messages));
const list = uniqueMessages.map(function (msg) {
return '<li>' + msg + '</li>';
}).join('');
summary.innerHTML = [
'<div class="rc-blocking-title">' + summaryTitle + '</div>',
'<ul class="rc-blocking-list">',
list,
'</ul>'
].join('');
summary.style.display = 'block';
}
function applyBlocking(hasErrors) {
state.targetButtons.forEach(function (button) {
if (!button) return;
if (hasErrors) {
button.disabled = true;
button.dataset.rcInvalid = 'true';
return;
}
if (button.dataset.rcInvalid === 'true') {
button.dataset.rcInvalid = 'false';
button.disabled = button.dataset.rcOriginalDisabled === 'true';
}
});
}
function recompute() {
const messages = [];
state.fieldErrors.forEach(function (error) {
if (error && error.message) {
messages.push(error.message);
}
});
state.externalChecks.forEach(function (entry, key) {
const checkResult = typeof entry.fn === 'function' ? entry.fn() : { valid: true };
if (!checkResult || checkResult.valid === false) {
const message = checkResult && checkResult.message ? checkResult.message : entry.message || key;
messages.push(message);
}
});
applySummary(messages);
applyBlocking(messages.length > 0);
if (state.onChange) {
state.onChange(messages);
}
return messages;
}
function validateField(field) {
const config = state.fieldRules.get(field);
if (!config) return;
if (!field || !field.isConnected || field.disabled) {
if (config.errorSlot) setFieldError(field, config.errorSlot, '');
state.fieldErrors.delete(config.key);
recompute();
return;
}
if (typeof config.activeWhen === 'function' && !config.activeWhen(field)) {
setFieldError(field, config.errorSlot, '');
state.fieldErrors.delete(config.key);
recompute();
return;
}
// Sécurité défaut: sans activeWhen explicite, on ignore un champ masqué.
if (typeof config.activeWhen !== 'function' && field.type !== 'hidden' && field.offsetParent === null) {
setFieldError(field, config.errorSlot, '');
state.fieldErrors.delete(config.key);
recompute();
return;
}
const result = evaluateRule(field, config);
setFieldError(field, config.errorSlot, result.valid ? '' : result.message);
if (result.valid) {
state.fieldErrors.delete(config.key);
} else {
state.fieldErrors.set(config.key, {
message: result.message
});
}
recompute();
}
function attachField(field, config) {
if (!field) return;
const key = config.key || (field.id ? field.id : (field.name ? field.name : ('field_' + state.fieldRules.size)));
if (field.dataset && field.dataset.rcGuardAttached === 'true' && state.fieldRules.has(field)) return;
const fieldConfig = Object.assign({}, config, {
key: key,
errorSlot: ensureErrorSlot(field, config.errorId)
});
state.fieldRules.set(field, fieldConfig);
if (field.dataset) {
field.dataset.rcGuardAttached = 'true';
}
const handler = function () {
validateField(field);
};
field.addEventListener('input', handler);
field.addEventListener('change', handler);
field.addEventListener('blur', handler);
validateField(field);
}
function registerField(selectorOrElement, config) {
if (!selectorOrElement || !config || !config.profile) return;
if (typeof selectorOrElement === 'string') {
document.querySelectorAll(selectorOrElement).forEach(function (element) {
attachField(element, config);
});
return;
}
attachField(selectorOrElement, config);
}
function observe(selector, config) {
if (!selector || !config || !config.profile) return;
registerField(selector, config);
const observer = new MutationObserver(function () {
registerField(selector, config);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
return observer;
}
function registerExternal(key, fn, fallbackMessage) {
if (!key || typeof fn !== 'function') return;
state.externalChecks.set(key, {
fn: fn,
message: fallbackMessage || ''
});
recompute();
}
function refresh() {
state.fieldRules.forEach(function (_cfg, field) {
validateField(field);
});
return recompute();
}
return {
registerField: registerField,
observe: observe,
registerExternal: registerExternal,
refresh: refresh,
formatFrenchAmount: formatFrenchAmount,
parseLooseNumber: parseLooseNumber
};
}
window.RCValidationUtils = {
createGuard: createGuard,
parseLooseNumber: parseLooseNumber,
formatFrenchAmount: formatFrenchAmount,
syncFloatingLabels: syncFloatingLabels
};
})();

View File

@ -78,6 +78,18 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
let rcTarifGuard = null; let rcTarifGuard = null;
let rcTarifHasErrors = false; let rcTarifHasErrors = false;
function syncRCFloatingLabels() {
if (window.RCValidationUtils && typeof window.RCValidationUtils.syncFloatingLabels === 'function') {
window.RCValidationUtils.syncFloatingLabels(document);
}
}
function updateTarifettesVisibility() {
const tarifettesContainer = document.getElementById('tarifettesContainer');
if (!tarifettesContainer) return;
tarifettesContainer.classList.toggle('rc-tarifettes-hidden', rcTarifHasErrors);
}
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
// FONCTIONS HELPERS // FONCTIONS HELPERS
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
@ -107,6 +119,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
} }
button.title = 'Corrigez les erreurs de saisie avant de sélectionner un tarif.'; button.title = 'Corrigez les erreurs de saisie avant de sélectionner un tarif.';
}); });
updateTarifettesVisibility();
} }
function setupRCSafeValidation() { function setupRCSafeValidation() {
@ -287,6 +300,29 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
required: false required: false
}); });
rcTarifGuard.registerExternal('marchandises-valides', function () {
const visibleContainers = Array.from(document.querySelectorAll('[name^="mar"]')).filter((container) => {
return container && container.offsetParent !== null;
});
if (!visibleContainers.length) {
return { valid: true };
}
const hasAtLeastOneChecked = visibleContainers.some((container) => {
return Boolean(container.querySelector('input[type="checkbox"]:checked'));
});
if (hasAtLeastOneChecked) {
return { valid: true };
}
return {
valid: false,
message: 'Marchandises invalides : sélectionnez au moins une marchandise.'
};
});
rcTarifGuard.refresh(); rcTarifGuard.refresh();
updateTarifChoiceButtonsState(); updateTarifChoiceButtonsState();
} }
@ -418,12 +454,14 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
setupTarifetteButtons(); setupTarifetteButtons();
populateFormData(); populateFormData();
setupRCSafeValidation(); setupRCSafeValidation();
syncRCFloatingLabels();
updatePercentageIndicator(100); // Initialiser à 100% updatePercentageIndicator(100); // Initialiser à 100%
calcGlobal(); calcGlobal();
setTimeout(() => { setTimeout(() => {
if (rcTarifGuard) rcTarifGuard.refresh(); if (rcTarifGuard) rcTarifGuard.refresh();
updateTarifChoiceButtonsState(); updateTarifChoiceButtonsState();
syncRCFloatingLabels();
}, 300); }, 300);
}) })
} }
@ -456,6 +494,16 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
}); });
} }
document.addEventListener('change', function (event) {
const target = event.target;
if (!target) return;
if (!target.matches('[name^="mar"] input[type="checkbox"], [name^="actCompl"] input[type="checkbox"], #checkVoiturier, #checkCommissionnaire, #checkDemenageur, #checkLogistique, #checkAutocariste, #checkAutres, #zone1, #zone2, #zone3, #zone4, #zone5, #zone6')) {
return;
}
if (rcTarifGuard) rcTarifGuard.refresh();
updateTarifChoiceButtonsState();
});
document.getElementById('loadHistoriqueBtn').addEventListener('click', function () { document.getElementById('loadHistoriqueBtn').addEventListener('click', function () {
handleLoadHistoriqueBtn(); handleLoadHistoriqueBtn();
}); });
@ -1820,10 +1868,12 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio
} }
console.log('Formulaire tarif pre-rempli avec succes'); console.log('Formulaire tarif pre-rempli avec succes');
syncRCFloatingLabels();
// Recalculer après pré-remplissage // Recalculer après pré-remplissage
setTimeout(() => { setTimeout(() => {
calcGlobal(); calcGlobal();
syncRCFloatingLabels();
}, 500); }, 500);
} }

View File

@ -59,6 +59,8 @@
<script src="/js/verif-form.js"></script> <script src="/js/verif-form.js"></script>
<!-- Feuille de script pour les donnée static JSON de saisie formulaire --> <!-- Feuille de script pour les donnée static JSON de saisie formulaire -->
<script src="/js/json/json-verif-form.js"></script> <script src="/js/json/json-verif-form.js"></script>
<!-- Utilitaires de validation et blocage RC -->
<script src="/js/rc-validation-utils.js"></script>
<!-- Feuille de script pour le bon fonctionnement du loader --> <!-- Feuille de script pour le bon fonctionnement du loader -->
<script src="/js/loader.js"></script> <script src="/js/loader.js"></script>
<!-- Script pour la navigation AJAX --> <!-- Script pour la navigation AJAX -->

View File

@ -1007,7 +1007,7 @@
<!-- ------------------------------------------- --> <!-- ------------------------------------------- -->
<!-- Nouvelle section : Génération et sauvegarde --> <!-- Nouvelle section : Génération et sauvegarde -->
<!-- ------------------------------------------- --> <!-- ------------------------------------------- -->
<div class="row"> <div id="projetActionsRow" class="row">
<div class="header"> <div class="header">
<div class="chapter"> <div class="chapter">
<i class="material-icons">save</i><span style="margin-left: 10px;">Enregistrer et poursuivre le parcours :</span> <i class="material-icons">save</i><span style="margin-left: 10px;">Enregistrer et poursuivre le parcours :</span>

View File

@ -128,8 +128,8 @@
<span><strong style="color: darkblue;">Voiturier / Loueur</strong></span> <span><strong style="color: darkblue;">Voiturier / Loueur</strong></span>
<hr> <hr>
<div class="row"> <div class="row">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="capitalActVoiturierLoueur">Capital à assurer</label>
<input type="text" name="selectActVoiturier/Loueur" placeholder="Ex: 5000, 10000, 25000" style="text-align: center; font-weight: bold;"> <input type="text" id="capitalActVoiturierLoueur" name="selectActVoiturier/Loueur" placeholder="Ex: 5000, 10000, 25000" style="text-align: center; font-weight: bold;">
</div> </div>
<div class="row" style="display: flex;align-items: center; justify-content: space-around;"> <div class="row" style="display: flex;align-items: center; justify-content: space-around;">
<input id="pourcentVoiturier/Loueur" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" /> <input id="pourcentVoiturier/Loueur" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" />
@ -156,8 +156,8 @@
<span><strong style="color: darkblue;">Commissionnaire de Transport</strong></span> <span><strong style="color: darkblue;">Commissionnaire de Transport</strong></span>
<hr> <hr>
<div class="row"> <div class="row">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="capitalActCommissionnaire">Capital à assurer</label>
<input type="text" name="selectActCommissionnaire de Transport" placeholder="Ex: 100000, 150000, 200000" style="text-align: center; font-weight: bold;"> <input type="text" id="capitalActCommissionnaire" name="selectActCommissionnaire de Transport" placeholder="Ex: 100000, 150000, 200000" style="text-align: center; font-weight: bold;">
</div> </div>
<div class="row" style="display: flex;align-items: center; justify-content: space-around;"> <div class="row" style="display: flex;align-items: center; justify-content: space-around;">
<input id="pourcentCommissionnaire de Transport" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" /> <input id="pourcentCommissionnaire de Transport" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" />
@ -184,8 +184,8 @@
<span><strong style="color: darkblue;">Déménageur</strong></span> <span><strong style="color: darkblue;">Déménageur</strong></span>
<hr> <hr>
<div class="row"> <div class="row">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="capitalActDemenageur">Capital à assurer</label>
<input type="text" name="selectActDéménageur" placeholder="Ex: 5000, 10000, 25000" style="text-align: center; font-weight: bold;"> <input type="text" id="capitalActDemenageur" name="selectActDéménageur" placeholder="Ex: 5000, 10000, 25000" style="text-align: center; font-weight: bold;">
</div> </div>
<div class="row" style="display: flex;align-items: center; justify-content: space-around;"> <div class="row" style="display: flex;align-items: center; justify-content: space-around;">
<input id="pourcentDéménageur" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" /> <input id="pourcentDéménageur" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" />
@ -212,8 +212,8 @@
<span><strong style="color: darkblue;">Logistique</strong></span> <span><strong style="color: darkblue;">Logistique</strong></span>
<hr> <hr>
<div class="row"> <div class="row">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="capitalActLogistique">Capital à assurer</label>
<input type="text" name="selectActLogistique" placeholder="Ex: 5000, 10000, 25000" style="text-align: center; font-weight: bold;"> <input type="text" id="capitalActLogistique" name="selectActLogistique" placeholder="Ex: 5000, 10000, 25000" style="text-align: center; font-weight: bold;">
</div> </div>
<div class="row" style="display: flex;align-items: center; justify-content: space-around;"> <div class="row" style="display: flex;align-items: center; justify-content: space-around;">
<input id="pourcentLogistique" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" /> <input id="pourcentLogistique" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" />
@ -240,8 +240,8 @@
<span><strong style="color: darkblue;">Autocariste</strong></span> <span><strong style="color: darkblue;">Autocariste</strong></span>
<hr> <hr>
<div class="row"> <div class="row">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="capitalActAutocariste">Capital à assurer</label>
<input type="text" name="selectActAutocariste" placeholder="Ex: 25000, 50000, 75000" style="text-align: center; font-weight: bold;"> <input type="text" id="capitalActAutocariste" name="selectActAutocariste" placeholder="Ex: 25000, 50000, 75000" style="text-align: center; font-weight: bold;">
</div> </div>
<div class="row" style="display: flex;align-items: center; justify-content: space-around;"> <div class="row" style="display: flex;align-items: center; justify-content: space-around;">
<input id="pourcentAutocariste" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" /> <input id="pourcentAutocariste" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" />
@ -268,8 +268,8 @@
<span><strong style="color: darkblue;">Autres activites</strong></span> <span><strong style="color: darkblue;">Autres activites</strong></span>
<hr> <hr>
<div class="row"> <div class="row">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="capitalActAutres">Capital à assurer</label>
<input type="text" name="selectActAutres activites" placeholder="Ex: 5000, 10000, 25000" style="text-align: center; font-weight: bold;"> <input type="text" id="capitalActAutres" name="selectActAutres activites" placeholder="Ex: 5000, 10000, 25000" style="text-align: center; font-weight: bold;">
</div> </div>
<div class="row" style="display: flex;align-items: center; justify-content: space-around;"> <div class="row" style="display: flex;align-items: center; justify-content: space-around;">
<input id="pourcentAutres activites" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" /> <input id="pourcentAutres activites" class="input-pourcent" type="text" placeholder="Ex: 50" title="Appuyez sur Entrée pour valider" style="width: 80px; text-align: center; font-weight: bold; border: 2px solid #3f51b5; border-radius: 4px; padding: 8px;" />
@ -819,7 +819,7 @@
<label><input id="checkDomImmat" type='checkbox' class='filled-in' /><span>Dommages immatériels</span></label> <label><input id="checkDomImmat" type='checkbox' class='filled-in' /><span>Dommages immatériels</span></label>
</div> </div>
<div id="selectDomImmat" style="display: none; margin-top: 20px;"> <div id="selectDomImmat" style="display: none; margin-top: 20px;">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="inputDomImmat">Capital à assurer</label>
<input type="text" id="inputDomImmat" placeholder="Ex: 15000, 50000, 100000" style="font-weight: bold;"> <input type="text" id="inputDomImmat" placeholder="Ex: 15000, 50000, 100000" style="font-weight: bold;">
</div> </div>
</div> </div>
@ -834,7 +834,7 @@
<label><input id="checkContConf" type='checkbox' class='filled-in' /><span>Contenants confiés</span></label> <label><input id="checkContConf" type='checkbox' class='filled-in' /><span>Contenants confiés</span></label>
</div> </div>
<div id="selectContConf" style="display: none; margin-top: 20px;"> <div id="selectContConf" style="display: none; margin-top: 20px;">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="inputContConf">Capital à assurer</label>
<input type="text" id="inputContConf" placeholder="Ex: 25000, 50000, 100000" style="font-weight: bold;"> <input type="text" id="inputContConf" placeholder="Ex: 25000, 50000, 100000" style="font-weight: bold;">
</div> </div>
</div> </div>
@ -849,7 +849,7 @@
<label><input id="checkDiffInv" type='checkbox' class='filled-in' /><span>Différence inventaire</span></label> <label><input id="checkDiffInv" type='checkbox' class='filled-in' /><span>Différence inventaire</span></label>
</div> </div>
<div id="selectDiffInv" style="display: none; margin-top: 20px;"> <div id="selectDiffInv" style="display: none; margin-top: 20px;">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="inputDiffInv">Capital à assurer</label>
<input type="text" id="inputDiffInv" placeholder="Ex: 15000, 30000, 50000" style="font-weight: bold;"> <input type="text" id="inputDiffInv" placeholder="Ex: 15000, 30000, 50000" style="font-weight: bold;">
</div> </div>
</div> </div>
@ -883,7 +883,7 @@
<label><input id="checkTPPC" type='checkbox' class='filled-in' /><span>TPPC</span></label> <label><input id="checkTPPC" type='checkbox' class='filled-in' /><span>TPPC</span></label>
</div> </div>
<div id="selectTPPC" style="display: none; margin-top: 20px;"> <div id="selectTPPC" style="display: none; margin-top: 20px;">
<label class="rc-field-label">Capital à assurer</label> <label class="rc-field-label" for="selTPPCcapital">Capital à assurer</label>
<input type="text" id="selTPPCcapital" placeholder="Ex: 5000, 10000, 15000" style="font-weight: bold; margin-bottom: 10px;"> <input type="text" id="selTPPCcapital" placeholder="Ex: 5000, 10000, 15000" style="font-weight: bold; margin-bottom: 10px;">
<label class="rc-field-label" for="selTPPCveh">Nombre de véhicules</label> <label class="rc-field-label" for="selTPPCveh">Nombre de véhicules</label>
<input type="number" id="selTPPCveh" placeholder="Ex: 1 ou 2" min="1" max="10" style="font-weight: bold;"> <input type="number" id="selTPPCveh" placeholder="Ex: 1 ou 2" min="1" max="10" style="font-weight: bold;">
@ -973,7 +973,7 @@
</div> </div>
<div id="rcTarifBlockingSummary" class="rc-blocking-summary"></div> <div id="rcTarifBlockingSummary" class="rc-blocking-summary"></div>
<div class="col s12" style="display: flex;"> <div id="tarifettesContainer" class="col s12" style="display: flex;">
<div class="col s4"> <div class="col s4">
<div class="franchise-card card border"> <div class="franchise-card card border">
<div class="card-tarif-head" style="background-color:#b71c1c"> <div class="card-tarif-head" style="background-color:#b71c1c">