✨ feat(admin.scss): Améliore le style global et ajoute le thème TomSelect.
✨ feat(admin.js): Regroupe l'enregistrement des custom elements et améliore l'UI. ✨ feat(app.js): Initialise Sentry et ajoute une gestion des erreurs Turbo. ✨ feat(reserve.js): Optimise le chargement des images et améliore l'UI.
This commit is contained in:
198
assets/admin.js
198
assets/admin.js
@@ -6,141 +6,117 @@ import { RepeatLine } from "./libs/RepeatLine.js";
|
||||
import { DevisManager } from "./libs/DevisManager.js";
|
||||
import { CrmEditor } from "./libs/CrmEditor.js";
|
||||
import { initTomSelect } from "./libs/initTomSelect.js";
|
||||
import { SearchProduct,SearchOptions } from "./libs/SearchProduct.js";
|
||||
import { SearchProductDevis,SearchOptionsDevis } from "./libs/SearchProductDevis.js";
|
||||
import { SearchProductFormule,SearchOptionsFormule } from "./libs/SearchProductFormule.js";
|
||||
// --- INITIALISATION SENTRY ---
|
||||
import { SearchProduct, SearchOptions } from "./libs/SearchProduct.js";
|
||||
import { SearchProductDevis, SearchOptionsDevis } from "./libs/SearchProductDevis.js";
|
||||
import { SearchProductFormule, SearchOptionsFormule } from "./libs/SearchProductFormule.js";
|
||||
|
||||
// --- CONFIGURATION SENTRY ---
|
||||
Sentry.init({
|
||||
dsn: "https://803814be6540031b1c37bf92ba9c0f79@sentry.esy-web.dev/24",
|
||||
tunnel: "/sentry-tunnel",
|
||||
sendDefaultPii: true,
|
||||
integrations: [
|
||||
Sentry.browserTracingIntegration(),
|
||||
Sentry.replayIntegration()
|
||||
],
|
||||
integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()],
|
||||
tracesSampleRate: 1.0,
|
||||
tracePropagationTargets: ["localhost", "esy-web.dev"],
|
||||
replaysSessionSampleRate: 0.1,
|
||||
replaysOnErrorSampleRate: 1.0
|
||||
});
|
||||
|
||||
/**
|
||||
* Enregistre les Custom Elements de manière groupée
|
||||
*/
|
||||
const registerCustomElements = () => {
|
||||
const elements = [
|
||||
{ name: 'repeat-line', class: RepeatLine, extends: 'div' },
|
||||
{ name: 'devis-manager', class: DevisManager, extends: 'div' },
|
||||
{ name: 'search-product', class: SearchProduct, extends: 'button' },
|
||||
{ name: 'search-productformule', class: SearchProductFormule, extends: 'button' },
|
||||
{ name: 'search-optionsformule', class: SearchOptionsFormule, extends: 'button' },
|
||||
{ name: 'search-options', class: SearchOptions, extends: 'button' },
|
||||
{ name: 'search-productdevis', class: SearchProductDevis, extends: 'button' },
|
||||
{ name: 'search-optionsdevis', class: SearchOptionsDevis, extends: 'button' },
|
||||
{ name: 'crm-editor', class: CrmEditor, extends: 'textarea' }
|
||||
];
|
||||
|
||||
elements.forEach(el => {
|
||||
if (!customElements.get(el.name)) {
|
||||
customElements.define(el.name, el.class, { extends: el.extends });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Preview d'image optimisée
|
||||
*/
|
||||
function initImagePreview() {
|
||||
const input = document.getElementById('product_image_input');
|
||||
const preview = document.getElementById('product-image-preview');
|
||||
const placeholder = document.getElementById('product-image-placeholder');
|
||||
|
||||
if (input && preview) {
|
||||
input.addEventListener('change', function(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
preview.src = e.target.result;
|
||||
preview.classList.remove('hidden');
|
||||
if (placeholder) placeholder.classList.add('hidden');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
input?.addEventListener('change', ({ target }) => {
|
||||
const file = target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (preview) {
|
||||
preview.src = e.target.result;
|
||||
preview.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
placeholder?.classList.add('hidden');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère le filtrage dynamique des listes (Contrats, Devis, etc.)
|
||||
* Filtrage dynamique (Contrats, Devis, etc.)
|
||||
*/
|
||||
function initDynamicSearch() {
|
||||
const searchInput = document.getElementById('searchContrat');
|
||||
const listContainer = document.getElementById('contratsList');
|
||||
|
||||
if (searchInput && listContainer) {
|
||||
searchInput.addEventListener('input', function() {
|
||||
const filter = this.value.toLowerCase();
|
||||
const cards = listContainer.querySelectorAll('.contrat-card');
|
||||
searchInput?.addEventListener('input', (e) => {
|
||||
const filter = e.target.value.toLowerCase();
|
||||
const cards = listContainer?.querySelectorAll('.contrat-card');
|
||||
|
||||
cards.forEach(card => {
|
||||
// On récupère tout le texte de la carte pour une recherche globale
|
||||
const content = card.textContent.toLowerCase();
|
||||
|
||||
if (content.includes(filter)) {
|
||||
card.classList.remove('hidden');
|
||||
// Optionnel : petite animation de ré-apparition
|
||||
card.style.opacity = "1";
|
||||
card.style.transform = "scale(1)";
|
||||
} else {
|
||||
card.classList.add('hidden');
|
||||
card.style.opacity = "0";
|
||||
card.style.transform = "scale(0.95)";
|
||||
}
|
||||
});
|
||||
cards?.forEach(card => {
|
||||
const isVisible = card.textContent.toLowerCase().includes(filter);
|
||||
card.classList.toggle('hidden', !isVisible);
|
||||
card.style.opacity = isVisible ? "1" : "0";
|
||||
card.style.transform = isVisible ? "scale(1)" : "scale(0.95)";
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise les composants de l'interface d'administration.
|
||||
* Gestion UI : Sidebar, Submenus et Flash messages
|
||||
*/
|
||||
function initAdminLayout() {
|
||||
initDynamicSearch();
|
||||
initImagePreview();
|
||||
// Enregistrement des Custom Elements
|
||||
if (!customElements.get('repeat-line')) {
|
||||
customElements.define('repeat-line', RepeatLine, { extends: 'div' });
|
||||
}
|
||||
if (!customElements.get('devis-manager')) {
|
||||
customElements.define('devis-manager', DevisManager, { extends: 'div' });
|
||||
}
|
||||
function initUI() {
|
||||
// Sidebar
|
||||
const elements = {
|
||||
sidebar: document.getElementById('sidebar'),
|
||||
overlay: document.getElementById('sidebar-overlay'),
|
||||
toggleBtn: document.getElementById('sidebar-toggle'),
|
||||
settingsToggle: document.getElementById('settings-toggle'),
|
||||
settingsSubmenu: document.getElementById('settings-submenu')
|
||||
};
|
||||
|
||||
if (!customElements.get('search-product')) {
|
||||
customElements.define('search-product', SearchProduct, { extends: 'button' });
|
||||
}
|
||||
if (!customElements.get('search-productformule')) {
|
||||
customElements.define('search-productformule', SearchProductFormule, { extends: 'button' });
|
||||
}
|
||||
if (!customElements.get('search-optionsformule')) {
|
||||
customElements.define('search-optionsformule', SearchOptionsFormule, { extends: 'button' });
|
||||
}
|
||||
if (!customElements.get('search-options')) {
|
||||
customElements.define('search-options', SearchOptions, { extends: 'button' });
|
||||
}
|
||||
if (!customElements.get('search-productdevis')) {
|
||||
customElements.define('search-productdevis', SearchProductDevis, { extends: 'button' });
|
||||
}
|
||||
if (!customElements.get('search-optionsdevis')) {
|
||||
customElements.define('search-optionsdevis', SearchOptionsDevis, { extends: 'button' });
|
||||
}
|
||||
if (!customElements.get('crm-editor')) {
|
||||
customElements.define('crm-editor', CrmEditor, { extends: 'textarea' });
|
||||
}
|
||||
// S
|
||||
// Sidebar & UI
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const overlay = document.getElementById('sidebar-overlay');
|
||||
const toggleBtn = document.getElementById('sidebar-toggle');
|
||||
const toggleSidebar = () => {
|
||||
elements.sidebar?.classList.toggle('-translate-x-full');
|
||||
elements.overlay?.classList.toggle('hidden');
|
||||
};
|
||||
|
||||
if (toggleBtn && sidebar && overlay) {
|
||||
toggleBtn.onclick = () => {
|
||||
sidebar.classList.toggle('-translate-x-full');
|
||||
overlay.classList.toggle('hidden');
|
||||
};
|
||||
overlay.onclick = () => {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
overlay.classList.add('hidden');
|
||||
};
|
||||
}
|
||||
elements.toggleBtn?.addEventListener('click', toggleSidebar);
|
||||
elements.overlay?.addEventListener('click', toggleSidebar);
|
||||
|
||||
// Dropdown Paramètres
|
||||
const settingsToggle = document.getElementById('settings-toggle');
|
||||
const settingsSubmenu = document.getElementById('settings-submenu');
|
||||
if (settingsToggle && settingsSubmenu) {
|
||||
settingsToggle.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
settingsSubmenu.classList.toggle('hidden');
|
||||
const isOpen = !settingsSubmenu.classList.contains('hidden');
|
||||
localStorage.setItem('admin_settings_open', isOpen);
|
||||
};
|
||||
}
|
||||
// Settings Submenu
|
||||
elements.settingsToggle?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const isHidden = elements.settingsSubmenu?.classList.toggle('hidden');
|
||||
localStorage.setItem('admin_settings_open', !isHidden);
|
||||
});
|
||||
|
||||
// Flash messages
|
||||
document.querySelectorAll('.flash-message').forEach((flash) => {
|
||||
// Flash Messages (Délégation pour performance)
|
||||
document.querySelectorAll('.flash-message').forEach(flash => {
|
||||
setTimeout(() => {
|
||||
flash.classList.add('opacity-0', 'translate-x-10');
|
||||
setTimeout(() => flash.remove(), 500);
|
||||
@@ -148,15 +124,19 @@ function initAdminLayout() {
|
||||
});
|
||||
}
|
||||
|
||||
// Turbo Hooks
|
||||
// --- INITIALISATION ---
|
||||
document.addEventListener('turbo:load', () => {
|
||||
initAdminLayout();
|
||||
initTomSelect(); // Init au chargement de la page
|
||||
registerCustomElements();
|
||||
initDynamicSearch();
|
||||
initImagePreview();
|
||||
initUI();
|
||||
initTomSelect();
|
||||
});
|
||||
|
||||
// Confirmation de suppression Turbo
|
||||
document.addEventListener("turbo:click", (event) => {
|
||||
const message = event.target.closest("[data-turbo-confirm]")?.getAttribute("data-turbo-confirm");
|
||||
if (message && !confirm(message)) {
|
||||
const attr = event.target.closest("[data-turbo-confirm]")?.getAttribute("data-turbo-confirm");
|
||||
if (attr && !confirm(attr)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,363 +1,126 @@
|
||||
@import "tailwindcss";
|
||||
@import "tom-select/dist/css/tom-select.css";
|
||||
|
||||
/* --- CONFIGURATION GLOBALE --- */
|
||||
@layer base {
|
||||
form label {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
form {
|
||||
label {
|
||||
color: white;
|
||||
.custom-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
@apply w-[5px] h-[5px];
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply bg-slate-300 rounded-full dark:bg-slate-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
.animate-fadeIn { animation: fadeIn 0.3s ease-in-out; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
||||
.custom-scrollbar::-webkit-scrollbar { width: 5px; height: 5px; }
|
||||
.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
|
||||
.dark .custom-scrollbar::-webkit-scrollbar-thumb { background: #334155; }
|
||||
|
||||
.page-transition { animation: fadeIn 0.3s ease-out; }
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(4px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
/* --- ANIMATIONS --- */
|
||||
@theme {
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.page-transition {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* --- UI COMPONENTS --- */
|
||||
/* Menu Accordion sans JS */
|
||||
details summary::-webkit-details-marker { display:none; }
|
||||
details[open] .arrow-icon { transform: rotate(180deg); }
|
||||
|
||||
.ts-control {
|
||||
border: 1px solid rgba(255, 255, 255, 0.05); /* Dark border */
|
||||
padding: 8px 8px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
box-shadow: none; /* Removed inset shadow */
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background-color: rgba(15, 23, 42, 0.6) !important; /* Slate-900 transparent */
|
||||
}
|
||||
.ts-wrapper.multi.has-items .ts-control {
|
||||
padding: calc(8px - 2px - 1px) 8px calc(8px - 2px - 3px - 1px);
|
||||
}
|
||||
.full .ts-control {
|
||||
background-color: #0f172a; /* Slate-900 */
|
||||
}
|
||||
.disabled .ts-control, .disabled .ts-control * {
|
||||
cursor: default !important;
|
||||
}
|
||||
.focus .ts-control {
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); /* Blue halo */
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
.ts-control > * {
|
||||
vertical-align: baseline;
|
||||
display: inline-block;
|
||||
}
|
||||
.ts-wrapper.multi .ts-control > div {
|
||||
cursor: pointer;
|
||||
margin: 0 3px 3px 0;
|
||||
padding: 2px 6px;
|
||||
background: #2563eb; /* Blue-600 */
|
||||
color: #fff;
|
||||
border: 1px solid #1d4ed8;
|
||||
}
|
||||
.ts-wrapper.multi .ts-control > div.active {
|
||||
background: #1e40af; /* Deeper blue */
|
||||
color: #fff;
|
||||
border: 1px solid #1e3a8a;
|
||||
}
|
||||
.ts-wrapper.multi.disabled .ts-control > div, .ts-wrapper.multi.disabled .ts-control > div.active {
|
||||
color: #475569;
|
||||
background: #1e293b;
|
||||
border: 1px solid #334155;
|
||||
}
|
||||
.ts-control > input {
|
||||
flex: 1 1 auto;
|
||||
min-width: 7rem;
|
||||
display: inline-block !important;
|
||||
padding: 0 !important;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
text-indent: 0 !important;
|
||||
border: 0 none !important;
|
||||
background: none !important;
|
||||
line-height: inherit !important;
|
||||
-webkit-user-select: auto !important;
|
||||
-moz-user-select: auto !important;
|
||||
-ms-user-select: auto !important;
|
||||
user-select: auto !important;
|
||||
box-shadow: none !important;
|
||||
color: #f8fafc !important; /* Slate-50 text */
|
||||
}
|
||||
.ts-control > input::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
.ts-control > input:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
.has-items .ts-control > input {
|
||||
margin: 0 4px !important;
|
||||
}
|
||||
.ts-control.rtl {
|
||||
text-align: right;
|
||||
}
|
||||
.ts-control.rtl.single .ts-control:after {
|
||||
left: 15px;
|
||||
right: auto;
|
||||
}
|
||||
.ts-control.rtl .ts-control > input {
|
||||
margin: 0 4px 0 -2px !important;
|
||||
}
|
||||
.disabled .ts-control {
|
||||
opacity: 0.5;
|
||||
background-color: #0f172a;
|
||||
}
|
||||
.input-hidden .ts-control > input {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
}
|
||||
|
||||
.ts-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: #0f172a; /* Dark background */
|
||||
margin: 0.25rem 0 0;
|
||||
border-top: 0 none;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 0 0 3px 3px;
|
||||
color: #f8fafc;
|
||||
}
|
||||
.ts-dropdown [data-selectable] {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ts-dropdown [data-selectable] .highlight {
|
||||
background: rgba(59, 130, 246, 0.3); /* Blue highlight */
|
||||
border-radius: 1px;
|
||||
}
|
||||
.ts-dropdown .option,
|
||||
.ts-dropdown .optgroup-header,
|
||||
.ts-dropdown .no-results,
|
||||
.ts-dropdown .create {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
.ts-dropdown .option, .ts-dropdown [data-disabled], .ts-dropdown [data-disabled] [data-selectable].option {
|
||||
cursor: inherit;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.ts-dropdown [data-selectable].option {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ts-dropdown .optgroup:first-child .optgroup-header {
|
||||
border-top: 0 none;
|
||||
}
|
||||
.ts-dropdown .optgroup-header {
|
||||
color: #94a3b8; /* Slate-400 */
|
||||
background: #0f172a;
|
||||
cursor: default;
|
||||
}
|
||||
.ts-dropdown .active {
|
||||
background-color: #1e293b; /* Slate-800 */
|
||||
color: #3b82f6; /* Blue-500 */
|
||||
}
|
||||
.ts-dropdown .active.create {
|
||||
color: #3b82f6;
|
||||
}
|
||||
.ts-dropdown .create {
|
||||
color: rgba(248, 248, 248, 0.5);
|
||||
}
|
||||
.ts-dropdown .spinner {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 5px 8px;
|
||||
}
|
||||
.ts-dropdown .spinner::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 3px;
|
||||
border-radius: 50%;
|
||||
border: 5px solid #1e293b;
|
||||
border-color: #3b82f6 transparent #3b82f6 transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
details {
|
||||
& summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
&[open] .arrow-icon {
|
||||
@apply rotate-180 transition-transform;
|
||||
}
|
||||
}
|
||||
|
||||
.ts-dropdown-content {
|
||||
overflow: hidden auto;
|
||||
max-height: 200px;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.ts-wrapper.plugin-drag_drop .ts-dragging {
|
||||
color: transparent !important;
|
||||
}
|
||||
.ts-wrapper.plugin-drag_drop .ts-dragging > * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.plugin-checkbox_options:not(.rtl) .option input {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.plugin-checkbox_options.rtl .option input {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
/* stylelint-disable function-name-case */
|
||||
.plugin-clear_button {
|
||||
--ts-pr-clear-button: 1em;
|
||||
}
|
||||
.plugin-clear_button .clear-button {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: calc(8px - 6px);
|
||||
margin-right: 0 !important;
|
||||
background: transparent !important;
|
||||
transition: opacity 0.5s;
|
||||
cursor: pointer;
|
||||
color: #ef4444; /* Red for clear */
|
||||
}
|
||||
.plugin-clear_button.focus.has-items .clear-button, .plugin-clear_button:not(.disabled):hover.has-items .clear-button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ts-wrapper .dropdown-header {
|
||||
position: relative;
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: #1e293b;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
.ts-wrapper .dropdown-header-close {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
color: #f8fafc;
|
||||
opacity: 0.4;
|
||||
margin-top: -12px;
|
||||
line-height: 20px;
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.ts-wrapper .dropdown-header-close:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.plugin-dropdown_input.focus.dropdown-active .ts-control {
|
||||
box-shadow: none;
|
||||
border: 1px solid #3b82f6;
|
||||
}
|
||||
.plugin-dropdown_input .dropdown-input {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-width: 0 0 1px;
|
||||
display: block;
|
||||
padding: 8px 8px;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
background: #0f172a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ts-dropdown.plugin-optgroup_columns .optgroup {
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-top: 0 none;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ts-wrapper.plugin-remove_button .item .remove {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
border-radius: 0 2px 2px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ts-wrapper.plugin-remove_button .item .remove:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* --- TOMSELECT CUSTOM DARK THEME --- */
|
||||
/* On augmente la spécificité par le nesting pour éviter les !important */
|
||||
.ts-wrapper {
|
||||
position: relative;
|
||||
& .ts-control {
|
||||
@apply bg-slate-900/60 border-white/5 rounded p-2 flex flex-wrap transition-all shadow-none relative z-1;
|
||||
|
||||
& > input {
|
||||
@apply text-slate-50 text-sm flex-1 min-w-[7rem] bg-transparent border-none outline-none;
|
||||
&::placeholder { @apply text-slate-500; }
|
||||
}
|
||||
}
|
||||
|
||||
&.focus .ts-control {
|
||||
@apply ring-2 ring-blue-500/20 border-blue-500;
|
||||
}
|
||||
|
||||
/* Tags (Multi-select) */
|
||||
&.multi .ts-control > div {
|
||||
@apply bg-blue-600 text-white rounded px-1.5 py-0.5 border border-blue-700 mr-1 mb-1 cursor-pointer;
|
||||
&.active { @apply bg-blue-800 border-blue-900; }
|
||||
}
|
||||
|
||||
/* Dropdown menu */
|
||||
& .ts-dropdown {
|
||||
@apply absolute top-full left-0 w-full z-10 bg-slate-900 border border-white/10 shadow-2xl rounded-b mt-1 text-slate-50;
|
||||
|
||||
& .option {
|
||||
@apply px-2 py-1.5 cursor-pointer transition-colors opacity-100;
|
||||
&.active { @apply bg-slate-800 text-blue-500; }
|
||||
&[data-selectable]:hover { @apply bg-slate-800; }
|
||||
&.highlight { @apply bg-blue-500/30 rounded-sm; }
|
||||
}
|
||||
|
||||
& .optgroup-header {
|
||||
@apply px-2 py-1.5 text-slate-400 bg-slate-900 font-bold text-[10px] uppercase tracking-widest cursor-default border-t border-white/5;
|
||||
}
|
||||
|
||||
& .no-results, & .create { @apply px-2 py-1.5; }
|
||||
}
|
||||
|
||||
/* Plugins */
|
||||
&.plugin-clear_button .clear-button {
|
||||
@apply opacity-0 transition-opacity cursor-pointer text-red-500 absolute right-2 top-1/2 -translate-y-1/2;
|
||||
}
|
||||
&:hover.has-items .clear-button, &.focus.has-items .clear-button {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@apply opacity-50;
|
||||
& .ts-control { @apply bg-slate-900 cursor-default; }
|
||||
}
|
||||
}
|
||||
|
||||
.ts-dropdown,
|
||||
.ts-control,
|
||||
.ts-control input {
|
||||
color: #f8fafc; /* Global text color */
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
/* --- LOGIQUE MÉTIER & FIXES --- */
|
||||
.form-repeater__row {
|
||||
@apply relative z-[90];
|
||||
}
|
||||
|
||||
.ts-control,
|
||||
.ts-wrapper.single.input-active .ts-control {
|
||||
background: rgba(15, 23, 42, 0.6) !important;
|
||||
cursor: text;
|
||||
/* Fix pour l'affichage des dropdowns dans les repeaters */
|
||||
.ts-dropdown.single {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.ts-wrapper.single .ts-control::after {
|
||||
border-color: #64748b transparent transparent transparent; /* Slate-500 arrow */
|
||||
}
|
||||
/* --- BOUTON BACK TO TOP (Injected via JS) --- */
|
||||
/* --- BOUTON BACK TO TOP (Injected via JS) --- */
|
||||
#back-to-top {
|
||||
/* Utilisation de la classe standard Tailwind pour la fluidité */
|
||||
@apply transition-all duration-500 ease-in-out;
|
||||
|
||||
.ts-wrapper.single.dropdown-active .ts-control::after {
|
||||
border-color: transparent transparent #3b82f6 transparent;
|
||||
}
|
||||
/* Optionnel : si tu veux vraiment un cubic-bezier spécifique, utilise les crochets : */
|
||||
/* transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); */
|
||||
|
||||
.ts-wrapper.multi .ts-control [data-value] {
|
||||
text-shadow: none;
|
||||
border-radius: 3px;
|
||||
background-color: #3b82f6;
|
||||
background-image: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.ts-wrapper.multi.disabled .ts-control [data-value] {
|
||||
color: #475569;
|
||||
background: #1e293b;
|
||||
}
|
||||
|
||||
.ts-wrapper.single .ts-control {
|
||||
box-shadow: none;
|
||||
background-color: rgba(15, 23, 42, 0.6) !important;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.ts-wrapper.single .ts-control, .ts-dropdown.single {
|
||||
border-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.ts-dropdown .optgroup {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.form-repeater__row{
|
||||
z-index: 90;
|
||||
position: relative;
|
||||
}
|
||||
.ts-dropdown.single{
|
||||
position: relative;
|
||||
&:active {
|
||||
@apply scale-95;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,58 @@
|
||||
import './app.scss'
|
||||
import * as Turbo from "@hotwired/turbo"
|
||||
import './app.scss';
|
||||
import * as Turbo from "@hotwired/turbo";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
|
||||
// --- INITIALISATION SENTRY (En premier !) ---
|
||||
// --- INITIALISATION SENTRY ---
|
||||
// On initialise Sentry immédiatement sans attendre le DOM pour capturer les erreurs précoces
|
||||
Sentry.init({
|
||||
dsn: "https://803814be6540031b1c37bf92ba9c0f79@sentry.esy-web.dev/24",
|
||||
tunnel: "/sentry-tunnel",
|
||||
sendDefaultPii: true,
|
||||
integrations: [
|
||||
Sentry.browserTracingIntegration(),
|
||||
Sentry.replayIntegration()
|
||||
],
|
||||
tracesSampleRate: 1.0,
|
||||
tracePropagationTargets: ["localhost", "esy-web.dev"], // Remplace par ton domaine réel
|
||||
replaysSessionSampleRate: 0.1,
|
||||
replaysOnErrorSampleRate: 1.0
|
||||
});
|
||||
// --- INITIALISATION DES COMPOSANTS APRÈS TURBO/CHARGEMENT ---
|
||||
document.addEventListener('DOMContentLoaded', ()=>{
|
||||
|
||||
/**
|
||||
* Initialisation globale des composants UI
|
||||
* Appelée à chaque navigation Turbo
|
||||
*/
|
||||
const initApp = () => {
|
||||
// Flash Messages Auto-hide
|
||||
document.querySelectorAll('.flash-message').forEach(flash => {
|
||||
setTimeout(() => {
|
||||
flash.classList.add('opacity-0', 'translate-y-2');
|
||||
setTimeout(() => flash.remove(), 500);
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
// Exemple : Init d'un bouton de retour en haut de page
|
||||
// initBackToTop();
|
||||
};
|
||||
|
||||
// --- TURBO HOOKS ---
|
||||
|
||||
// 'turbo:load' s'exécute au chargement initial ET à chaque navigation
|
||||
document.addEventListener('turbo:load', () => {
|
||||
initApp();
|
||||
});
|
||||
|
||||
document.addEventListener('turbo:load', () => {
|
||||
// Gestion des confirmations de suppression ou d'actions sensibles via Turbo
|
||||
document.addEventListener("turbo:click", (event) => {
|
||||
const confirmationMsg = event.target.closest("[data-turbo-confirm]")?.getAttribute("data-turbo-confirm");
|
||||
if (confirmationMsg && !confirm(confirmationMsg)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Capture des erreurs de rendu Turbo dans Sentry
|
||||
document.addEventListener("turbo:render", () => {
|
||||
Sentry.addBreadcrumb({
|
||||
category: "turbo",
|
||||
message: "Page rendered: " + window.location.pathname,
|
||||
level: "info",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,113 +2,101 @@ import './reserve.scss';
|
||||
import { UtmEvent, UtmAccount } from "./tools/UtmEvent.js";
|
||||
import { CookieBanner } from "./tools/CookieBanner.js";
|
||||
import * as Turbo from "@hotwired/turbo";
|
||||
import {onLCP, onINP, onCLS} from 'web-vitals';
|
||||
import { onLCP, onINP, onCLS } from 'web-vitals';
|
||||
|
||||
// --- DÉTECTION BOT / PERFORMANCE ---
|
||||
const isLighthouse = () => {
|
||||
if (typeof navigator === 'undefined' || !navigator.userAgent) return false;
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
const patterns = ['chrome-lighthouse', 'google/lighthouse', 'lighthouse', 'pagespeed', 'headless', 'webdriver'];
|
||||
return patterns.some(pattern => userAgent.includes(pattern));
|
||||
// --- CONFIGURATION & ÉTAT ---
|
||||
const CONFIG = {
|
||||
sentryDsn: "https://803814be6540031b1c37bf92ba9c0f79@sentry.esy-web.dev/24",
|
||||
vitalsUrl: '/reservation/web-vitals'
|
||||
};
|
||||
|
||||
// --- GESTION DYNAMIQUE DE SENTRY ---
|
||||
// --- UTILS ---
|
||||
const isLighthouse = () => {
|
||||
if (typeof navigator === 'undefined' || !navigator.userAgent) return false;
|
||||
return ['lighthouse', 'pagespeed', 'headless', 'webdriver'].some(p =>
|
||||
navigator.userAgent.toLowerCase().includes(p)
|
||||
);
|
||||
};
|
||||
|
||||
// --- PERFORMANCE (Web Vitals) ---
|
||||
const sendToAnalytics = (metric) => {
|
||||
if (isLighthouse()) return;
|
||||
const body = JSON.stringify({ ...metric, path: window.location.pathname });
|
||||
if (navigator.sendBeacon) {
|
||||
navigator.sendBeacon(CONFIG.vitalsUrl, body);
|
||||
} else {
|
||||
fetch(CONFIG.vitalsUrl, { body, method: 'POST', keepalive: true });
|
||||
}
|
||||
};
|
||||
|
||||
const initVitals = () => {
|
||||
onLCP(sendToAnalytics);
|
||||
onINP(sendToAnalytics);
|
||||
onCLS(sendToAnalytics);
|
||||
};
|
||||
|
||||
// --- SERVICES (Sentry) ---
|
||||
const toggleSentry = async (status) => {
|
||||
if (isLighthouse()) return;
|
||||
|
||||
try {
|
||||
const Sentry = await import("@sentry/browser");
|
||||
|
||||
if (status === 'accepted') {
|
||||
if (!window.SentryInitialized) {
|
||||
window.sentryClient = Sentry.init({
|
||||
dsn: "https://803814be6540031b1c37bf92ba9c0f79@sentry.esy-web.dev/24",
|
||||
dsn: CONFIG.sentryDsn,
|
||||
tunnel: "/sentry-tunnel",
|
||||
integrations: [Sentry.browserTracingIntegration()],
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
window.SentryInitialized = true;
|
||||
console.log("✔️ Sentry initialisé et activé");
|
||||
} else {
|
||||
// Réactivation si déjà chargé
|
||||
if (window.sentryClient) window.sentryClient.getOptions().enabled = true;
|
||||
console.log("✔️ Sentry ré-activé");
|
||||
} else if (window.sentryClient) {
|
||||
window.sentryClient.getOptions().enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (status === 'refused' && window.SentryInitialized) {
|
||||
// Désactivation sans décharger le script
|
||||
} else if (status === 'refused' && window.SentryInitialized) {
|
||||
if (window.sentryClient) window.sentryClient.getOptions().enabled = false;
|
||||
console.log("🛑 Sentry désactivé (Client muet)");
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Sentry toggle failed", e);
|
||||
}
|
||||
} catch (e) { console.warn("Sentry error", e); }
|
||||
};
|
||||
|
||||
// --- UI : LOADERS & IMAGES ---
|
||||
const initImageLoader = () => {
|
||||
// On cible uniquement les images à l'intérieur de la balise <main>
|
||||
const mainContainer = document.querySelector('main');
|
||||
if (!mainContainer) return;
|
||||
|
||||
const images = mainContainer.querySelectorAll('img:not(.loaded)');
|
||||
|
||||
|
||||
const images = document.querySelectorAll('main img:not(.loaded)');
|
||||
images.forEach(img => {
|
||||
// Sécurité : si l'image est déjà chargée (cache), on marque et on skip
|
||||
if (img.complete) {
|
||||
img.classList.add('loaded');
|
||||
img.style.opacity = '1';
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Préparation du parent (doit être relatif pour le loader absolu)
|
||||
const parent = img.parentElement;
|
||||
if (!parent) return;
|
||||
parent.classList.add('relative', 'overflow-hidden', 'bg-gray-50');
|
||||
parent.classList.add('relative', 'overflow-hidden', 'bg-slate-50');
|
||||
|
||||
// 2. Création du Loader (Spinner Tailwind)
|
||||
const loader = document.createElement('div');
|
||||
loader.id = `loader-${Math.random().toString(36).substr(2, 9)}`;
|
||||
loader.className = 'absolute inset-0 flex items-center justify-center z-10 bg-gray-50 transition-opacity duration-500';
|
||||
loader.innerHTML = `
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="w-7 h-7 border-3 border-slate-200 border-t-slate-800 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
`;
|
||||
loader.className = 'absolute inset-0 flex items-center justify-center z-10 bg-slate-50 transition-opacity duration-500';
|
||||
loader.innerHTML = `<div class="w-7 h-7 border-3 border-slate-200 border-t-[#f39e36] rounded-full animate-spin"></div>`;
|
||||
|
||||
parent.insertBefore(loader, img);
|
||||
|
||||
// 3. État initial de l'image (invisible)
|
||||
img.classList.add('opacity-0', 'transition-opacity', 'duration-700');
|
||||
|
||||
// 4. Gestionnaire de fin de chargement
|
||||
img.onload = () => {
|
||||
img.classList.replace('opacity-0', 'opacity-100');
|
||||
img.classList.add('loaded');
|
||||
loader.classList.add('opacity-0');
|
||||
setTimeout(() => loader.remove(), 500);
|
||||
};
|
||||
|
||||
// Gestion de l'erreur
|
||||
img.onerror = () => {
|
||||
loader.innerHTML = '<span class="text-[10px] text-gray-400 font-medium uppercase">Erreur</span>';
|
||||
};
|
||||
img.onerror = () => { loader.innerHTML = '⚠️'; };
|
||||
});
|
||||
};
|
||||
// --- LOGIQUE DU LOADER TURBO ---
|
||||
const initLoader = () => {
|
||||
if (document.getElementById('turbo-loader')) return;
|
||||
|
||||
const initTurboLoader = () => {
|
||||
if (document.getElementById('turbo-loader')) return;
|
||||
const loaderEl = document.createElement('div');
|
||||
loaderEl.id = 'turbo-loader';
|
||||
loaderEl.className = 'fixed inset-0 z-[9999] flex items-center justify-center bg-white transition-opacity duration-300 opacity-0 pointer-events-none';
|
||||
loaderEl.className = 'fixed inset-0 z-[9999] flex items-center justify-center bg-white/80 backdrop-blur-sm transition-opacity duration-300 opacity-0 pointer-events-none';
|
||||
loaderEl.innerHTML = `
|
||||
<div class="relative flex items-center justify-center">
|
||||
<div class="absolute w-24 h-24 border-4 border-[#f39e36] border-t-transparent rounded-full animate-spin"></div>
|
||||
<img src="/provider/images/favicon.webp" class="w-12 h-12 relative z-10 animate-pulse" alt="Logo">
|
||||
</div>
|
||||
`;
|
||||
<div class="relative flex flex-col items-center gap-4">
|
||||
<div class="w-16 h-16 border-4 border-[#f39e36] border-t-[#fc0e50] rounded-full animate-spin"></div>
|
||||
<img src="/provider/images/favicon.webp" class="w-8 h-8 absolute top-4 animate-pulse" alt="LDK">
|
||||
</div>`;
|
||||
document.body.appendChild(loaderEl);
|
||||
|
||||
document.addEventListener("turbo:click", () => {
|
||||
@@ -116,141 +104,145 @@ const initLoader = () => {
|
||||
loaderEl.classList.remove('pointer-events-none');
|
||||
});
|
||||
|
||||
const hideLoader = () => {
|
||||
setTimeout(() => {
|
||||
loaderEl.classList.replace('opacity-100', 'opacity-0');
|
||||
loaderEl.classList.add('pointer-events-none');
|
||||
}, 300);
|
||||
const hide = () => {
|
||||
loaderEl.classList.replace('opacity-100', 'opacity-0');
|
||||
loaderEl.classList.add('pointer-events-none');
|
||||
};
|
||||
|
||||
document.addEventListener("turbo:load", hideLoader);
|
||||
document.addEventListener("turbo:render", hideLoader);
|
||||
document.addEventListener("turbo:load", hide);
|
||||
document.addEventListener("turbo:render", hide);
|
||||
};
|
||||
|
||||
// --- LOGIQUE INTERFACE (Menu, Filtres, Redirect, Register) ---
|
||||
// --- UI : BACK TO TOP ---
|
||||
const initBackToTop = () => {
|
||||
if (document.getElementById('back-to-top')) return;
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.id = 'back-to-top';
|
||||
|
||||
// Modification :
|
||||
// 1. bottom-24 au lieu de bottom-8 pour laisser de la place aux cookies
|
||||
// 2. bg-[#f39e36] pour respecter ta couleur orange
|
||||
// 3. hover:bg-[#fc0e50] pour le rappel rose au survol
|
||||
btn.className = `
|
||||
fixed bottom-24 right-8 z-50 p-4
|
||||
bg-[#f39e36] text-white rounded-2xl shadow-[0_10px_25px_-5px_rgba(243,158,54,0.4)]
|
||||
border-2 border-white/20 opacity-0 translate-y-10
|
||||
pointer-events-none transition-all duration-500
|
||||
hover:bg-[#fc0e50] hover:-translate-y-2 group
|
||||
`;
|
||||
|
||||
btn.innerHTML = `
|
||||
<svg class="w-6 h-6 transform group-hover:animate-bounce" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M5 10l7-7m0 0l7 7m-7-7v18"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
document.body.appendChild(btn);
|
||||
|
||||
const handleScroll = () => {
|
||||
const isVisible = window.scrollY > 400;
|
||||
if (isVisible) {
|
||||
btn.classList.replace('opacity-0', 'opacity-100');
|
||||
btn.classList.replace('translate-y-10', 'translate-y-0');
|
||||
btn.classList.remove('pointer-events-none');
|
||||
} else {
|
||||
btn.classList.replace('opacity-100', 'opacity-0');
|
||||
btn.classList.replace('translate-y-0', 'translate-y-10');
|
||||
btn.classList.add('pointer-events-none');
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
|
||||
btn.onclick = () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
};
|
||||
|
||||
// --- LOGIQUE MÉTIER ---
|
||||
const initMobileMenu = () => {
|
||||
const btn = document.getElementById('menu-button');
|
||||
const menu = document.getElementById('mobile-menu');
|
||||
if (btn && menu) {
|
||||
btn.onclick = null;
|
||||
btn.addEventListener('click', () => {
|
||||
const isExpanded = btn.getAttribute('aria-expanded') === 'true';
|
||||
btn.setAttribute('aria-expanded', !isExpanded);
|
||||
menu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
if (!btn || !menu) return;
|
||||
btn.onclick = () => {
|
||||
const open = menu.classList.toggle('hidden');
|
||||
btn.setAttribute('aria-expanded', !open);
|
||||
};
|
||||
};
|
||||
|
||||
const initCatalogueSearch = () => {
|
||||
const filters = document.querySelectorAll('.filter-btn');
|
||||
const products = document.querySelectorAll('.product-item');
|
||||
const emptyMsg = document.getElementById('empty-msg');
|
||||
if (filters.length === 0) return;
|
||||
|
||||
filters.forEach(btn => {
|
||||
btn.onclick = () => {
|
||||
const category = btn.getAttribute('data-filter').toLowerCase();
|
||||
const category = btn.dataset.filter.toLowerCase();
|
||||
let count = 0;
|
||||
filters.forEach(f => {
|
||||
f.classList.replace('bg-slate-900', 'bg-white');
|
||||
f.classList.replace('text-white', 'text-slate-500');
|
||||
});
|
||||
btn.classList.replace('bg-white', 'bg-slate-900');
|
||||
btn.classList.replace('text-slate-500', 'text-white');
|
||||
filters.forEach(f => f.classList.remove('bg-slate-900', 'text-white'));
|
||||
btn.classList.add('bg-slate-900', 'text-white');
|
||||
|
||||
products.forEach(item => {
|
||||
const itemCat = (item.getAttribute('data-category') || '').toLowerCase();
|
||||
const isVisible = category === 'all' || itemCat.includes(category);
|
||||
item.style.display = isVisible ? 'block' : 'none';
|
||||
const isVisible = category === 'all' || (item.dataset.category || '').toLowerCase().includes(category);
|
||||
item.classList.toggle('hidden', !isVisible);
|
||||
if (isVisible) count++;
|
||||
});
|
||||
if (emptyMsg) count === 0 ? emptyMsg.classList.remove('hidden') : emptyMsg.classList.add('hidden');
|
||||
emptyMsg?.classList.toggle('hidden', count > 0);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const initAutoRedirect = () => {
|
||||
const container = document.getElementById('payment-check-container');
|
||||
if (container && container.dataset.autoRedirect) {
|
||||
const url = container.dataset.autoRedirect;
|
||||
setTimeout(() => {
|
||||
if (document.getElementById('payment-check-container')) Turbo.visit(url);
|
||||
}, 10000);
|
||||
}
|
||||
};
|
||||
|
||||
const initRegisterLogic = () => {
|
||||
const siretContainer = document.getElementById('siret-container');
|
||||
const typeRadios = document.querySelectorAll('input[name="type"]');
|
||||
if (!siretContainer || typeRadios.length === 0) return;
|
||||
const updateSiretVisibility = () => {
|
||||
const selectedType = document.querySelector('input[name="type"]:checked')?.value;
|
||||
if (selectedType === 'buisness') {
|
||||
siretContainer.classList.remove('hidden');
|
||||
siretContainer.querySelector('input')?.setAttribute('required', 'required');
|
||||
} else {
|
||||
siretContainer.classList.add('hidden');
|
||||
siretContainer.querySelector('input')?.removeAttribute('required');
|
||||
}
|
||||
if (!siretContainer) return;
|
||||
|
||||
const toggle = () => {
|
||||
const isBiz = document.querySelector('input[name="type"]:checked')?.value === 'buisness';
|
||||
siretContainer.classList.toggle('hidden', !isBiz);
|
||||
siretContainer.querySelector('input')?.toggleAttribute('required', isBiz);
|
||||
};
|
||||
typeRadios.forEach(radio => radio.addEventListener('change', updateSiretVisibility));
|
||||
updateSiretVisibility();
|
||||
typeRadios.forEach(r => r.addEventListener('change', toggle));
|
||||
toggle();
|
||||
};
|
||||
|
||||
const sendToAnalytics = ({ name, delta, id }) => {
|
||||
// On ne veut pas polluer les stats avec les tests Lighthouse
|
||||
if (isLighthouse()) return;
|
||||
|
||||
const body = JSON.stringify({
|
||||
name, // 'LCP', 'INP', ou 'CLS'
|
||||
value: delta, // La valeur de la mesure
|
||||
id, // ID unique de la session de page (pour éviter les doublons)
|
||||
path: window.location.pathname // Pour savoir quelle page est lente
|
||||
});
|
||||
|
||||
const url = '/reservation/web-vitals';
|
||||
|
||||
// sendBeacon est idéal pour les stats car il ne bloque pas le thread principal
|
||||
if (navigator.sendBeacon) {
|
||||
navigator.sendBeacon(url, body);
|
||||
} else {
|
||||
fetch(url, { body, method: 'POST', keepalive: true });
|
||||
}
|
||||
// --- INITIALISATION ---
|
||||
const registerComponents = () => {
|
||||
const comps = [['utm-event', UtmEvent], ['utm-account', UtmAccount], ['cookie-banner', CookieBanner]];
|
||||
comps.forEach(([name, cl]) => { if (!customElements.get(name)) customElements.define(name, cl); });
|
||||
};
|
||||
|
||||
// --- INITIALISATION GLOBALE ---
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initVitals();
|
||||
initTurboLoader();
|
||||
registerComponents();
|
||||
|
||||
onLCP(sendToAnalytics);
|
||||
onINP(sendToAnalytics);
|
||||
onCLS(sendToAnalytics);
|
||||
initLoader();
|
||||
initImageLoader();
|
||||
// Enregistrement Custom Elements
|
||||
if (!customElements.get('utm-event')) customElements.define('utm-event', UtmEvent);
|
||||
if (!customElements.get('utm-account')) customElements.define('utm-account', UtmAccount);
|
||||
if (!customElements.get('cookie-banner')) customElements.define('cookie-banner', CookieBanner);
|
||||
const consent = sessionStorage.getItem('ldk_cookie');
|
||||
if (consent) toggleSentry(consent);
|
||||
|
||||
// Initialisation Sentry basée sur le choix existant
|
||||
const currentConsent = sessionStorage.getItem('ldk_cookie');
|
||||
if (currentConsent) toggleSentry(currentConsent);
|
||||
|
||||
// Écouteurs pour changements de choix cookies
|
||||
window.addEventListener('cookieAccepted', () => toggleSentry('accepted'));
|
||||
window.addEventListener('cookieRefused', () => toggleSentry('refused'));
|
||||
});
|
||||
|
||||
document.addEventListener('turbo:load', () => {
|
||||
onLCP(sendToAnalytics);
|
||||
onINP(sendToAnalytics);
|
||||
onCLS(sendToAnalytics);
|
||||
initMobileMenu();
|
||||
initCatalogueSearch();
|
||||
initAutoRedirect();
|
||||
initRegisterLogic();
|
||||
initVitals();
|
||||
initImageLoader();
|
||||
initBackToTop();
|
||||
initMobileMenu();
|
||||
initRegisterLogic();
|
||||
initCatalogueSearch();
|
||||
|
||||
const payContainer = document.getElementById('payment-check-container');
|
||||
if (payContainer?.dataset.autoRedirect) {
|
||||
setTimeout(() => {
|
||||
if (document.getElementById('payment-check-container')) {
|
||||
Turbo.visit(payContainer.dataset.autoRedirect);
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("turbo:before-cache", () => {
|
||||
document.querySelectorAll('.product-item').forEach(i => i.style.display = 'block');
|
||||
const emptyMsg = document.getElementById('empty-msg');
|
||||
if (emptyMsg) emptyMsg.classList.add('hidden');
|
||||
document.querySelectorAll('.product-item').forEach(i => i.classList.remove('hidden'));
|
||||
document.getElementById('empty-msg')?.classList.add('hidden');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user