// "💡 Suggestions" modal for the checklist card — fetches the deterministic // packing advice (GET .../checklist/suggestions), lets the user tick which // ones to add and whether they go in as shared or personal items, then bulk // adds them (POST .../checklist/suggestions). Kept as its own module so // checklist.js stays small (mirrors dayEditor.js's costForm/waypoints split). import { api } from '../api.js'; import { el, mount, loading, errorBox, toast } from '../dom.js'; export function openSuggestionsModal(tctx, onAdded) { const overlay = el('div', { class: 'overlay overlay-center' }); const modal = el('div', { class: 'modal', role: 'dialog', 'aria-modal': 'true' }); overlay.appendChild(modal); document.body.appendChild(overlay); document.body.classList.add('no-scroll'); function close() { document.body.classList.remove('no-scroll'); overlay.remove(); document.removeEventListener('keydown', onKey); } function onKey(e) { if (e.key === 'Escape') close(); } document.addEventListener('keydown', onKey); overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); }); load(); async function load() { mount(modal, modalHead(), loading('Loading suggestions…')); try { const data = await api.checklist.suggestions(tctx.tripId); draw(data.suggestions || []); } catch (err) { mount(modal, modalHead(), errorBox(err.message, load)); } } function modalHead() { return el( 'div', { class: 'slideover-head' }, el('h2', {}, '💡 Packing suggestions'), el('button', { class: 'icon-btn', type: 'button', title: 'Close', onClick: close }, '×'), ); } function draw(suggestions) { let scope = 'shared'; // 'shared' | 'personal' — applies to the whole batch const checks = new Map(); // suggestion key -> its (enabled) checkbox const groups = new Map(); for (const s of suggestions) { if (!groups.has(s.category)) groups.set(s.category, []); groups.get(s.category).push(s); } const listEl = el('div', { class: 'suggest-list' }); if (!suggestions.length) { listEl.appendChild(el('p', { class: 'muted' }, 'No suggestions yet — add some entries to the trip first.')); } for (const [category, items] of groups) { const rows = el('div', { class: 'suggest-rows' }); for (const s of items) { rows.appendChild(suggestionRow(s, checks)); } listEl.appendChild(el('div', { class: 'suggest-category' }, el('h3', {}, category), rows)); } const sharedRadio = el('input', { type: 'radio', name: 'suggest-scope', checked: true }); const personalRadio = el('input', { type: 'radio', name: 'suggest-scope' }); sharedRadio.addEventListener('change', () => { if (sharedRadio.checked) scope = 'shared'; }); personalRadio.addEventListener('change', () => { if (personalRadio.checked) scope = 'personal'; }); const scopeRow = el( 'div', { class: 'suggest-scope' }, el('label', {}, sharedRadio, 'Shared'), el('label', {}, personalRadio, '🔒 Personal to me'), ); const errorEl = el('p', { class: 'form-error' }); const cancelBtn = el('button', { class: 'btn btn-ghost', type: 'button', onClick: close }, 'Cancel'); const addBtn = el('button', { class: 'btn btn-primary', type: 'button', onClick: onAddSelected }, 'Add selected'); async function onAddSelected() { errorEl.textContent = ''; const keys = [...checks.entries()].filter(([, cb]) => cb.checked).map(([key]) => key); if (!keys.length) return (errorEl.textContent = 'Pick at least one suggestion.'); addBtn.disabled = true; addBtn.textContent = 'Adding…'; try { await api.checklist.addSuggestions(tctx.tripId, keys, scope === 'personal'); toast(`Added ${keys.length} item${keys.length === 1 ? '' : 's'}`, 'success'); close(); await onAdded(); } catch (err) { errorEl.textContent = err.message; addBtn.disabled = false; addBtn.textContent = 'Add selected'; } } mount(modal, modalHead(), listEl, suggestions.length ? scopeRow : null, errorEl, el('div', { class: 'form-actions' }, cancelBtn, addBtn)); } // One suggestion row: checkbox (ticked + disabled if already added), text + // qty, and the reason as muted subtext. Registers its checkbox in `checks` // only when selectable (not already added), for onAddSelected to read. function suggestionRow(s, checks) { const disabled = !!s.added; const check = el('input', { type: 'checkbox', class: 'suggest-check', checked: disabled, disabled, }); if (!disabled) checks.set(s.key, check); return el( 'label', { class: `suggest-row${disabled ? ' added' : ''}` }, check, el( 'div', { class: 'suggest-body' }, el('span', { class: 'suggest-text' }, s.text, s.qty != null ? el('span', { class: 'suggest-qty' }, `×${s.qty}`) : null), el('div', { class: 'suggest-reason muted' }, s.reason), ), ); } }