Spaces:
Running
Running
| // ===== Données exemple (contexte terrain / chantier) ===== | |
| let products = [ | |
| { id: 1, name: "Gants de manutention", unit: "paire", qty: 3 }, | |
| { id: 2, name: "Vis autoperceuse 4.8×38", unit: "boîte", qty: 2 }, | |
| { id: 3, name: "Marteau de coffreur 700g", unit: "pièce", qty: 1 }, | |
| { id: 4, name: "Chaussures de sécurité S3", unit: "paire", qty: 1 }, | |
| { id: 5, name: "Niveau à bulle 40 cm", unit: "pièce", qty: 1 }, | |
| { id: 6, name: "Ruban de mesure 5 m", unit: "pièce", qty: 2 }, | |
| { id: 7, name: "Bobine de fil de tension", unit: "pièce", qty: 1 }, | |
| { id: 8, name: "Serre-joints 80 cm", unit: "pièce", qty: 2 }, | |
| { id: 9, name: "Cutter lame 18 mm", unit: "pièce", qty: 3 }, | |
| { id: 10, name: "Lunettes de protection", unit: "pièce", qty: 5 }, | |
| { id: 11, name: "Coffret forets béton SDS", unit: "boîte", qty: 1 }, | |
| { id: 12, name: "Gilet haute visibilité Classe 2", unit: "pièce", qty: 4 }, | |
| ]; | |
| let activeId = null; | |
| const listEl = document.getElementById('product-list'); | |
| const headerCountEl = document.getElementById('header-count'); | |
| const totalQtyEl = document.getElementById('total-qty'); | |
| // ===== Rendu initial ===== | |
| function init() { | |
| renderList(); | |
| updateSummary(); | |
| lucide.createIcons(); | |
| } | |
| function renderList() { | |
| listEl.innerHTML = products.map(p => ` | |
| <div class="product-row" data-id="${p.id}"> | |
| <div class="row-main"> | |
| <div class="product-meta"> | |
| <div class="product-name">${escapeHtml(p.name)}</div> | |
| <div class="product-unit">${escapeHtml(p.unit)}</div> | |
| </div> | |
| <span class="qty-badge" data-qty-id="${p.id}">${p.qty}</span> | |
| </div> | |
| <div class="row-actions"> | |
| <button class="btn-action btn-minus" data-action="minus" aria-label="Diminuer la quantité"> | |
| <i data-lucide="minus" class="w-5 h-5"></i> | |
| </button> | |
| <button class="btn-action btn-plus" data-action="plus" aria-label="Augmenter la quantité"> | |
| <i data-lucide="plus" class="w-5 h-5"></i> | |
| </button> | |
| <button class="btn-action btn-delete" data-action="delete" aria-label="Supprimer le produit"> | |
| <i data-lucide="trash-2" class="w-5 h-5"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `).join(''); | |
| lucide.createIcons(); | |
| } | |
| function escapeHtml(str) { | |
| const div = document.createElement('div'); | |
| div.textContent = str; | |
| return div.innerHTML; | |
| } | |
| // ===== Mise à jour du résumé ===== | |
| function updateSummary() { | |
| const total = products.reduce((sum, p) => sum + p.qty, 0); | |
| const count = products.length; | |
| headerCountEl.textContent = `${count} article${count > 1 ? 's' : ''}`; | |
| totalQtyEl.textContent = total; | |
| } | |
| // ===== Gestion du focus / activation ===== | |
| function setActiveId(id) { | |
| // Désactiver l’ancien | |
| if (activeId !== null) { | |
| const oldRow = document.querySelector(`.product-row[data-id="${activeId}"]`); | |
| if (oldRow) oldRow.classList.remove('active'); | |
| } | |
| activeId = id; | |
| // Activer le nouveau | |
| if (activeId !== null) { | |
| const newRow = document.querySelector(`.product-row[data-id="${activeId}"]`); | |
| if (newRow) newRow.classList.add('active'); | |
| } | |
| } | |
| // ===== Actions quantité / suppression ===== | |
| function updateQty(id, delta) { | |
| const product = products.find(p => p.id === id); | |
| if (!product) return; | |
| const newQty = product.qty + delta; | |
| if (newQty < 1) return; // empêche de descendre sous 1 (utiliser la poubelle) | |
| product.qty = newQty; | |
| // Mise à jour DOM ciblée (pas de re-render complet pour garder l’état visuel) | |
| const badge = document.querySelector(`.qty-badge[data-qty-id="${id}"]`); | |
| if (badge) badge.textContent = product.qty; | |
| updateSummary(); | |
| } | |
| function deleteProduct(id) { | |
| const row = document.querySelector(`.product-row[data-id="${id}"]`); | |
| if (row) { | |
| // Animation rapide de sortie | |
| row.style.transition = "opacity 0.2s ease, transform 0.2s ease, max-height 0.25s ease"; | |
| row.style.opacity = "0"; | |
| row.style.transform = "translateX(-1rem)"; | |
| row.style.maxHeight = row.offsetHeight + "px"; | |
| requestAnimationFrame(() => { | |
| row.style.maxHeight = "0px"; | |
| row.style.padding = "0px"; | |
| row.style.margin = "0px"; | |
| row.style.overflow = "hidden"; | |
| }); | |
| } | |
| setTimeout(() => { | |
| products = products.filter(p => p.id !== id); | |
| if (activeId === id) activeId = null; | |
| renderList(); | |
| updateSummary(); | |
| // Ré-applique l’état actif si un autre était déjà sélectionné (théoriquement impossible ici car 1 seul actif) | |
| if (activeId !== null) { | |
| const remain = document.querySelector(`.product-row[data-id="${activeId}"]`); | |
| if (remain) remain.classList.add('active'); | |
| } | |
| }, 220); | |
| } | |
| // ===== Écouteurs d’événements ===== | |
| // Clics sur les lignes et boutons (délégation) | |
| listEl.addEventListener('click', (e) => { | |
| const btn = e.target.closest('[data-action]'); | |
| // 1. Bouton d’action (+, –, poubelle) | |
| if (btn) { | |
| e.stopPropagation(); | |
| const row = btn.closest('.product-row'); | |
| const id = parseInt(row.dataset.id, 10); | |
| const action = btn.dataset.action; | |
| if (action === 'plus') updateQty(id, 1); | |
| else if (action === 'minus') updateQty(id, -1); | |
| else if (action === 'delete') deleteProduct(id); | |
| return; | |
| } | |
| // 2. Ligne produit (hors bouton) | |
| const row = e.target.closest('.product-row'); | |
| if (row) { | |
| const id = parseInt(row.dataset.id, 10); | |
| // Si on tape la ligne déjà active → on la referme | |
| // Sinon on active la nouvelle | |
| setActiveId(activeId === id ? null : id); | |
| } | |
| }); | |
| // Clic en dehors des lignes (zone vide, header, footer…) → désactive tout | |
| document.getElementById('app').addEventListener('click', (e) => { | |
| const row = e.target.closest('.product-row'); | |
| const btn = e.target.closest('[data-action]'); | |
| if (!row && !btn) { | |
| setActiveId(null); | |
| } | |
| }); | |
| // Démarrage | |
| init(); |