@@ -87,6 +87,16 @@
|
||||
border-color: rgba(239, 68, 68, 0.5) !important;
|
||||
color: var(--danger) !important;
|
||||
}
|
||||
.groceries-toolbar-btn--outline {
|
||||
border: 1px dashed var(--border-strong) !important;
|
||||
background: transparent !important;
|
||||
color: var(--text-muted) !important;
|
||||
}
|
||||
.groceries-toolbar-btn--outline:hover {
|
||||
background: var(--bg-page) !important;
|
||||
color: var(--text-main) !important;
|
||||
border-color: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
.groceries-section-title {
|
||||
font-size: 0.72rem;
|
||||
@@ -249,3 +259,90 @@
|
||||
[data-theme='dark'] .groceries-check.checked {
|
||||
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.22), 0 2px 8px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
/* Historique produits */
|
||||
.groceries-history-card {
|
||||
margin-top: 1.75rem;
|
||||
padding: 1.15rem 1.2rem 1.2rem;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--border-light);
|
||||
background: var(--bg-panel);
|
||||
box-shadow: 0 2px 12px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
.groceries-history-h2 {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 0.35rem;
|
||||
color: var(--text-main);
|
||||
}
|
||||
.groceries-history-meta {
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-muted);
|
||||
margin: 0 0 0.85rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.groceries-history-settings-link {
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
.groceries-history-settings-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.groceries-history-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
.groceries-history-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.45rem 0.75rem 0.45rem 0.55rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
color: var(--text-main);
|
||||
background: var(--bg-page);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
|
||||
text-align: left;
|
||||
max-width: 100%;
|
||||
}
|
||||
.groceries-history-chip:hover {
|
||||
border-color: var(--primary);
|
||||
background: rgba(59, 130, 246, 0.06);
|
||||
box-shadow: 0 1px 4px rgba(59, 130, 246, 0.12);
|
||||
}
|
||||
.groceries-history-chip-plus {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
color: var(--text-on-primary, #fff);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.groceries-history-empty {
|
||||
margin: 0.5rem 0 0;
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .groceries-history-card {
|
||||
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
[data-theme='dark'] .groceries-history-chip {
|
||||
background: var(--bg-page);
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
[data-theme='dark'] .groceries-history-chip:hover {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
@@ -54,11 +54,12 @@ async function api(action, method = 'GET', data = null, extra = '') {
|
||||
return j.data;
|
||||
}
|
||||
|
||||
async function loadItems() {
|
||||
async function loadAll() {
|
||||
try {
|
||||
const items = await api('items', 'GET');
|
||||
const [items, hist] = await Promise.all([api('items', 'GET'), api('history', 'GET')]);
|
||||
render(items);
|
||||
updateSubtitle(items);
|
||||
renderHistory(hist);
|
||||
} catch (e) {
|
||||
toast(e.message || T('error_occured', 'Erreur'), 'error');
|
||||
}
|
||||
@@ -79,6 +80,46 @@ function updateSubtitle(items) {
|
||||
.replace('{cart}', String(cart));
|
||||
}
|
||||
|
||||
function renderHistory(hist) {
|
||||
const root = document.getElementById('groceries-history-root');
|
||||
if (!root) return;
|
||||
const max = hist && typeof hist.history_max === 'number' ? hist.history_max : 20;
|
||||
const labels = (hist && hist.labels) || [];
|
||||
const title = escHtml(T('groceries_history_title', 'Historique'));
|
||||
const metaTpl = T('groceries_history_meta', 'Jusqu’à {max} produits · ');
|
||||
const meta = escHtml(metaTpl.replace('{max}', String(max)));
|
||||
const linkLabel = escHtml(T('groceries_settings_link', 'Paramètres'));
|
||||
let chips = '';
|
||||
labels.forEach((lab) => {
|
||||
const l = String(lab);
|
||||
chips +=
|
||||
'<button type="button" class="groceries-history-chip" data-label="' +
|
||||
escAttr(l) +
|
||||
'"><span class="groceries-history-chip-plus">+</span>' +
|
||||
escHtml(l) +
|
||||
'</button>';
|
||||
});
|
||||
const emptyHint = labels.length
|
||||
? ''
|
||||
: '<p class="groceries-history-empty">' + escHtml(T('groceries_history_empty', '')) + '</p>';
|
||||
root.innerHTML =
|
||||
'<div class="groceries-history-inner">' +
|
||||
'<div class="groceries-history-head">' +
|
||||
'<h2 class="groceries-history-h2">' +
|
||||
title +
|
||||
'</h2>' +
|
||||
'<p class="groceries-history-meta">' +
|
||||
meta +
|
||||
'<a href="/settings.php" class="groceries-history-settings-link">' +
|
||||
linkLabel +
|
||||
'</a></p></div>' +
|
||||
'<div class="groceries-history-chips">' +
|
||||
chips +
|
||||
'</div>' +
|
||||
emptyHint +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function render(items) {
|
||||
const wrap = document.getElementById('groceries-lists');
|
||||
if (!items.length) {
|
||||
@@ -160,7 +201,19 @@ async function addFromInput() {
|
||||
input.value = '';
|
||||
input.focus();
|
||||
toast(T('groceries_added', 'Ajouté'));
|
||||
loadItems();
|
||||
loadAll();
|
||||
} catch (e) {
|
||||
toast(e.message || T('error_occured', 'Erreur'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function addFromHistory(label) {
|
||||
const t = String(label || '').trim();
|
||||
if (!t) return;
|
||||
try {
|
||||
await api('items', 'POST', { label: t });
|
||||
toast(T('groceries_added', 'Ajouté'));
|
||||
loadAll();
|
||||
} catch (e) {
|
||||
toast(e.message || T('error_occured', 'Erreur'), 'error');
|
||||
}
|
||||
@@ -169,7 +222,7 @@ async function addFromInput() {
|
||||
async function toggleCart(id, next) {
|
||||
try {
|
||||
await api('items', 'PUT', { in_cart: !!parseInt(next, 10) }, '&id=' + id);
|
||||
loadItems();
|
||||
loadAll();
|
||||
} catch (e) {
|
||||
toast(e.message || T('error_occured', 'Erreur'), 'error');
|
||||
}
|
||||
@@ -188,7 +241,7 @@ async function editItem(id) {
|
||||
try {
|
||||
await api('items', 'PUT', { label: t }, '&id=' + id);
|
||||
toast(T('groceries_updated', 'Mis à jour'));
|
||||
loadItems();
|
||||
loadAll();
|
||||
} catch (e) {
|
||||
toast(e.message || T('error_occured', 'Erreur'), 'error');
|
||||
}
|
||||
@@ -203,7 +256,7 @@ async function deleteItem(id) {
|
||||
try {
|
||||
await api('items', 'DELETE', null, '&id=' + id);
|
||||
toast(T('groceries_deleted', 'Supprimé'));
|
||||
loadItems();
|
||||
loadAll();
|
||||
} catch (e) {
|
||||
toast(e.message || T('error_occured', 'Erreur'), 'error');
|
||||
}
|
||||
@@ -218,7 +271,7 @@ async function uncheckAll() {
|
||||
try {
|
||||
await api('uncheck_all', 'POST', {});
|
||||
toast(T('groceries_reset_next', 'Liste prête pour la prochaine sortie'));
|
||||
loadItems();
|
||||
loadAll();
|
||||
} catch (e) {
|
||||
toast(e.message || T('error_occured', 'Erreur'), 'error');
|
||||
}
|
||||
@@ -233,7 +286,22 @@ async function deletePicked() {
|
||||
try {
|
||||
await api('delete_picked', 'POST', {});
|
||||
toast(T('groceries_picked_removed', 'Articles retirés'));
|
||||
loadItems();
|
||||
loadAll();
|
||||
} catch (e) {
|
||||
toast(e.message || T('error_occured', 'Erreur'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function clearWholeList() {
|
||||
const ok = await pachaConfirm(
|
||||
T('groceries_confirm_empty_list_title', 'Vider toute la liste ?'),
|
||||
T('groceries_confirm_empty_list_body', '')
|
||||
);
|
||||
if (!ok) return;
|
||||
try {
|
||||
await api('clear_all', 'POST', {});
|
||||
toast(T('groceries_list_cleared', 'Liste vidée'));
|
||||
loadAll();
|
||||
} catch (e) {
|
||||
toast(e.message || T('error_occured', 'Erreur'), 'error');
|
||||
}
|
||||
@@ -249,5 +317,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
loadItems();
|
||||
const histRoot = document.getElementById('groceries-history-root');
|
||||
if (histRoot) {
|
||||
histRoot.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.groceries-history-chip');
|
||||
if (!btn || !histRoot.contains(btn)) return;
|
||||
const raw = btn.getAttribute('data-label');
|
||||
if (raw) addFromHistory(raw);
|
||||
});
|
||||
}
|
||||
loadAll();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user