From b28c6591a9b37e52b556fe671552afa5eabecac6 Mon Sep 17 00:00:00 2001 From: morgane Date: Thu, 18 Jun 2026 08:58:15 +0000 Subject: [PATCH] Actualiser assets/js/app.js --- assets/js/app.js | 172 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/assets/js/app.js b/assets/js/app.js index e8f0a53..7263141 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1370,6 +1370,17 @@ function renderLocationButtons(containerId, activeKey, onClickFnName) { }).join(''); } +function showConfigTab(tab) { + document.querySelectorAll('.config-tab-content').forEach(el => el.style.display = 'none'); + const target = document.getElementById('config-tab-' + tab); + if (target) target.style.display = 'block'; + document.querySelectorAll('.config-tab-btn').forEach(btn => { + const isActive = btn.dataset.tab === tab; + btn.classList.toggle('btn-primary', isActive); + btn.classList.toggle('btn-secondary', !isActive); + }); +} + async function _loadConfigPage() { const container = document.getElementById('locations-list-container'); if (!container) return; @@ -1384,9 +1395,141 @@ async function _loadConfigPage() { } catch (e) { container.innerHTML = `

Erreur de chargement.

`; } + await _loadCategoriesConfigSection(); await _loadSubcategoryConfigSection(); } +async function _loadCategoriesConfigSection() { + const container = document.getElementById('categories-list-container'); + if (!container) return; + container.innerHTML = `

Chargement…

`; + try { + const result = await api('categories_list', {}, 'GET'); + if (!result.success) { + container.innerHTML = `

Erreur de chargement.

`; + return; + } + renderCategoriesList(result.categories); + } catch (e) { + container.innerHTML = `

Erreur de chargement.

`; + } +} + +function renderCategoriesList(categories) { + const container = document.getElementById('categories-list-container'); + if (!container) return; + if (!categories || categories.length === 0) { + container.innerHTML = `

Aucune catégorie.

`; + return; + } + container.innerHTML = categories.map(cat => { + const builtinBadge = cat.is_builtin + ? `native` + : ''; + const deleteBtn = cat.is_builtin + ? '' + : ``; + return ` +
+
+ + + ${builtinBadge} + + ${deleteBtn} +
+ +
+ `; + }).join(''); +} + +async function addCategory() { + const iconInput = document.getElementById('new-category-icon'); + const labelInput = document.getElementById('new-category-label'); + const keywordsInput = document.getElementById('new-category-keywords'); + const icon = iconInput.value.trim() || '📦'; + const label = labelInput.value.trim(); + const keywords = keywordsInput.value.trim(); + + if (!label) { + showToast('Indique un nom pour la nouvelle catégorie', 'warning'); + return; + } + + showLoading(true); + try { + const result = await api('categories_add', {}, 'POST', { label, icon, keywords }); + showLoading(false); + if (result.success) { + showToast(`Catégorie "${label}" ajoutée`, 'success'); + iconInput.value = ''; + labelInput.value = ''; + keywordsInput.value = ''; + CATEGORY_LABELS[result.key] = label; + CATEGORY_ICONS[result.key] = icon; + if (!CATEGORY_LOCATION[result.key]) CATEGORY_LOCATION[result.key] = 'dispensa'; + if (keywords) CUSTOM_CATEGORY_KEYWORDS[result.key] = keywords.split(',').map(k => k.trim().toLowerCase()).filter(Boolean); + _loadCategoriesConfigSection(); + } else { + showToast(result.error || 'Erreur lors de l\'ajout', 'error'); + } + } catch (e) { + showLoading(false); + showToast('Erreur lors de l\'ajout', 'error'); + } +} + +async function updateCategory(key) { + const icon = document.getElementById(`cat-icon-${key}`).value.trim() || '📦'; + const label = document.getElementById(`cat-label-${key}`).value.trim(); + const keywords = document.getElementById(`cat-keywords-${key}`).value.trim(); + + if (!label) { + showToast('Le nom ne peut pas être vide', 'warning'); + return; + } + + showLoading(true); + try { + const result = await api('categories_update', {}, 'POST', { key, label, icon, keywords }); + showLoading(false); + if (result.success) { + showToast('Catégorie mise à jour', 'success'); + CATEGORY_LABELS[key] = label; + CATEGORY_ICONS[key] = icon; + CUSTOM_CATEGORY_KEYWORDS[key] = keywords ? keywords.split(',').map(k => k.trim().toLowerCase()).filter(Boolean) : []; + } else { + showToast(result.error || 'Erreur', 'error'); + } + } catch (e) { + showLoading(false); + showToast('Erreur', 'error'); + } +} + +async function removeCategory(key) { + if (!confirm(`Supprimer la catégorie "${CATEGORY_LABELS[key] || key}" ?`)) return; + + showLoading(true); + try { + const result = await api('categories_remove', {}, 'POST', { key }); + showLoading(false); + if (result.success) { + showToast('Catégorie supprimée', 'success'); + delete CATEGORY_LABELS[key]; + delete CATEGORY_ICONS[key]; + delete CUSTOM_CATEGORY_KEYWORDS[key]; + _loadCategoriesConfigSection(); + } else { + showToast(result.error || 'Impossible de supprimer', 'error'); + } + } catch (e) { + showLoading(false); + showToast('Erreur lors de la suppression', 'error'); + } +} + async function _loadSubcategoryConfigSection() { const catSelect = document.getElementById('subcat-config-category'); if (!catSelect) return; @@ -1628,6 +1771,28 @@ const CATEGORY_ICONS = { 'cereali': '🌾', 'igiene': '🧴', 'pulizia': '🧹', 'altro': '📦' }; +// Mots-clés des catégories personnalisées (utilisateur), pour la détection auto par nom de produit +let CUSTOM_CATEGORY_KEYWORDS = {}; + +async function loadCustomCategories() { + try { + const result = await api('categories_list', {}, 'GET'); + if (result.success && Array.isArray(result.categories)) { + CUSTOM_CATEGORY_KEYWORDS = {}; + result.categories.forEach(cat => { + CATEGORY_LABELS[cat.key] = cat.label; + CATEGORY_ICONS[cat.key] = cat.icon || '📦'; + if (!CATEGORY_LOCATION[cat.key]) CATEGORY_LOCATION[cat.key] = 'dispensa'; + if (cat.keywords) { + CUSTOM_CATEGORY_KEYWORDS[cat.key] = cat.keywords.split(',').map(k => k.trim().toLowerCase()).filter(Boolean); + } + }); + } + } catch (e) { + console.warn('[EverShelf] Could not load custom categories:', e); + } +} + // Auto-detect location based on category and product name const CATEGORY_LOCATION = { 'latticini': 'frigo', 'carne': 'frigo', 'pesce': 'frigo', @@ -1774,6 +1939,10 @@ function guessCategoryFromName(name, brand = '') { if (/sapone|shampoo|dentifricio|deodorante|carta igienica|fazzoletti|cotton fioc|assorbente|rasoio|schiuma da barba|gel doccia|balsamo\b|lozione/.test(n)) return 'igiene'; // Pulizia casa if (/detersivo|pulito|sgrassatore|candeggina|ammorbidente|anticalcare|bucato|piatti\b|lavatrice|lavastoviglie|detergente/.test(n)) return 'pulizia'; + // Mots-clés personnalisés définis via Config (catégories ajoutées par l'utilisateur) + for (const [key, keywords] of Object.entries(CUSTOM_CATEGORY_KEYWORDS)) { + if (keywords.some(kw => kw && n.includes(kw))) return key; + } return 'altro'; } @@ -19856,6 +20025,9 @@ async function _initApp() { const _startupOk = await _runStartupCheck(); if (!_startupOk) return; // preloader stays visible with error; app does not start + // Load custom categories (merges into CATEGORY_LABELS/CATEGORY_ICONS before any UI renders) + await loadCustomCategories(); + // Load custom locations (merges into LOCATIONS before any UI renders) await loadCustomLocations();