Fix flat-rate coat wizard UX, — literal, select caret overlap, and walkthrough modal sizing
- Flat-rate items now default coat type to Custom so Color Name field is immediately visible - Catalog search blur copies typed text to Color Name when no catalog result was selected - Item card shows 'No color specified' badge when coat has powder-to-order but no color name - Color Name label marked required with '(shows on quote)' hint - Coat name select min-width prevents text overlapping Bootstrap caret arrow - Remove extra unbalanced </div> from renderSalesFields - Fix literal — in quote simple-mode hint (textContent → innerHTML) - Formula walkthrough modal fixed at 700px so all steps render at identical window size Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2284,7 +2284,7 @@
|
||||
<!-- Custom Formula Walkthrough Modal -->
|
||||
<div class="modal fade" id="cfWalkthroughModal" tabindex="-1" aria-labelledby="cfWalkthroughLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content" style="height:700px">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title" id="cfWalkthroughLabel">
|
||||
<i class="bi bi-calculator text-info me-2"></i>Custom Formula Templates — How It Works
|
||||
|
||||
@@ -590,7 +590,7 @@
|
||||
function applyMode(mode) {
|
||||
if (mode === 'simple') {
|
||||
form.classList.add('quote-simple-mode');
|
||||
hint.textContent = 'Advanced fields are hidden — switch to Full Quote to see them.';
|
||||
hint.innerHTML = 'Advanced fields are hidden — switch to Full Quote to see them.';
|
||||
} else {
|
||||
form.classList.remove('quote-simple-mode');
|
||||
hint.textContent = '';
|
||||
|
||||
@@ -209,11 +209,20 @@ function wizardNext() {
|
||||
if (wz.step === 1) {
|
||||
wz.step = 2;
|
||||
} else if (wz.step === 2) {
|
||||
// Generic / labor / sales: step 2 is the last step
|
||||
if (wz.itemType === 'generic' || wz.itemType === 'labor' || wz.itemType === 'sales') {
|
||||
// Labor / sales: step 2 is the last step
|
||||
if (wz.itemType === 'labor' || wz.itemType === 'sales') {
|
||||
wizardSave();
|
||||
return;
|
||||
}
|
||||
// Flat-Rate Charge: go to step 3 only if user opted in to specifying coatings
|
||||
if (wz.itemType === 'generic') {
|
||||
if (wz.data.genericHasCoatings) {
|
||||
wz.step = 3;
|
||||
} else {
|
||||
wizardSave();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// AI: step 2 must be accepted before proceeding
|
||||
if (wz.itemType === 'ai') {
|
||||
if (!wz.ai.accepted) {
|
||||
@@ -223,6 +232,11 @@ function wizardNext() {
|
||||
}
|
||||
wz.step = 3;
|
||||
} else if (wz.step === 3) {
|
||||
// Flat-Rate Charge with coatings stops here — no prep or catalog-save step
|
||||
if (wz.itemType === 'generic') {
|
||||
wizardSave();
|
||||
return;
|
||||
}
|
||||
// Step 4 (prep services) for product, calculated, and ai items — always shown
|
||||
// so users with no prep services configured see the empty-state prompt
|
||||
wz.step = 4;
|
||||
@@ -615,6 +629,18 @@ function renderGenericFields() {
|
||||
<label class="form-label fw-semibold">Notes <small class="text-muted">(optional)</small></label>
|
||||
<input type="text" class="form-control" id="wz_notes" placeholder="Internal notes about this charge…" maxlength="500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center border rounded py-2 px-3 mt-3 gap-2">
|
||||
<div class="form-check form-switch mb-0 flex-shrink-0">
|
||||
<input class="form-check-input" type="checkbox" id="wz_genericHasCoatings"
|
||||
${wz.data.genericHasCoatings ? 'checked' : ''}
|
||||
onchange="wz.data.genericHasCoatings = this.checked; updateStepDots(); updateWizardButtons();"
|
||||
style="cursor:pointer">
|
||||
</div>
|
||||
<label for="wz_genericHasCoatings" class="mb-0" style="cursor:pointer; line-height:1.3">
|
||||
<strong>Specify powder coating</strong>
|
||||
<span class="d-block text-muted fw-normal small">Add coat & color specs for powder ordering and tracking (price stays flat)</span>
|
||||
</label>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -1795,7 +1821,7 @@ function addCoatRow() {
|
||||
const coats = wz.data.coats = wz.data.coats || [];
|
||||
const i = coats.length;
|
||||
const defaultName = i === 0 ? 'Base Coat' : '';
|
||||
const coat = { coatName: defaultName, sequence: i + 1, inventoryItemId: null, powderType: 'stock' };
|
||||
const coat = { coatName: defaultName, sequence: i + 1, inventoryItemId: null, powderType: wz.itemType === 'generic' ? 'custom' : 'stock' };
|
||||
coats.push(coat);
|
||||
|
||||
const container = document.getElementById('coatsListContainer');
|
||||
@@ -1816,7 +1842,7 @@ function buildCoatNameHtml(i, currentName) {
|
||||
`<option value="${n}"${selectVal === n ? ' selected' : ''}>${n}</option>`
|
||||
).join('');
|
||||
return `
|
||||
<select class="form-select form-select-sm" style="max-width:160px"
|
||||
<select class="form-select form-select-sm" style="min-width:150px;max-width:200px"
|
||||
id="coat_name_sel_${i}" onchange="onCoatNameSelect(${i})">
|
||||
<option value="">-- Select --</option>
|
||||
${options}
|
||||
@@ -1932,6 +1958,7 @@ function buildCoatRowHtml(i, coat) {
|
||||
placeholder="Lookup from catalog (color name or SKU)…"
|
||||
oninput="customPowderCatalogInput(${i})"
|
||||
onkeydown="if(event.key==='Escape'){customPowderCatalogClose(${i})}"
|
||||
onblur="catalogQBlur(${i})"
|
||||
autocomplete="off">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="customPowderCatalogClose(${i})" title="Clear lookup">
|
||||
<i class="bi bi-x" style="font-size:.8rem;"></i>
|
||||
@@ -1945,8 +1972,8 @@ function buildCoatRowHtml(i, coat) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label form-label-sm">Color Name</label>
|
||||
<input type="text" class="form-control form-control-sm" id="coat_colorName_${i}" value="${escHtml(coat.colorName || '')}" placeholder="e.g., Gloss Black">
|
||||
<label class="form-label form-label-sm fw-semibold">Color Name <span class="text-danger">*</span> <small class="text-muted fw-normal">(shows on quote)</small></label>
|
||||
<input type="text" class="form-control form-control-sm" id="coat_colorName_${i}" value="${escHtml(coat.colorName || '')}" placeholder="e.g., Gloss Black, Matte Red…" required>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label class="form-label form-label-sm">Color Code</label>
|
||||
@@ -2265,6 +2292,19 @@ function customPowderCatalogClose(i) {
|
||||
if (qEl) qEl.value = '';
|
||||
}
|
||||
|
||||
// When the catalog search loses focus without a catalog result being selected,
|
||||
// copy whatever the user typed into Color Name (if Color Name is still empty).
|
||||
// This handles the common case of typing a color name in the search, not finding
|
||||
// a catalog match, and expecting the typed text to become the color name.
|
||||
function catalogQBlur(i) {
|
||||
const q = document.getElementById(`coat_catalog_q_${i}`);
|
||||
const catId = document.getElementById(`coat_custom_catalogItemId_${i}`);
|
||||
const cn = document.getElementById(`coat_colorName_${i}`);
|
||||
if (q && !catId?.value && q.value.trim() && cn && !cn.value.trim()) {
|
||||
cn.value = q.value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
function applyCustomCatalogResult(i, r) {
|
||||
customPowderCatalogClose(i);
|
||||
// Fill in the custom fields from the catalog result
|
||||
@@ -2967,9 +3007,12 @@ function buildCardHtml(item, i) {
|
||||
const orderBadge = (!c.inventoryItemId && c.powderToOrder)
|
||||
? ` <span class="badge bg-warning text-dark" style="font-size:.65em;vertical-align:middle;" title="Custom powder — must be purchased before coating"><i class="bi bi-cart me-1"></i>ORDER ${parseFloat(c.powderToOrder).toFixed(2)} lbs</span>`
|
||||
: '';
|
||||
const noColorHint = (!color && !c.inventoryItemId && c.powderToOrder)
|
||||
? ` <span class="badge bg-danger" style="font-size:.65em;vertical-align:middle;" title="Edit this item and enter a Color Name for the coat">No color specified</span>`
|
||||
: '';
|
||||
const coatNotes = c.notes ? `<span class="fst-italic ms-1 opacity-75">— ${escHtml(c.notes)}</span>` : '';
|
||||
return `<div style="font-size:.8rem;" class="text-muted mt-1">
|
||||
<i class="bi bi-layers me-1"></i><span class="fw-semibold">${escHtml(c.coatName || 'Coat')}</span>${color ? ` — ${color}${code}` : ''}${orderBadge}${coatNotes}
|
||||
<i class="bi bi-layers me-1"></i><span class="fw-semibold">${escHtml(c.coatName || 'Coat')}</span>${color ? ` — ${color}${code}` : ''}${orderBadge}${noColorHint}${coatNotes}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
@@ -3352,7 +3395,9 @@ function updateWizardButtons() {
|
||||
if (!btnBack || !btnNext || !btnSave) return;
|
||||
|
||||
const isLast = (wz.step === 4 && wz.itemType !== 'calculated' && wz.itemType !== 'ai')
|
||||
|| (wz.step === 2 && (wz.itemType === 'generic' || wz.itemType === 'labor' || wz.itemType === 'sales'));
|
||||
|| (wz.step === 3 && wz.itemType === 'generic' && wz.data.genericHasCoatings)
|
||||
|| (wz.step === 2 && (wz.itemType === 'labor' || wz.itemType === 'sales'))
|
||||
|| (wz.step === 2 && wz.itemType === 'generic' && !wz.data.genericHasCoatings);
|
||||
const isCatalogStep = wz.step === 5;
|
||||
|
||||
btnBack.classList.toggle('d-none', wz.step === 1);
|
||||
@@ -3369,7 +3414,8 @@ function updateStepDots() {
|
||||
const step2Line = document.getElementById('step2Line');
|
||||
const step3Line = document.getElementById('step3Line');
|
||||
|
||||
const hasCoats = wz.itemType !== 'generic' && wz.itemType !== 'labor' && wz.itemType !== 'sales';
|
||||
const hasCoats = wz.itemType !== 'labor' && wz.itemType !== 'sales'
|
||||
&& !(wz.itemType === 'generic' && !wz.data.genericHasCoatings);
|
||||
|
||||
if (step3Dot) step3Dot.classList.toggle('skip', !hasCoats);
|
||||
if (step4Dot) step4Dot.classList.toggle('skip', !hasCoats);
|
||||
@@ -3383,7 +3429,9 @@ function updateStepDots() {
|
||||
else if (s < wz.step) dot.classList.add('done');
|
||||
});
|
||||
|
||||
const totalSteps = hasCoats ? 4 : 2;
|
||||
// Flat-Rate + coatings stops at step 3 (no prep or catalog save); all others follow normal flow
|
||||
const totalSteps = (wz.itemType === 'generic' && wz.data.genericHasCoatings) ? 3
|
||||
: hasCoats ? 4 : 2;
|
||||
const currentDisplay = Math.min(wz.step, totalSteps);
|
||||
const label = document.getElementById('wizardStepLabel');
|
||||
if (label) label.textContent = wz.step === 5 ? 'Final Step' : `Step ${currentDisplay} of ${totalSteps}`;
|
||||
|
||||
Reference in New Issue
Block a user