Merge branch 'main' of http://nas.percolouco.com:3500/perco/HouseHub into parent_id
This commit is contained in:
+19
-7
@@ -15,6 +15,7 @@ require_once dirname(__DIR__, 2) . '/includes/db.php';
|
||||
// ── Auto-create tables ────────────────────────────────────────────────────────
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS pf_lists (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL DEFAULT 'Ma liste',
|
||||
color VARCHAR(20) DEFAULT NULL, list_type VARCHAR(50) DEFAULT NULL,
|
||||
position INT NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||
|
||||
@@ -48,6 +49,8 @@ $pdo->exec("CREATE TABLE IF NOT EXISTS pf_item_category_rules (
|
||||
try { $pdo->exec("ALTER TABLE pf_grocery_items ADD COLUMN list_id INT NOT NULL DEFAULT 1 AFTER id"); } catch (\Exception $e) {}
|
||||
try { $pdo->exec("ALTER TABLE pf_grocery_items ADD COLUMN category_id INT DEFAULT NULL AFTER list_id"); } catch (\Exception $e) {}
|
||||
try { $pdo->exec("ALTER TABLE pf_grocery_items ADD KEY idx_items_list (list_id)"); } catch (\Exception $e) {}
|
||||
try { $pdo->exec("ALTER TABLE pf_lists ADD COLUMN color VARCHAR(20) DEFAULT NULL AFTER name"); } catch (\Exception $e) {}
|
||||
try { $pdo->exec("ALTER TABLE pf_lists ADD COLUMN list_type VARCHAR(50) DEFAULT NULL AFTER color"); } catch (\Exception $e) {}
|
||||
|
||||
// ── Seed categories if empty ──────────────────────────────────────────────────
|
||||
if ((int)$pdo->query("SELECT COUNT(*) FROM pf_list_categories")->fetchColumn() === 0) {
|
||||
@@ -313,23 +316,32 @@ if ($action === 'set_category' && $method === 'POST') {
|
||||
if ($action === 'lists') {
|
||||
if ($method === 'GET') {
|
||||
$firstId = liste_ensure_default($pdo);
|
||||
$rows = $pdo->query("SELECT id, name, position FROM pf_lists ORDER BY position, id")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$rows = $pdo->query("SELECT id, name, color, list_type, position FROM pf_lists ORDER BY position, id")->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode(['lists' => $rows, 'default_id' => $firstId]);
|
||||
exit;
|
||||
}
|
||||
if ($method === 'POST') {
|
||||
$name = mb_substr(trim($body['name'] ?? ''), 0, 100);
|
||||
$name = mb_substr(trim($body['name'] ?? ''), 0, 100);
|
||||
$color = mb_substr(trim($body['color'] ?? ''), 0, 20) ?: null;
|
||||
$list_type = mb_substr(trim($body['list_type'] ?? ''), 0, 50) ?: null;
|
||||
if (!$name) { http_response_code(400); echo json_encode(['error' => 'name required']); exit; }
|
||||
$pos = (int)$pdo->query("SELECT COALESCE(MAX(position),0)+1 FROM pf_lists")->fetchColumn();
|
||||
$pdo->prepare("INSERT INTO pf_lists (name, position) VALUES (?,?)")->execute([$name, $pos]);
|
||||
echo json_encode(['id' => (int)$pdo->lastInsertId(), 'name' => $name, 'position' => $pos]);
|
||||
$pdo->prepare("INSERT INTO pf_lists (name, color, list_type, position) VALUES (?,?,?,?)")->execute([$name, $color, $list_type, $pos]);
|
||||
echo json_encode(['id' => (int)$pdo->lastInsertId(), 'name' => $name, 'color' => $color, 'list_type' => $list_type, 'position' => $pos]);
|
||||
exit;
|
||||
}
|
||||
if ($method === 'PUT') {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$name = mb_substr(trim($body['name'] ?? ''), 0, 100);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$name = mb_substr(trim($body['name'] ?? ''), 0, 100);
|
||||
$color = array_key_exists('color', $body) ? (mb_substr(trim($body['color'] ?? ''), 0, 20) ?: null) : false;
|
||||
$list_type = array_key_exists('list_type', $body) ? (mb_substr(trim($body['list_type'] ?? ''), 0, 50) ?: null) : false;
|
||||
if (!$id || !$name) { http_response_code(400); echo json_encode(['error' => 'invalid']); exit; }
|
||||
$pdo->prepare("UPDATE pf_lists SET name=? WHERE id=?")->execute([$name, $id]);
|
||||
$sets = ['name=?'];
|
||||
$vals = [$name];
|
||||
if ($color !== false) { $sets[] = 'color=?'; $vals[] = $color; }
|
||||
if ($list_type !== false) { $sets[] = 'list_type=?'; $vals[] = $list_type; }
|
||||
$vals[] = $id;
|
||||
$pdo->prepare("UPDATE pf_lists SET " . implode(', ', $sets) . " WHERE id=?")->execute($vals);
|
||||
echo json_encode(['ok' => true]); exit;
|
||||
}
|
||||
if ($method === 'DELETE') {
|
||||
|
||||
@@ -310,6 +310,135 @@
|
||||
}
|
||||
.liste-empty-icon { font-size: 3rem; margin-bottom: .75rem; }
|
||||
|
||||
/* ── List modal ────────────────────────────────────────────────────────────── */
|
||||
|
||||
.liste-modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,.45);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.liste-modal {
|
||||
background: var(--card-bg, #fff);
|
||||
border: 1px solid var(--border, #dee2e6);
|
||||
border-radius: 14px;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
.liste-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 1.25rem;
|
||||
border-bottom: 1px solid var(--border, #dee2e6);
|
||||
}
|
||||
.liste-modal-header h3 { margin: 0; font-size: .95rem; font-weight: 700; color: var(--text, #212529); }
|
||||
|
||||
.liste-modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.3rem;
|
||||
cursor: pointer;
|
||||
color: var(--muted, #6c757d);
|
||||
line-height: 1;
|
||||
padding: 0 .25rem;
|
||||
transition: color .15s;
|
||||
}
|
||||
.liste-modal-close:hover { color: var(--text, #212529); }
|
||||
|
||||
.liste-modal-body {
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .85rem;
|
||||
}
|
||||
|
||||
.liste-modal-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: .5rem;
|
||||
padding: .85rem 1.25rem;
|
||||
border-top: 1px solid var(--border, #dee2e6);
|
||||
}
|
||||
.liste-modal-footer .btn-tool-danger { margin-right: auto; }
|
||||
|
||||
.liste-form-group { display: flex; flex-direction: column; gap: .3rem; }
|
||||
.liste-form-label {
|
||||
font-size: .73rem;
|
||||
font-weight: 700;
|
||||
color: var(--muted, #6c757d);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
.liste-modal-name, .liste-modal-type {
|
||||
padding: .5rem .7rem;
|
||||
border: 1px solid var(--border, #dee2e6);
|
||||
border-radius: 8px;
|
||||
background: var(--input-bg, #fff);
|
||||
color: var(--text, #212529);
|
||||
font-size: .875rem;
|
||||
font-family: inherit;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
transition: border-color .15s;
|
||||
}
|
||||
.liste-modal-name:focus, .liste-modal-type:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary, #4361ee);
|
||||
box-shadow: 0 0 0 3px rgba(67,97,238,.15);
|
||||
}
|
||||
|
||||
.btn-tool-primary {
|
||||
background: var(--primary, #4361ee);
|
||||
color: #fff;
|
||||
border-color: var(--primary, #4361ee);
|
||||
}
|
||||
.btn-tool-primary:hover { filter: brightness(1.1); background: var(--primary, #4361ee); }
|
||||
|
||||
/* ── Color picker ──────────────────────────────────────────────────────────── */
|
||||
|
||||
.liste-color-picker {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .45rem;
|
||||
}
|
||||
|
||||
.liste-color-swatch {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
background: var(--swatch, transparent);
|
||||
cursor: pointer;
|
||||
transition: transform .1s;
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.liste-color-swatch:hover { transform: scale(1.2); }
|
||||
.liste-color-swatch.active {
|
||||
outline-color: var(--swatch, var(--primary, #4361ee));
|
||||
border-color: var(--card-bg, #fff);
|
||||
}
|
||||
.liste-color-swatch-none {
|
||||
background: transparent !important;
|
||||
border: 2px dashed var(--border, #dee2e6) !important;
|
||||
}
|
||||
.liste-color-swatch-none.active { outline-color: var(--primary, #4361ee); }
|
||||
|
||||
/* ── Tab type emoji ────────────────────────────────────────────────────────── */
|
||||
|
||||
.liste-tab-type { font-size: .85rem; line-height: 1; }
|
||||
|
||||
/* ── Category section headers ──────────────────────────────────────────────── */
|
||||
|
||||
.liste-cat-section-header {
|
||||
@@ -429,6 +558,13 @@
|
||||
[data-theme='dark'] .btn-tool:hover { background: #2a2a3e; }
|
||||
[data-theme='dark'] .liste-new-form input { background: #1e1e2e; color: #cdd6f4; border-color: #4361ee; }
|
||||
[data-theme='dark'] .liste-new-form #btn-cancel-new-list { background: #2a2a3e; color: #cdd6f4; }
|
||||
[data-theme='dark'] .liste-modal { background: #1e1e2e; border-color: #3a3a4e; }
|
||||
[data-theme='dark'] .liste-modal-header,
|
||||
[data-theme='dark'] .liste-modal-footer { border-color: #3a3a4e; }
|
||||
[data-theme='dark'] .liste-modal-header h3 { color: #cdd6f4; }
|
||||
[data-theme='dark'] .liste-modal-name,
|
||||
[data-theme='dark'] .liste-modal-type { background: #2a2a3e; border-color: #3a3a4e; color: #cdd6f4; }
|
||||
[data-theme='dark'] .liste-color-swatch.active { border-color: #1e1e2e; }
|
||||
[data-theme='dark'] .liste-cat-badge { border-color: #3a3a4e; }
|
||||
[data-theme='dark'] .liste-cat-badge:hover { background: #313155; border-color: #4361ee; }
|
||||
[data-theme='dark'] .liste-cat-section-count { background: #2a2a3e; color: #8892b0; }
|
||||
|
||||
+169
-66
@@ -11,6 +11,35 @@ const state = {
|
||||
catMap: {},
|
||||
};
|
||||
|
||||
// ── Constants ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const LIST_COLORS = [
|
||||
{ id: 'none', hex: null },
|
||||
{ id: 'blue', hex: '#3b82f6' },
|
||||
{ id: 'red', hex: '#ef4444' },
|
||||
{ id: 'amber', hex: '#f59e0b' },
|
||||
{ id: 'purple', hex: '#8b5cf6' },
|
||||
{ id: 'pink', hex: '#ec4899' },
|
||||
{ id: 'emerald', hex: '#10b981' },
|
||||
{ id: 'orange', hex: '#f97316' },
|
||||
{ id: 'cyan', hex: '#06b6d4' },
|
||||
{ id: 'indigo', hex: '#6366f1' },
|
||||
{ id: 'teal', hex: '#14b8a6' },
|
||||
{ id: 'gray', hex: '#6b7280' },
|
||||
];
|
||||
|
||||
const LIST_TYPES = [
|
||||
{ value: '', label: '— Aucun —', emoji: '' },
|
||||
{ value: 'courses', label: 'Courses', emoji: '🛒' },
|
||||
{ value: 'todo', label: 'To-do', emoji: '✅' },
|
||||
{ value: 'voyage', label: 'Voyage', emoji: '✈️' },
|
||||
{ value: 'travail', label: 'Travail', emoji: '💼' },
|
||||
{ value: 'maison', label: 'Maison', emoji: '🏠' },
|
||||
{ value: 'sante', label: 'Santé', emoji: '💊' },
|
||||
{ value: 'loisirs', label: 'Loisirs', emoji: '🎮' },
|
||||
{ value: 'autre', label: 'Autre', emoji: '📋' },
|
||||
];
|
||||
|
||||
// ── Utilities ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function esc(s) {
|
||||
@@ -42,6 +71,116 @@ async function api(action, method = 'GET', body = null, params = {}) {
|
||||
return r.json();
|
||||
}
|
||||
|
||||
// ── List modal ─────────────────────────────────────────────────────────────────
|
||||
|
||||
let activeModal = null;
|
||||
|
||||
function openListModal(listId = null) {
|
||||
if (activeModal) { activeModal.remove(); activeModal = null; }
|
||||
const list = listId ? state.lists.find(l => l.id === listId) : null;
|
||||
const isNew = !listId;
|
||||
|
||||
const backdrop = document.createElement('div');
|
||||
backdrop.className = 'liste-modal-backdrop';
|
||||
backdrop.innerHTML = `
|
||||
<div class="liste-modal" role="dialog" aria-modal="true">
|
||||
<div class="liste-modal-header">
|
||||
<h3>${isNew ? 'Nouvelle liste' : 'Modifier la liste'}</h3>
|
||||
<button class="liste-modal-close" aria-label="Fermer">×</button>
|
||||
</div>
|
||||
<div class="liste-modal-body">
|
||||
<div class="liste-form-group">
|
||||
<label class="liste-form-label">Nom</label>
|
||||
<input class="liste-modal-name" type="text" maxlength="100"
|
||||
value="${esc(list?.name ?? '')}" placeholder="Ma liste…" autocomplete="off">
|
||||
</div>
|
||||
<div class="liste-form-group">
|
||||
<label class="liste-form-label">Couleur</label>
|
||||
<div class="liste-color-picker">
|
||||
${LIST_COLORS.map(c => {
|
||||
const isActive = (c.hex === null && !list?.color) || (list?.color === c.hex);
|
||||
const cls = 'liste-color-swatch' + (c.hex === null ? ' liste-color-swatch-none' : '') + (isActive ? ' active' : '');
|
||||
const style = c.hex ? `style="--swatch:${c.hex}"` : '';
|
||||
return `<button class="${cls}" data-color="${c.hex ?? ''}" ${style} title="${c.id}"></button>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="liste-form-group">
|
||||
<label class="liste-form-label">Type de liste</label>
|
||||
<select class="liste-modal-type">
|
||||
${LIST_TYPES.map(t =>
|
||||
`<option value="${esc(t.value)}" ${list?.list_type === t.value ? 'selected' : ''}>${t.emoji ? t.emoji + ' ' : ''}${esc(t.label)}</option>`
|
||||
).join('')}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="liste-modal-footer">
|
||||
${!isNew && state.lists.length > 1
|
||||
? `<button class="btn-tool btn-tool-danger liste-modal-delete">Supprimer</button>`
|
||||
: ''}
|
||||
<button class="btn-tool liste-modal-cancel">Annuler</button>
|
||||
<button class="btn-tool btn-tool-primary liste-modal-save">Enregistrer</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(backdrop);
|
||||
activeModal = backdrop;
|
||||
|
||||
const nameInput = backdrop.querySelector('.liste-modal-name');
|
||||
nameInput.focus();
|
||||
nameInput.setSelectionRange(nameInput.value.length, nameInput.value.length);
|
||||
|
||||
// Color swatches
|
||||
backdrop.querySelectorAll('.liste-color-swatch').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
backdrop.querySelectorAll('.liste-color-swatch').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
const close = () => { backdrop.remove(); activeModal = null; };
|
||||
backdrop.querySelector('.liste-modal-close').addEventListener('click', close);
|
||||
backdrop.querySelector('.liste-modal-cancel').addEventListener('click', close);
|
||||
backdrop.addEventListener('click', e => { if (e.target === backdrop) close(); });
|
||||
|
||||
backdrop.querySelector('.liste-modal-delete')?.addEventListener('click', () => { close(); deleteList(listId); });
|
||||
backdrop.querySelector('.liste-modal-save').addEventListener('click', () => saveListModal(backdrop, listId));
|
||||
nameInput.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') saveListModal(backdrop, listId);
|
||||
if (e.key === 'Escape') close();
|
||||
});
|
||||
}
|
||||
|
||||
async function saveListModal(backdrop, listId) {
|
||||
const name = backdrop.querySelector('.liste-modal-name').value.trim();
|
||||
if (!name) { backdrop.querySelector('.liste-modal-name').focus(); return; }
|
||||
|
||||
const activeSwatch = backdrop.querySelector('.liste-color-swatch.active');
|
||||
const color = activeSwatch?.dataset.color || null;
|
||||
const list_type = backdrop.querySelector('.liste-modal-type').value || null;
|
||||
|
||||
if (listId) {
|
||||
const r = await api('lists', 'PUT', { name, color, list_type }, { id: listId });
|
||||
if (r.ok) {
|
||||
const list = state.lists.find(l => l.id === listId);
|
||||
if (list) { list.name = name; list.color = color; list.list_type = list_type; }
|
||||
backdrop.remove(); activeModal = null;
|
||||
renderTabs();
|
||||
toast('Liste mise à jour');
|
||||
}
|
||||
} else {
|
||||
const r = await api('lists', 'POST', { name, color, list_type });
|
||||
if (r.id) {
|
||||
state.lists.push({ id: r.id, name, color: r.color, list_type: r.list_type, position: r.position });
|
||||
state.currentListId = r.id;
|
||||
backdrop.remove(); activeModal = null;
|
||||
await loadItems();
|
||||
renderTabs();
|
||||
toast('Liste créée : ' + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Categories ─────────────────────────────────────────────────────────────────
|
||||
|
||||
async function loadCategories() {
|
||||
@@ -104,49 +243,54 @@ function closeCategoryPicker() {
|
||||
|
||||
// ── Tabs ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
function typeEmoji(list_type) {
|
||||
return LIST_TYPES.find(t => t.value === list_type)?.emoji || '';
|
||||
}
|
||||
|
||||
function renderTabs() {
|
||||
const container = document.getElementById('liste-tabs');
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
|
||||
state.lists.forEach(list => {
|
||||
const isActive = list.id === state.currentListId;
|
||||
const tab = document.createElement('div');
|
||||
tab.className = 'liste-tab' + (list.id === state.currentListId ? ' active' : '');
|
||||
tab.className = 'liste-tab' + (isActive ? ' active' : '');
|
||||
tab.dataset.id = list.id;
|
||||
|
||||
if (list.id === state.currentListId) {
|
||||
// Apply custom color to entire tab
|
||||
if (list.color) {
|
||||
if (isActive) {
|
||||
tab.style.cssText = `background:${list.color};border-color:${list.color};color:#fff`;
|
||||
} else {
|
||||
tab.style.cssText = `border-color:${list.color};color:${list.color}`;
|
||||
}
|
||||
}
|
||||
|
||||
const emoji = typeEmoji(list.list_type);
|
||||
const prefix = emoji ? `<span class="liste-tab-type">${emoji}</span>` : '';
|
||||
|
||||
if (isActive) {
|
||||
tab.innerHTML = `
|
||||
${prefix}
|
||||
<span class="liste-tab-name">${esc(list.name)}</span>
|
||||
<button class="liste-tab-btn liste-tab-rename" title="${esc(T.rename_list)}" data-id="${list.id}">✏</button>
|
||||
${state.lists.length > 1 ? `<button class="liste-tab-btn liste-tab-delete" title="Supprimer" data-id="${list.id}">×</button>` : ''}
|
||||
<button class="liste-tab-btn liste-tab-edit" title="Modifier" data-id="${list.id}">✏</button>
|
||||
`;
|
||||
} else {
|
||||
tab.innerHTML = `<span class="liste-tab-name">${esc(list.name)}</span>`;
|
||||
tab.innerHTML = `${prefix}<span class="liste-tab-name">${esc(list.name)}</span>`;
|
||||
tab.addEventListener('click', () => switchList(list.id));
|
||||
}
|
||||
container.appendChild(tab);
|
||||
});
|
||||
|
||||
container.querySelectorAll('.liste-tab-rename').forEach(btn => {
|
||||
btn.addEventListener('click', e => { e.stopPropagation(); startRename(parseInt(btn.dataset.id)); });
|
||||
});
|
||||
container.querySelectorAll('.liste-tab-delete').forEach(btn => {
|
||||
btn.addEventListener('click', e => { e.stopPropagation(); deleteList(parseInt(btn.dataset.id)); });
|
||||
container.querySelectorAll('.liste-tab-edit').forEach(btn => {
|
||||
btn.addEventListener('click', e => { e.stopPropagation(); openListModal(parseInt(btn.dataset.id)); });
|
||||
});
|
||||
}
|
||||
|
||||
function startRename(listId) {
|
||||
const list = state.lists.find(l => l.id === listId);
|
||||
if (!list) return;
|
||||
const name = prompt(T.new_name || 'Nouveau nom :', list.name);
|
||||
if (name === null || name.trim() === '') return;
|
||||
api('lists', 'PUT', { name: name.trim() }, { id: listId }).then(r => {
|
||||
if (r.ok) {
|
||||
list.name = name.trim();
|
||||
renderTabs();
|
||||
toast(T.list_renamed || 'Liste renommée');
|
||||
}
|
||||
});
|
||||
}
|
||||
// ── List management ─────────────────────────────────────────────────────────────
|
||||
|
||||
document.getElementById('btn-add-list')?.addEventListener('click', () => openListModal(null));
|
||||
|
||||
async function deleteList(listId) {
|
||||
if (!confirm(T.confirm_delete_list || 'Supprimer cette liste et tous ses articles ?')) return;
|
||||
@@ -161,41 +305,6 @@ async function deleteList(listId) {
|
||||
toast(T.list_deleted || 'Liste supprimée');
|
||||
}
|
||||
|
||||
// ── List management ─────────────────────────────────────────────────────────────
|
||||
|
||||
document.getElementById('btn-add-list')?.addEventListener('click', () => {
|
||||
document.getElementById('liste-new-form')?.classList.remove('hidden');
|
||||
document.getElementById('btn-add-list')?.classList.add('hidden');
|
||||
document.getElementById('new-list-name')?.focus();
|
||||
});
|
||||
document.getElementById('btn-cancel-new-list')?.addEventListener('click', cancelNewList);
|
||||
document.getElementById('btn-confirm-new-list')?.addEventListener('click', confirmNewList);
|
||||
document.getElementById('new-list-name')?.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') confirmNewList();
|
||||
if (e.key === 'Escape') cancelNewList();
|
||||
});
|
||||
|
||||
function cancelNewList() {
|
||||
document.getElementById('liste-new-form')?.classList.add('hidden');
|
||||
document.getElementById('btn-add-list')?.classList.remove('hidden');
|
||||
if (document.getElementById('new-list-name')) document.getElementById('new-list-name').value = '';
|
||||
}
|
||||
|
||||
async function confirmNewList() {
|
||||
const input = document.getElementById('new-list-name');
|
||||
const name = input?.value.trim();
|
||||
if (!name) return;
|
||||
const r = await api('lists', 'POST', { name });
|
||||
if (r.id) {
|
||||
state.lists.push({ id: r.id, name: r.name, position: r.position });
|
||||
state.currentListId = r.id;
|
||||
cancelNewList();
|
||||
await loadItems();
|
||||
renderTabs();
|
||||
toast((T.list_created || 'Liste créée') + ' : ' + r.name);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Switch list ─────────────────────────────────────────────────────────────────
|
||||
|
||||
async function switchList(listId) {
|
||||
@@ -207,10 +316,7 @@ async function switchList(listId) {
|
||||
// ── Items ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
async function loadItems() {
|
||||
if (!state.currentListId) {
|
||||
renderItems();
|
||||
return;
|
||||
}
|
||||
if (!state.currentListId) { renderItems(); return; }
|
||||
const r = await api('items', 'GET', null, { list_id: state.currentListId });
|
||||
state.items = r.items || [];
|
||||
renderItems();
|
||||
@@ -236,15 +342,12 @@ function renderItems() {
|
||||
let html = '';
|
||||
|
||||
if (hasCategories && state.categories.length) {
|
||||
// Group pending items by category
|
||||
const groups = new Map();
|
||||
pending.forEach(item => {
|
||||
const key = item.category_id ?? 0;
|
||||
if (!groups.has(key)) groups.set(key, []);
|
||||
groups.get(key).push(item);
|
||||
});
|
||||
|
||||
// Render in category order, uncategorized last
|
||||
const catIds = state.categories.map(c => parseInt(c.id));
|
||||
const orderedKeys = catIds.filter(id => groups.has(id));
|
||||
if (groups.has(0)) orderedKeys.push(0);
|
||||
@@ -284,7 +387,7 @@ function rowHtml(item) {
|
||||
const checked = item.in_cart ? 'checked' : '';
|
||||
const cls = item.in_cart ? 'liste-row in-cart' : 'liste-row';
|
||||
const cat = item.category_id ? state.catMap[item.category_id] : null;
|
||||
const badgeCls = 'liste-cat-badge' + (cat ? '' : ' liste-cat-badge-empty');
|
||||
const badgeCls = 'liste-cat-badge' + (cat ? '' : ' liste-cat-badge-empty');
|
||||
const badgeIcon = cat ? esc(cat.icon) : '🏷️';
|
||||
const badgeTip = cat ? esc(cat.name) : 'Non classé';
|
||||
return `<div class="${cls}" data-id="${item.id}">
|
||||
|
||||
Reference in New Issue
Block a user