Add detailed modifier view with source tracking

Backend (analyzeMenuImages.cfm):
- Track which image each modifier was found on via sourceImageIndex
- Changed loop to use index to capture image number

Frontend (setup-wizard.html):
- Show detailed modifier information in expandable cards
- Display source image number for each modifier
- Show appliesTo status (category/uncertain) with badges
- Display all modifier options with prices in detail view
- Click modifier header to expand/collapse full option list
- Added comprehensive CSS for new modifier display:
  * Expandable card design with hover effects
  * Color-coded badges for source image, category assignment
  * Detailed option list with prices
  * Visual distinction for uncertain modifiers

Users can now:
- See which image each modifier came from
- View complete option list before confirming
- Understand how each modifier will be applied
- Make informed decisions about which modifiers to keep

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-01-15 16:02:03 -08:00
parent 0f36874435
commit 8999805ec6
2 changed files with 148 additions and 18 deletions

View file

@ -223,7 +223,8 @@
<!--- 3. Merge modifiers (dedupe by name) ---> <!--- 3. Merge modifiers (dedupe by name) --->
<cfset modifierMap = structNew()> <cfset modifierMap = structNew()>
<cfloop array="#allResults#" index="result"> <cfloop from="1" to="#arrayLen(allResults)#" index="resultIndex">
<cfset result = allResults[resultIndex]>
<cfif structKeyExists(result, "modifiers") AND isArray(result.modifiers)> <cfif structKeyExists(result, "modifiers") AND isArray(result.modifiers)>
<cfloop array="#result.modifiers#" index="mod"> <cfloop array="#result.modifiers#" index="mod">
<cfif isStruct(mod) AND structKeyExists(mod, "name") AND len(trim(mod.name))> <cfif isStruct(mod) AND structKeyExists(mod, "name") AND len(trim(mod.name))>
@ -234,6 +235,7 @@
<cfset normalizedMod["name"] = trim(mod.name)> <cfset normalizedMod["name"] = trim(mod.name)>
<cfset normalizedMod["required"] = structKeyExists(mod, "required") AND mod.required EQ true> <cfset normalizedMod["required"] = structKeyExists(mod, "required") AND mod.required EQ true>
<cfset normalizedMod["appliesTo"] = structKeyExists(mod, "appliesTo") ? mod.appliesTo : "uncertain"> <cfset normalizedMod["appliesTo"] = structKeyExists(mod, "appliesTo") ? mod.appliesTo : "uncertain">
<cfset normalizedMod["sourceImageIndex"] = resultIndex>
<cfif structKeyExists(mod, "categoryName") AND len(trim(mod.categoryName))> <cfif structKeyExists(mod, "categoryName") AND len(trim(mod.categoryName))>
<cfset normalizedMod["categoryName"] = trim(mod.categoryName)> <cfset normalizedMod["categoryName"] = trim(mod.categoryName)>
</cfif> </cfif>

View file

@ -291,27 +291,117 @@
gap: 12px; gap: 12px;
padding: 12px 16px; padding: 12px 16px;
background: #fff; background: #fff;
border-bottom: 1px solid var(--gray-200);
cursor: pointer; cursor: pointer;
transition: background 0.2s;
}
.modifier-header:hover {
background: var(--gray-50);
} }
.modifier-header input[type="checkbox"] { .modifier-header input[type="checkbox"] {
width: 18px; width: 18px;
height: 18px; height: 18px;
flex-shrink: 0;
}
.modifier-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
.modifier-name-row {
display: flex;
align-items: center;
gap: 12px;
} }
.modifier-name { .modifier-name {
flex: 1;
font-weight: 600; font-weight: 600;
color: var(--gray-800); color: var(--gray-800);
font-size: 15px;
} }
.modifier-type { .modifier-type {
font-size: 12px; font-size: 11px;
color: var(--gray-500); color: var(--gray-500);
background: var(--gray-100); background: var(--gray-100);
padding: 2px 8px; padding: 2px 8px;
border-radius: 10px; border-radius: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.modifier-meta {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.source-badge, .applies-to-badge, .options-count {
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
background: var(--gray-100);
color: var(--gray-600);
}
.source-badge {
background: rgba(99, 102, 241, 0.1);
color: var(--primary);
}
.applies-to-badge {
background: rgba(34, 197, 94, 0.1);
color: var(--success);
}
.applies-to-badge.uncertain {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
.expand-icon {
font-size: 12px;
color: var(--gray-400);
transition: transform 0.2s;
flex-shrink: 0;
}
.modifier-details {
border-top: 1px solid var(--gray-200);
background: var(--gray-50);
}
.modifier-options-list {
padding: 12px 16px 12px 46px;
display: flex;
flex-direction: column;
gap: 6px;
}
.modifier-option-detail {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: #fff;
border: 1px solid var(--gray-200);
border-radius: 4px;
}
.option-name {
font-size: 14px;
color: var(--gray-700);
}
.option-price {
font-size: 13px;
color: var(--success);
font-weight: 500;
} }
.modifier-options { .modifier-options {
@ -1432,22 +1522,47 @@
return; return;
} }
let modifiersHtml = modifiers.map((mod, i) => ` let modifiersHtml = modifiers.map((mod, i) => {
<div class="modifier-template"> const sourceImg = mod.sourceImageIndex ? `Image ${mod.sourceImageIndex}` : 'Unknown source';
<div class="modifier-header"> const appliesToInfo = mod.appliesTo === 'category' && mod.categoryName
<input type="checkbox" checked data-index="${i}"> ? `<span class="applies-to-badge">Applies to: ${mod.categoryName}</span>`
: mod.appliesTo === 'uncertain'
? '<span class="applies-to-badge uncertain">Uncertain application</span>'
: '';
const optionsCount = (mod.options || []).length;
const optionsList = (mod.options || []).filter(opt => opt && opt.name).map(opt => `
<div class="modifier-option-detail">
<span class="option-name">${opt.name}</span>
<span class="option-price">${opt.price ? `+$${opt.price.toFixed(2)}` : '$0.00'}</span>
</div>
`).join('');
return `
<div class="modifier-template" data-index="${i}">
<div class="modifier-header" onclick="toggleModifierDetails(${i})">
<input type="checkbox" checked data-index="${i}" onclick="event.stopPropagation()">
<div class="modifier-info">
<div class="modifier-name-row">
<span class="modifier-name">${mod.name || 'Unnamed'}</span> <span class="modifier-name">${mod.name || 'Unnamed'}</span>
<span class="modifier-type">${mod.required ? 'Required' : 'Optional'}</span> <span class="modifier-type">${mod.required ? 'Required' : 'Optional'}</span>
</div> </div>
<div class="modifier-options"> <div class="modifier-meta">
${(mod.options || []).filter(opt => opt && opt.name).map(opt => ` <span class="source-badge">${sourceImg}</span>
<span class="modifier-option"> ${appliesToInfo}
${opt.name}${opt.price ? `<span class="price">+$${opt.price.toFixed(2)}</span>` : ''} <span class="options-count">${optionsCount} option${optionsCount !== 1 ? 's' : ''}</span>
</span>
`).join('')}
</div> </div>
</div> </div>
`).join(''); <span class="expand-icon"></span>
</div>
<div class="modifier-details" id="mod-details-${i}" style="display: none;">
<div class="modifier-options-list">
${optionsList}
</div>
</div>
</div>
`;
}).join('');
addMessage('ai', ` addMessage('ai', `
<p>I found <strong>${modifiers.length} modifier templates</strong> that can be applied to menu items:</p> <p>I found <strong>${modifiers.length} modifier templates</strong> that can be applied to menu items:</p>
@ -1466,6 +1581,19 @@
`); `);
} }
function toggleModifierDetails(index) {
const details = document.getElementById(`mod-details-${index}`);
const icon = details.previousElementSibling.querySelector('.expand-icon');
if (details.style.display === 'none') {
details.style.display = 'block';
icon.textContent = '▲';
} else {
details.style.display = 'none';
icon.textContent = '▼';
}
}
function confirmModifiers() { function confirmModifiers() {
const list = document.getElementById('modifiersList'); const list = document.getElementById('modifiersList');
const templates = list.querySelectorAll('.modifier-template'); const templates = list.querySelectorAll('.modifier-template');