Fix garbled encoding symbols in oven display, bill/invoice tooltips, and profile timezone dropdown
- Replace mojibake × with × in oven batch cost row across Jobs Create/Edit/EditItems and Quotes Create/Edit - Fix Qty × Unit Price tooltip in Bills/Edit and Invoices/Edit - Fix all â€" (garbled em dash) and São Paulo in Profile timezone dropdown option labels Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -51,7 +51,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right"
|
||||
data-bs-title="Customer vs Prospect/Walk-In"
|
||||
data-bs-content="Choose <strong>Existing Customer</strong> if this person is already in your system. Choose <strong>New Prospect/Walk-In</strong> if they haven't committed yet — their details stay on the quote. When they approve, you can convert them to a full customer record with one click.<br><br><a href='/Help/Quotes#prospect-conversion' target='_blank'>Learn more →</a>">
|
||||
data-bs-content="Choose <strong>Existing Customer</strong> if this person is already in your system. Choose <strong>New Prospect/Walk-In</strong> if they haven't committed yet — their details stay on the quote. When they approve, you can convert them to a full customer record with one click.<br><br><a href='/Help/Quotes#prospect-conversion' target='_blank'>Learn more →</a>">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h5>
|
||||
@@ -146,7 +146,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right"
|
||||
data-bs-title="Quote Information"
|
||||
data-bs-content="Set the quote date, expiration, and any internal notes. The <strong>Expiration Date</strong> is shown to the customer — once it passes the quote is flagged Expired and can no longer be approved without editing. The <strong>Customer PO</strong> field is optional — use it if the customer provides their own purchase order number.<br><br><a href='/Help/Quotes#quote-statuses' target='_blank'>Learn more →</a>">
|
||||
data-bs-content="Set the quote date, expiration, and any internal notes. The <strong>Expiration Date</strong> is shown to the customer — once it passes the quote is flagged Expired and can no longer be approved without editing. The <strong>Customer PO</strong> field is optional — use it if the customer provides their own purchase order number.<br><br><a href='/Help/Quotes#quote-statuses' target='_blank'>Learn more →</a>">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h5>
|
||||
@@ -210,7 +210,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right"
|
||||
data-bs-title="Oven & Batch Pricing"
|
||||
data-bs-content="The oven cost is charged once per batch at the quote level, not per item. Estimate how many oven loads the full job will fill — for example, if you have 20 small parts and your oven fits 10, that's 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven's hourly rate in Settings.">
|
||||
data-bs-content="The oven cost is charged once per batch at the quote level, not per item. Estimate how many oven loads the full job will fill — for example, if you have 20 small parts and your oven fits 10, that's 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven's hourly rate in Settings.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h5>
|
||||
@@ -253,7 +253,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right"
|
||||
data-bs-title="Quote Item Types"
|
||||
data-bs-content="<strong>Calculated</strong> — you enter surface area (sq ft) and the system prices it using your rates for materials, labour, and overhead.<br><strong>Custom Work</strong> — you enter a description and a manual price. Use this for flat-rate jobs or work that doesn't fit the formula.<br><strong>AI Photo</strong> — upload photos and let the AI estimate surface area and complexity for you.<br><br><a href='/Help/Quotes#quote-items' target='_blank'>Learn more →</a>">
|
||||
data-bs-content="<strong>Calculated</strong> — you enter surface area (sq ft) and the system prices it using your rates for materials, labour, and overhead.<br><strong>Custom Work</strong> — you enter a description and a manual price. Use this for flat-rate jobs or work that doesn't fit the formula.<br><strong>AI Photo</strong> — upload photos and let the AI estimate surface area and complexity for you.<br><br><a href='/Help/Quotes#quote-items' target='_blank'>Learn more →</a>">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h5>
|
||||
@@ -314,7 +314,7 @@
|
||||
<div class="form-check">
|
||||
<input asp-for="HideDiscountFromCustomer" class="form-check-input" type="checkbox" id="hideDiscountFromCustomer" />
|
||||
<label class="form-check-label small" for="hideDiscountFromCustomer">
|
||||
Hide discount from customer — PDFs and approval portal show final price only
|
||||
Hide discount from customer — PDFs and approval portal show final price only
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -329,7 +329,7 @@
|
||||
<a tabindex="0" class="help-icon text-white" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="left"
|
||||
data-bs-title="Pricing Summary"
|
||||
data-bs-content="The total is built up from materials, labour, equipment time, overhead, and profit margin — all based on the rates in Settings. A <strong>Tier Discount</strong> appears automatically if the customer has a pricing tier assigned. A <strong>Rush Fee</strong> is added when Rush Job is checked.<br><br><a href='/Help/Quotes#pricing-breakdown' target='_blank'>Learn more →</a>">
|
||||
data-bs-content="The total is built up from materials, labour, equipment time, overhead, and profit margin — all based on the rates in Settings. A <strong>Tier Discount</strong> appears automatically if the customer has a pricing tier assigned. A <strong>Rush Fee</strong> is added when Rush Job is checked.<br><br><a href='/Help/Quotes#pricing-breakdown' target='_blank'>Learn more →</a>">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h5>
|
||||
@@ -340,7 +340,7 @@
|
||||
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
||||
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
||||
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
||||
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
||||
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
||||
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
||||
</p>
|
||||
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
||||
@@ -379,7 +379,7 @@
|
||||
<div class="row g-3" id="stagedPhotoGrid"></div>
|
||||
<div id="stagedPhotoUploadProgress" class="d-none mt-2">
|
||||
<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" style="width:100%"></div></div>
|
||||
<small class="text-muted">Uploading…</small>
|
||||
<small class="text-muted">Uploading…</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -560,7 +560,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.textContent = 'Advanced fields are hidden — switch to Full Quote to see them.';
|
||||
} else {
|
||||
form.classList.remove('quote-simple-mode');
|
||||
hint.textContent = '';
|
||||
@@ -628,8 +628,8 @@
|
||||
smsNote.style.display = 'inline';
|
||||
smsNote.className = hasSms ? 'badge bg-info text-white' : 'badge bg-warning text-dark';
|
||||
smsNote.innerHTML = hasSms
|
||||
? '<i class="bi bi-phone me-1"></i>No email — send via SMS from quote details'
|
||||
: '<i class="bi bi-phone-slash me-1"></i>No email — SMS consent required';
|
||||
? '<i class="bi bi-phone me-1"></i>No email — send via SMS from quote details'
|
||||
: '<i class="bi bi-phone-slash me-1"></i>No email — SMS consent required';
|
||||
} else {
|
||||
smsNote.style.display = 'none';
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right"
|
||||
data-bs-title="Quote Information"
|
||||
data-bs-content="Set the quote date, expiration, and any internal notes. The <strong>Expiration Date</strong> is shown to the customer — once it passes the quote is flagged Expired and can no longer be approved without editing. The <strong>Customer PO</strong> field is optional — use it if the customer provides their own purchase order number.<br><br><a href='/Help/Quotes#quote-statuses' target='_blank'>Learn more →</a>">
|
||||
data-bs-content="Set the quote date, expiration, and any internal notes. The <strong>Expiration Date</strong> is shown to the customer — once it passes the quote is flagged Expired and can no longer be approved without editing. The <strong>Customer PO</strong> field is optional — use it if the customer provides their own purchase order number.<br><br><a href='/Help/Quotes#quote-statuses' target='_blank'>Learn more →</a>">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h5>
|
||||
@@ -173,7 +173,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right"
|
||||
data-bs-title="Oven & Batch Pricing"
|
||||
data-bs-content="The oven cost is charged once per batch at the quote level, not per item. Estimate how many oven loads the full job will fill — for example, if you have 20 small parts and your oven fits 10, that's 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven's hourly rate in Settings.">
|
||||
data-bs-content="The oven cost is charged once per batch at the quote level, not per item. Estimate how many oven loads the full job will fill — for example, if you have 20 small parts and your oven fits 10, that's 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven's hourly rate in Settings.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h5>
|
||||
@@ -216,7 +216,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right"
|
||||
data-bs-title="Quote Item Types"
|
||||
data-bs-content="<strong>Calculated</strong> — you enter surface area (sq ft) and the system prices it using your rates for materials, labour, and overhead.<br><strong>Custom Work</strong> — you enter a description and a manual price. Use this for flat-rate jobs or work that doesn't fit the formula.<br><strong>AI Photo</strong> — upload photos and let the AI estimate surface area and complexity for you.<br><br><a href='/Help/Quotes#quote-items' target='_blank'>Learn more →</a>">
|
||||
data-bs-content="<strong>Calculated</strong> — you enter surface area (sq ft) and the system prices it using your rates for materials, labour, and overhead.<br><strong>Custom Work</strong> — you enter a description and a manual price. Use this for flat-rate jobs or work that doesn't fit the formula.<br><strong>AI Photo</strong> — upload photos and let the AI estimate surface area and complexity for you.<br><br><a href='/Help/Quotes#quote-items' target='_blank'>Learn more →</a>">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h5>
|
||||
@@ -277,7 +277,7 @@
|
||||
<div class="form-check">
|
||||
<input asp-for="HideDiscountFromCustomer" class="form-check-input" type="checkbox" id="hideDiscountFromCustomer" />
|
||||
<label class="form-check-label small" for="hideDiscountFromCustomer">
|
||||
Hide discount from customer — PDFs and approval portal show final price only
|
||||
Hide discount from customer — PDFs and approval portal show final price only
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -292,7 +292,7 @@
|
||||
<a tabindex="0" class="help-icon text-white" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="left"
|
||||
data-bs-title="Pricing Summary"
|
||||
data-bs-content="The total is built up from materials, labour, equipment time, overhead, and profit margin — all based on the rates in Settings. A <strong>Tier Discount</strong> appears automatically if the customer has a pricing tier assigned. A <strong>Rush Fee</strong> is added when Rush Job is checked.<br><br><a href='/Help/Quotes#pricing-breakdown' target='_blank'>Learn more →</a>">
|
||||
data-bs-content="The total is built up from materials, labour, equipment time, overhead, and profit margin — all based on the rates in Settings. A <strong>Tier Discount</strong> appears automatically if the customer has a pricing tier assigned. A <strong>Rush Fee</strong> is added when Rush Job is checked.<br><br><a href='/Help/Quotes#pricing-breakdown' target='_blank'>Learn more →</a>">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h5>
|
||||
@@ -303,7 +303,7 @@
|
||||
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
||||
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
||||
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
||||
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
||||
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
||||
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
||||
</p>
|
||||
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
||||
@@ -407,7 +407,7 @@
|
||||
</div>
|
||||
<div id="editPhotoUploadProgress" class="d-none mt-2">
|
||||
<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" style="width:100%"></div></div>
|
||||
<small class="text-muted">Uploading…</small>
|
||||
<small class="text-muted">Uploading…</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -435,11 +435,11 @@
|
||||
<span id="smsNotifyNote" class="badge @(editHasSms ? "bg-info text-white" : "bg-warning text-dark")">
|
||||
@if (editHasSms)
|
||||
{
|
||||
<i class="bi bi-phone me-1"></i><text>No email — send via SMS from quote details</text>
|
||||
<i class="bi bi-phone me-1"></i><text>No email — send via SMS from quote details</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="bi bi-phone-slash me-1"></i><text>No email — SMS consent required</text>
|
||||
<i class="bi bi-phone-slash me-1"></i><text>No email — SMS consent required</text>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@@ -488,7 +488,7 @@
|
||||
@Html.Raw(Json.Serialize(ViewBag.BlastSetups ?? new List<object>()))
|
||||
</script>
|
||||
|
||||
<!-- Existing items — always populated on Edit -->
|
||||
<!-- Existing items — always populated on Edit -->
|
||||
<script id="existingItemsData" type="application/json">
|
||||
@Html.Raw(System.Text.Json.JsonSerializer.Serialize((Model.QuoteItems ?? new List<PowderCoating.Application.DTOs.Quote.CreateQuoteItemDto>()).Select((item, i) => new {
|
||||
description = item.Description,
|
||||
@@ -613,8 +613,8 @@
|
||||
smsNote.style.display = 'inline';
|
||||
smsNote.className = hasSms ? 'badge bg-info text-white' : 'badge bg-warning text-dark';
|
||||
smsNote.innerHTML = hasSms
|
||||
? '<i class="bi bi-phone me-1"></i>No email — send via SMS from quote details'
|
||||
: '<i class="bi bi-phone-slash me-1"></i>No email — SMS consent required';
|
||||
? '<i class="bi bi-phone me-1"></i>No email — send via SMS from quote details'
|
||||
: '<i class="bi bi-phone-slash me-1"></i>No email — SMS consent required';
|
||||
} else {
|
||||
smsNote.style.display = 'none';
|
||||
}
|
||||
@@ -639,7 +639,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Quote photo direct upload (Edit page — quoteId is known)
|
||||
// Quote photo direct upload (Edit page — quoteId is known)
|
||||
(function () {
|
||||
const quoteId = @Model.Id;
|
||||
const uploadUrl = '@Url.Action("UploadQuotePhoto", "Quotes")';
|
||||
|
||||
Reference in New Issue
Block a user