export class FlowReserve extends HTMLAnchorElement { constructor() { super(); this.sidebarId = 'flow-reserve-sidebar'; this.storageKey = 'pl_list'; this.apiUrl = '/basket/json'; this.checkAvailabilityUrl = '/produit/check/basket'; this.isOpen = false; this.productsAvailable = true; } connectedCallback() { this.addEventListener('click', (e) => { e.preventDefault(); this.toggleSidebar(); }); // Listen for updates to the cart from other components window.addEventListener('cart:updated', () => { this.updateBadge(); this._checkProductAvailability(); // Re-check on cart update }); // Initial badge update this.updateBadge(); this._checkProductAvailability(); } /** * Updates the notification badge on the icon */ updateBadge() { const list = this.getList(); const badge = this.querySelector('[data-count]'); if (badge) { badge.innerText = list.length; if (list.length > 0) { badge.classList.remove('hidden'); } else { badge.classList.add('hidden'); } } } getList() { try { return JSON.parse(sessionStorage.getItem(this.storageKey) || '[]'); } catch (e) { return []; } } getOptions() { try { return JSON.parse(sessionStorage.getItem('pl_options') || '{}'); } catch (e) { return {}; } } removeFromList(id) { let list = this.getList(); list = list.filter(itemId => itemId.toString() !== id.toString()); sessionStorage.setItem(this.storageKey, JSON.stringify(list)); // Remove options for this product const options = this.getOptions(); if (options[id]) { delete options[id]; sessionStorage.setItem('pl_options', JSON.stringify(options)); } window.dispatchEvent(new CustomEvent('cart:updated')); this.refreshContent(); // Re-fetch and render } toggleSidebar() { if (this.isOpen) { this.close(); } else { this.open(); } } open() { this.ensureSidebarExists(); const sidebar = document.getElementById(this.sidebarId); const backdrop = sidebar.querySelector('.backdrop'); const panel = sidebar.querySelector('.panel'); sidebar.classList.remove('pointer-events-none'); backdrop.classList.remove('opacity-0'); panel.classList.remove('translate-x-full'); this.isOpen = true; this.refreshContent(); } close() { const sidebar = document.getElementById(this.sidebarId); if (!sidebar) return; const backdrop = sidebar.querySelector('.backdrop'); const panel = sidebar.querySelector('.panel'); backdrop.classList.add('opacity-0'); panel.classList.add('translate-x-full'); setTimeout(() => { sidebar.classList.add('pointer-events-none'); }, 300); // Match transition duration this.isOpen = false; } async _checkProductAvailability() { const ids = this.getList(); if (ids.length === 0) { this.productsAvailable = true; return; } let dates = { start: null, end: null }; try { dates = JSON.parse(sessionStorage.getItem('reservation_dates') || '{}'); } catch (e) { console.warn('Invalid reservation dates in sessionStorage for availability check'); } try { const response = await fetch(this.checkAvailabilityUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids, start: dates.start, end: dates.end }) }); if (!response.ok) throw new Error('Erreur réseau lors de la vérification de disponibilité'); const data = await response.json(); this.productsAvailable = data.available; if (data.unavailable_products_ids && data.unavailable_products_ids.length > 0) { let currentList = this.getList(); const initialLength = currentList.length; // Filter out unavailable items currentList = currentList.filter(id => !data.unavailable_products_ids.includes(parseInt(id)) && !data.unavailable_products_ids.includes(String(id))); if (currentList.length !== initialLength) { sessionStorage.setItem(this.storageKey, JSON.stringify(currentList)); window.dispatchEvent(new CustomEvent('cart:updated')); console.warn('Produits indisponibles retirés du panier:', data.unavailable_products_ids); // Force refresh to update UI immediately this.refreshContent(); } } } catch (error) { console.error('Erreur lors de la vérification de disponibilité:', error); this.productsAvailable = false; } } ensureSidebarExists() { if (document.getElementById(this.sidebarId)) return; const template = `

Ma Super
Future Réservation

`; document.body.insertAdjacentHTML('beforeend', template); // Bind events const sidebar = document.getElementById(this.sidebarId); const closeHandler = (e) => { if (e) { e.preventDefault(); e.stopPropagation(); } this.close(); }; sidebar.querySelector('.backdrop').addEventListener('click', closeHandler); sidebar.querySelector('#flow-reserve-close').addEventListener('click', closeHandler); } async refreshContent() { const container = document.getElementById('flow-reserve-content'); const footer = document.getElementById('flow-reserve-footer'); // Loader container.innerHTML = `
`; footer.innerHTML = ''; const ids = this.getList(); const options = this.getOptions(); // Retrieve dates from sessionStorage let dates = { start: null, end: null }; try { dates = JSON.parse(sessionStorage.getItem('reservation_dates') || '{}'); } catch (e) { console.warn('Invalid reservation dates in sessionStorage'); } if (ids.length === 0) { this.renderEmpty(container, footer); this.productsAvailable = true; return; } // Display warning if products are not available if (!this.productsAvailable) { container.innerHTML = `

Attention :

Certains produits de votre panier ne sont plus disponibles. Veuillez vérifier votre sélection.

`; footer.innerHTML = ` `; return; } try { const response = await fetch(this.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids, options, start: dates.start, end: dates.end, formule: sessionStorage.getItem('active_formule') }) }); if (!response.ok) throw new Error('Erreur réseau'); const data = await response.json(); // Handle removed products (deleted from DB) if (data.unavailable_products_ids && data.unavailable_products_ids.length > 0) { let currentList = this.getList(); const initialLength = currentList.length; currentList = currentList.filter(id => !data.unavailable_products_ids.includes(parseInt(id)) && !data.unavailable_products_ids.includes(String(id))); // Handle string/int types if (currentList.length !== initialLength) { sessionStorage.setItem(this.storageKey, JSON.stringify(currentList)); window.dispatchEvent(new CustomEvent('cart:updated')); console.warn('Certains produits ont été retirés car ils n\'existent plus:', data.unavailable_products_ids); } } // Merge client-side dates if server didn't return them (or to prioritize client choice) if (!data.start_date && dates.start) data.start_date = this.formatDate(dates.start); if (!data.end_date && dates.end) data.end_date = this.formatDate(dates.end); // Fallback: if server returns dates in a format we can use directly, fine. // If we just want to display what is in local storage: if (dates.start) data.start_date = this.formatDate(dates.start); if (dates.end) data.end_date = this.formatDate(dates.end); this.renderList(container, footer, data); } catch (error) { console.error(error); container.innerHTML = `

Une erreur est survenue lors du chargement de votre panier.

`; } } renderEmpty(container, footer) { container.innerHTML = `

Votre panier est vide

Ajoutez du bonheur à votre événement !

Voir le catalogue
`; footer.innerHTML = ''; container.querySelector('#flow-empty-catalog-link').addEventListener('click', () => this.close()); } renderList(container, footer, data) { // --- HEADER DATES --- let datesHtml = ''; if (data.start_date && data.end_date) { datesHtml = `

Votre période

${data.start_date} ${data.end_date}
`; } const productsHtml = data.products.map(product => { let optionsHtml = ''; if (product.options && product.options.length > 0) { optionsHtml = '
'; product.options.forEach(opt => { optionsHtml += `
+ ${opt.name} ${this.formatPrice(opt.price)}
`; }); optionsHtml += '
'; } return `
${product.name}
${product.in_formule ? `Inclus` : ''}

${product.name}

1J: ${this.formatPrice(product.priceHt1Day)} HT ${product.priceHTSupDay ? `|Sup: ${this.formatPrice(product.priceHTSupDay)} HT` : ''}
${optionsHtml}
${this.formatPrice(product.totalPriceTTC || product.totalPriceHT)} Total
`; }).join(''); container.innerHTML = `
${datesHtml}${productsHtml}
`; // Attach event listeners container.querySelectorAll('.remove-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); this.removeFromList(btn.dataset.removeId); }); }); // --- RENDER FOOTER (TOTALS) --- const total = data.total || {}; const hasTva = total.totalTva > 0; const promotion = data.promotion; const formuleName = total.formule; let promoHtml = ''; let originalTotalHT = total.totalHT; if (formuleName) { promoHtml += `
Formule ${formuleName}
`; } if (promotion && total.discount > 0) { originalTotalHT = total.totalHT + total.discount; promoHtml += `
${promotion.name} (-${promotion.percentage}%) -${this.formatPrice(total.discount)}
`; } footer.innerHTML = `
Total HT ${this.formatPrice(originalTotalHT)}
${promoHtml} ${hasTva ? `
TVA ${this.formatPrice(total.totalTva)}
` : ''}
Total ${hasTva ? 'TTC' : 'HT'} ${this.formatPrice(total.totalTTC || total.totalHT)}
Valider ma demande `; const validateBtn = footer.querySelector('#flow-validate-btn'); if (validateBtn) { validateBtn.addEventListener('click', (e) => { this.validateBasket(e); }); } } async validateBasket(e) { e.preventDefault(); const ids = this.getList(); const options = this.getOptions(); let dates = { start: null, end: null }; try { dates = JSON.parse(sessionStorage.getItem('reservation_dates') || '{}'); } catch (error) { console.warn('Invalid reservation dates in sessionStorage'); } try { const response = await fetch('/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids, options, start: dates.start, end: dates.end, formule: sessionStorage.getItem('active_formule') }) }); if (!response.ok) throw new Error('Erreur réseau'); const data = await response.json(); if (data.flowUrl) { window.location.href = data.flowUrl; } } catch (error) { console.error('Erreur lors de la validation du panier', error); } } formatDate(dateString) { if (!dateString) return ''; try { const date = new Date(dateString); return new Intl.DateTimeFormat('fr-FR').format(date); } catch (e) { return dateString; } } formatPrice(amount) { if (amount === undefined || amount === null) return '-'; return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(amount); } }