// HouseHub — Liste de courses const API = '/modules/groceries/api.php'; function escHtml(s) { const d = document.createElement('div'); d.textContent = String(s ?? ''); return d.innerHTML; } function T(key, fallback) { if (typeof tr === 'function') { const v = tr(key); if (v && v !== key) return v; } return fallback; } function escAttr(s) { return String(s ?? '') .replace(/&/g, '&') .replace(/"/g, '"') .replace(/ t.remove(), 3000); } async function api(action, method = 'GET', data = null, extra = '') { const opts = { method, credentials: 'same-origin', headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, }; if (data) { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(data); } const r = await fetch(API + '?action=' + action + extra, opts); const text = await r.text(); let j; try { j = JSON.parse(text); } catch (e) { console.error('Bad JSON from API:', text.slice(0, 200)); throw new Error(T('error_occured', 'Erreur serveur')); } if (!j.ok) throw new Error(j.error || T('error_occured', 'Erreur')); return j.data; } async function loadItems() { try { const items = await api('items', 'GET'); render(items); updateSubtitle(items); } catch (e) { toast(e.message || T('error_occured', 'Erreur'), 'error'); } } function updateSubtitle(items) { const el = document.getElementById('groceries-subtitle'); if (!el) return; const pending = items.filter((i) => !parseInt(i.in_cart, 10)).length; const cart = items.filter((i) => parseInt(i.in_cart, 10)).length; if (!items.length) { el.textContent = ''; return; } const tpl = typeof tr === 'function' ? tr('groceries_subtitle') : ''; el.textContent = (tpl && tpl !== 'groceries_subtitle' ? tpl : '{pending} à prendre · {cart} dans le caddie') .replace('{pending}', String(pending)) .replace('{cart}', String(cart)); } function render(items) { const wrap = document.getElementById('groceries-lists'); if (!items.length) { const msg = T('groceries_empty', 'Rien pour le moment. Ajoutez un produit ci-dessus.'); wrap.innerHTML = '
🛒

' + escHtml(msg) + '

'; return; } const pending = items.filter((i) => !parseInt(i.in_cart, 10)); const cart = items.filter((i) => parseInt(i.in_cart, 10)); const labPending = escHtml(T('groceries_section_pending', 'À prendre')); const labCart = escHtml(T('groceries_section_cart', 'Dans le caddie')); let html = ''; if (pending.length) { html += '
' + labPending + '
'; pending.forEach((i) => { html += rowHtml(i, false); }); html += '
'; } if (cart.length) { html += '
' + labCart + '
'; cart.forEach((i) => { html += rowHtml(i, true); }); html += '
'; } wrap.innerHTML = html; } function rowHtml(i, inCart) { const id = i.id; const checkClass = 'groceries-check' + (inCart ? ' checked' : ''); const rowClass = 'groceries-row' + (inCart ? ' in-cart' : ''); const aria = escAttr(T('groceries_aria_toggle', 'Marquer dans le caddie')); return ( '
' + '' + '
' + escHtml(i.label) + '
' + '
' + '' + '' + '
' ); } async function addFromInput() { const input = document.getElementById('groceries-new'); const label = (input.value || '').trim(); if (!label) return; try { await api('items', 'POST', { label: label }); input.value = ''; input.focus(); toast(T('groceries_added', 'Ajouté')); loadItems(); } catch (e) { toast(e.message || T('error_occured', 'Erreur'), 'error'); } } async function toggleCart(id, next) { try { await api('items', 'PUT', { in_cart: !!parseInt(next, 10) }, '&id=' + id); loadItems(); } catch (e) { toast(e.message || T('error_occured', 'Erreur'), 'error'); } } async function editItem(id) { const row = document.querySelector('.groceries-row[data-id="' + id + '"]'); const current = row ? row.querySelector('.groceries-label').textContent : ''; const label = window.prompt(T('groceries_prompt_edit', 'Modifier le produit'), current); if (label === null) return; const t = label.trim(); if (!t) { toast(T('groceries_label_empty', 'Libellé vide'), 'error'); return; } try { await api('items', 'PUT', { label: t }, '&id=' + id); toast(T('groceries_updated', 'Mis à jour')); loadItems(); } catch (e) { toast(e.message || T('error_occured', 'Erreur'), 'error'); } } async function deleteItem(id) { const ok = await pachaConfirm( T('groceries_confirm_del_title', 'Supprimer ?'), T('groceries_confirm_del_body', 'Retirer ce produit de la liste ?') ); if (!ok) return; try { await api('items', 'DELETE', null, '&id=' + id); toast(T('groceries_deleted', 'Supprimé')); loadItems(); } catch (e) { toast(e.message || T('error_occured', 'Erreur'), 'error'); } } async function uncheckAll() { const ok = await pachaConfirm( T('groceries_confirm_uncheck_title', 'Tout remettre à prendre'), T('groceries_confirm_uncheck_body', '') ); if (!ok) return; try { await api('uncheck_all', 'POST', {}); toast(T('groceries_reset_next', 'Liste prête pour la prochaine sortie')); loadItems(); } catch (e) { toast(e.message || T('error_occured', 'Erreur'), 'error'); } } async function deletePicked() { const ok = await pachaConfirm( T('groceries_confirm_clear_title', 'Retirer les produits du caddie'), T('groceries_confirm_clear_body', '') ); if (!ok) return; try { await api('delete_picked', 'POST', {}); toast(T('groceries_picked_removed', 'Articles retirés')); loadItems(); } catch (e) { toast(e.message || T('error_occured', 'Erreur'), 'error'); } } document.addEventListener('DOMContentLoaded', () => { const input = document.getElementById('groceries-new'); if (input) { input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addFromInput(); } }); } loadItems(); });