Add Show Modifiers toggle to menu builder outline view

Adds button in the outline modal to toggle modifier group display
under each item. Each group shows a + to expand into full options
with prices. Useful for debugging modifier assignments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-03-03 19:57:22 -08:00
parent 9000fc91fe
commit 2d3634693b

View file

@ -3391,15 +3391,14 @@
// Show outline modal - full menu in hierarchical text format // Show outline modal - full menu in hierarchical text format
showOutlineModal() { showOutlineModal() {
document.getElementById('modalTitle').textContent = 'Menu Outline'; document.getElementById('modalTitle').textContent = 'Menu Outline';
this._outlineShowMods = false;
let outline = ''; const buildOutline = () => {
const categories = this.menu.categories || []; const categories = this.menu.categories || [];
if (categories.length === 0)
if (categories.length === 0) { return '<div style="color: var(--gray-500); text-align: center; padding: 40px;">No menu items yet</div>';
outline = '<div style="color: var(--gray-500); text-align: center; padding: 40px;">No menu items yet</div>';
} else {
outline = '<div class="menu-outline">';
let outline = '<div class="menu-outline">';
const topLevel = categories.filter(c => !c.parentCategoryId || c.parentCategoryId === 0); const topLevel = categories.filter(c => !c.parentCategoryId || c.parentCategoryId === 0);
const getSubcats = (parent) => categories.filter(c => const getSubcats = (parent) => categories.filter(c =>
c.parentCategoryId === parent.id || (parent.dbId && c.parentCategoryDbId === parent.dbId) c.parentCategoryId === parent.id || (parent.dbId && c.parentCategoryDbId === parent.dbId)
@ -3410,7 +3409,9 @@
for (const item of (items || [])) { for (const item of (items || [])) {
const itemPrice = item.price ? `$${parseFloat(item.price).toFixed(2)}` : ''; const itemPrice = item.price ? `$${parseFloat(item.price).toFixed(2)}` : '';
html += `<div class="outline-item" style="padding-left: ${indentLevel * 20}px;">${this.escapeHtml(item.name)}${itemPrice ? ' <span class="outline-price">' + itemPrice + '</span>' : ''}</div>`; html += `<div class="outline-item" style="padding-left: ${indentLevel * 20}px;">${this.escapeHtml(item.name)}${itemPrice ? ' <span class="outline-price">' + itemPrice + '</span>' : ''}</div>`;
html += this.renderOutlineModifiers(item.modifiers || [], indentLevel + 1); if (this._outlineShowMods) {
html += this.renderOutlineModifierGroups(item.modifiers || [], indentLevel + 1);
}
} }
return html; return html;
}; };
@ -3418,24 +3419,37 @@
for (const cat of topLevel) { for (const cat of topLevel) {
outline += `<div class="outline-category">${this.escapeHtml(cat.name)}</div>`; outline += `<div class="outline-category">${this.escapeHtml(cat.name)}</div>`;
outline += renderOutlineItems(cat.items, 1); outline += renderOutlineItems(cat.items, 1);
const subcats = getSubcats(cat); const subcats = getSubcats(cat);
for (const subcat of subcats) { for (const subcat of subcats) {
outline += `<div class="outline-subcategory">${this.escapeHtml(subcat.name)}</div>`; outline += `<div class="outline-subcategory">${this.escapeHtml(subcat.name)}</div>`;
outline += renderOutlineItems(subcat.items, 2); outline += renderOutlineItems(subcat.items, 2);
} }
} }
outline += '</div>'; outline += '</div>';
} return outline;
};
document.getElementById('modalBody').innerHTML = ` const render = () => {
<style> const btnLabel = this._outlineShowMods ? 'Hide Modifiers' : 'Show Modifiers';
document.getElementById('modalBody').innerHTML = `
<div style="margin-bottom: 12px;">
<button class="outline-mods-btn" onclick="MenuBuilder.toggleOutlineMods()">${btnLabel}</button>
</div>
${buildOutline()}
`;
};
// Inject styles once
document.getElementById('modalBody').innerHTML = '';
if (!document.getElementById('outlineStyles')) {
const style = document.createElement('style');
style.id = 'outlineStyles';
style.textContent = `
.menu-outline { .menu-outline {
font-family: monospace; font-family: monospace;
font-size: 13px; font-size: 13px;
line-height: 1.6; line-height: 1.6;
max-height: 60vh; max-height: 55vh;
overflow-y: auto; overflow-y: auto;
padding: 16px; padding: 16px;
background: var(--bg-secondary); background: var(--bg-secondary);
@ -3449,9 +3463,7 @@
margin-bottom: 4px; margin-bottom: 4px;
text-transform: uppercase; text-transform: uppercase;
} }
.outline-category:first-child { .outline-category:first-child { margin-top: 0; }
margin-top: 0;
}
.outline-subcategory { .outline-subcategory {
font-weight: 600; font-weight: 600;
font-size: 13px; font-size: 13px;
@ -3464,21 +3476,72 @@
padding-left: 20px; padding-left: 20px;
color: var(--text-primary); color: var(--text-primary);
} }
.outline-modifier { .outline-modifier { color: var(--text-muted); }
.outline-price { color: var(--success); }
.outline-modifier .outline-price { color: var(--warning); }
.outline-mods-btn {
padding: 6px 14px;
font-size: 13px;
border: 1px solid var(--border-color, #ccc);
border-radius: 6px;
background: var(--bg-primary, #fff);
color: var(--text-secondary);
cursor: pointer;
}
.outline-mods-btn:hover { background: var(--bg-secondary, #f5f5f5); }
.outline-mod-group {
padding-left: 20px;
color: var(--text-muted); color: var(--text-muted);
cursor: pointer;
user-select: none;
} }
.outline-price { .outline-mod-group:hover { color: var(--text-secondary); }
color: var(--success); .outline-mod-expand { display: inline-block; width: 14px; font-size: 11px; color: var(--text-muted); }
} .outline-mod-opts { display: none; }
.outline-modifier .outline-price { .outline-mod-opts.open { display: block; }
color: var(--warning); `;
} document.head.appendChild(style);
</style> }
${outline}
`; this._outlineRender = render;
render();
this.showModal(); this.showModal();
}, },
toggleOutlineMods() {
this._outlineShowMods = !this._outlineShowMods;
if (this._outlineRender) this._outlineRender();
},
// Render first-level modifier groups with expandable options
renderOutlineModifierGroups(modifiers, depth) {
if (!modifiers || modifiers.length === 0) return '';
let html = '';
const pad = depth * 20;
for (let i = 0; i < modifiers.length; i++) {
const mod = modifiers[i];
const modPrice = mod.price ? `+$${parseFloat(mod.price).toFixed(2)}` : '';
const opts = mod.options || [];
const uid = 'om_' + Math.random().toString(36).substring(2, 8);
const countLabel = opts.length ? ` (${opts.length})` : '';
if (opts.length > 0) {
html += `<div class="outline-mod-group" style="padding-left: ${pad}px;" onclick="var el=document.getElementById('${uid}');el.classList.toggle('open');this.querySelector('.outline-mod-expand').textContent=el.classList.contains('open')?'':'+'">`;
html += `<span class="outline-mod-expand">+</span> ${this.escapeHtml(mod.name)}${modPrice ? ' <span class="outline-price">' + modPrice + '</span>' : ''}${countLabel}`;
html += `</div>`;
html += `<div id="${uid}" class="outline-mod-opts">`;
for (const opt of opts) {
const optPrice = opt.price ? `+$${parseFloat(opt.price).toFixed(2)}` : '';
html += `<div class="outline-modifier" style="padding-left: ${(depth + 1) * 20}px;">${this.escapeHtml(opt.name)}${optPrice ? ' <span class="outline-price">' + optPrice + '</span>' : ''}</div>`;
}
html += `</div>`;
} else {
html += `<div class="outline-modifier" style="padding-left: ${pad}px;">${this.escapeHtml(mod.name)}${modPrice ? ' <span class="outline-price">' + modPrice + '</span>' : ''}</div>`;
}
}
return html;
},
// Helper to render modifiers recursively for outline // Helper to render modifiers recursively for outline
renderOutlineModifiers(modifiers, depth) { renderOutlineModifiers(modifiers, depth) {
if (!modifiers || modifiers.length === 0) return ''; if (!modifiers || modifiers.length === 0) return '';