Fix corrupted Unicode characters and intake button rendering in Job Details

- Replace mojibake box-drawing chars (U+2500 encoded as Windows-1252) with
  plain ASCII dashes throughout all comments in Details.cshtml
- Fix intake button showing literal '✓' text: the entity was inside a
  C# string so Razor HTML-encoded the '&'; switched to Html.Raw() so the
  checkmark renders correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 16:43:36 -04:00
parent 8df37ca760
commit 3b5511a703
+16 -16
View File
@@ -321,7 +321,7 @@
<div class="card-body"> <div class="card-body">
@* ── Catalog Products ── *@ @* -- Catalog Products -- *@
@if (catalogItems.Any()) @if (catalogItems.Any())
{ {
<h6 class="text-primary mb-3"><i class="bi bi-bag-check me-2"></i>Catalog Products</h6> <h6 class="text-primary mb-3"><i class="bi bi-bag-check me-2"></i>Catalog Products</h6>
@@ -414,7 +414,7 @@
</div> </div>
} }
@* ── Custom Work ── *@ @* -- Custom Work -- *@
@if (customItems.Any()) @if (customItems.Any())
{ {
<h6 class="text-success mb-3"><i class="bi bi-calculator me-2"></i>Custom Work</h6> <h6 class="text-success mb-3"><i class="bi bi-calculator me-2"></i>Custom Work</h6>
@@ -565,7 +565,7 @@
</div> </div>
} }
@* ── Labor ── *@ @* -- Labor -- *@
@if (laborItems.Any()) @if (laborItems.Any())
{ {
<h6 class="text-warning mb-3"><i class="bi bi-person-gear me-2"></i>Labor</h6> <h6 class="text-warning mb-3"><i class="bi bi-person-gear me-2"></i>Labor</h6>
@@ -616,7 +616,7 @@
</div> </div>
} }
@* ── Mobile cards ── *@ @* -- Mobile cards -- *@
<div class="d-lg-none mt-2"> <div class="d-lg-none mt-2">
@foreach (var item in Model.Items) @foreach (var item in Model.Items)
{ {
@@ -1310,7 +1310,7 @@
<a asp-action="Intake" asp-route-id="@Model.Id" <a asp-action="Intake" asp-route-id="@Model.Id"
class="btn @(Model.IntakeDate.HasValue ? "btn-outline-secondary" : "btn-outline-info")" class="btn @(Model.IntakeDate.HasValue ? "btn-outline-secondary" : "btn-outline-info")"
title="@(Model.IntakeDate.HasValue ? "Update part intake record" : "Check in parts for this job")"> title="@(Model.IntakeDate.HasValue ? "Update part intake record" : "Check in parts for this job")">
<i class="bi bi-box-seam me-2"></i>@(Model.IntakeDate.HasValue ? "Intake &#10003;" : "Intake") <i class="bi bi-box-seam me-2"></i>@Html.Raw(Model.IntakeDate.HasValue ? "Intake &#10003;" : "Intake")
</a> </a>
} }
@{ @{
@@ -2332,7 +2332,7 @@
<script src="~/js/job-photos.js" asp-append-version="true"></script> <script src="~/js/job-photos.js" asp-append-version="true"></script>
<script src="~/js/customer-change.js" asp-append-version="true"></script> <script src="~/js/customer-change.js" asp-append-version="true"></script>
<script> <script>
// ── Inline date editing ────────────────────────────────────────────── // -- Inline date editing ----------------------------------------------
const jobId = @Model.Id; const jobId = @Model.Id;
const antiForgeryToken = () => document.querySelector('input[name="__RequestVerificationToken"]')?.value ?? ''; const antiForgeryToken = () => document.querySelector('input[name="__RequestVerificationToken"]')?.value ?? '';
@@ -2433,7 +2433,7 @@
jobPhotoModule.init(@Model.Id, @Html.Raw(ViewBag.PhotoTagSuggestions ?? "[]")); jobPhotoModule.init(@Model.Id, @Html.Raw(ViewBag.PhotoTagSuggestions ?? "[]"));
// ── Auto-submit after wizard saves an item ──────────────────────── // -- Auto-submit after wizard saves an item ------------------------
let itemsModified = false; let itemsModified = false;
// Wrap wizardSave to set a flag before the modal hides // Wrap wizardSave to set a flag before the modal hides
@@ -2451,7 +2451,7 @@
} }
}); });
// ── Delete confirmation modal ───────────────────────────────────── // -- Delete confirmation modal -------------------------------------
let pendingDeleteItemId = -1; let pendingDeleteItemId = -1;
const deleteModal = new bootstrap.Modal(document.getElementById('deleteConfirmModal')); const deleteModal = new bootstrap.Modal(document.getElementById('deleteConfirmModal'));
const deleteItemToken = document.querySelector('input[name="__RequestVerificationToken"]').value; const deleteItemToken = document.querySelector('input[name="__RequestVerificationToken"]').value;
@@ -2489,7 +2489,7 @@
}); });
</script> </script>
<!-- ── Rework / Warranty ────────────────────────────────────────────── --> <!-- -- Rework / Warranty ---------------------------------------------- -->
<script> <script>
const rework = (() => { const rework = (() => {
const jid = @Model.Id; const jid = @Model.Id;
@@ -2645,7 +2645,7 @@
})(); })();
</script> </script>
<!-- ── Job Costing ──────────────────────────────────────────────────── --> <!-- -- Job Costing ---------------------------------------------------- -->
<script> <script>
const costing = (() => { const costing = (() => {
const jid = @Model.Id; const jid = @Model.Id;
@@ -2754,7 +2754,7 @@
})(); })();
</script> </script>
<!-- ── Time Tracking ─────────────────────────────────────────────────── --> <!-- -- Time Tracking --------------------------------------------------- -->
<script> <script>
const timeTracking = (() => { const timeTracking = (() => {
const jid = @Model.Id; const jid = @Model.Id;
@@ -2762,7 +2762,7 @@
const modal = new bootstrap.Modal(document.getElementById('timeEntryModal')); const modal = new bootstrap.Modal(document.getElementById('timeEntryModal'));
let entries = []; let entries = [];
// ── Load ────────────────────────────────────────────────────────── // -- Load ----------------------------------------------------------
async function load() { async function load() {
const r = await fetch(`/Jobs/GetTimeEntries?jobId=${jid}`); const r = await fetch(`/Jobs/GetTimeEntries?jobId=${jid}`);
entries = await r.json(); entries = await r.json();
@@ -2810,7 +2810,7 @@
document.getElementById('timeEntriesTotalHours').textContent = total > 0 ? total.toFixed(2) : '—'; document.getElementById('timeEntriesTotalHours').textContent = total > 0 ? total.toFixed(2) : '—';
} }
// ── Modal helpers ───────────────────────────────────────────────── // -- Modal helpers -------------------------------------------------
function openAdd() { function openAdd() {
document.getElementById('timeEntryModalTitle').textContent = 'Log Time'; document.getElementById('timeEntryModalTitle').textContent = 'Log Time';
document.getElementById('teEntryId').value = '0'; document.getElementById('teEntryId').value = '0';
@@ -2917,7 +2917,7 @@
} }
}); });
// ── Deposits ───────────────────────────────────────────────────────────── // -- Deposits -------------------------------------------------------------
// Note: antiForgeryToken() is already defined above in this script block // Note: antiForgeryToken() is already defined above in this script block
document.getElementById('addDepositForm')?.addEventListener('submit', async function(e) { document.getElementById('addDepositForm')?.addEventListener('submit', async function(e) {
e.preventDefault(); e.preventDefault();
@@ -2973,7 +2973,7 @@
} }
} }
// ── Collapsible sections ────────────────────────────────────────────────── // -- Collapsible sections --------------------------------------------------
(function () { (function () {
const storageKey = 'jobDetailCollapse_@Model.Id'; const storageKey = 'jobDetailCollapse_@Model.Id';
const sections = ['collapseTimeTracking', 'collapsePartIntake', 'collapsePhotos', 'collapseDeposits', 'collapseMaterials']; const sections = ['collapseTimeTracking', 'collapsePartIntake', 'collapsePhotos', 'collapseDeposits', 'collapseMaterials'];
@@ -3012,7 +3012,7 @@
}); });
})(); })();
// ── Part Intake Modal ───────────────────────────────────────────────────── // -- Part Intake Modal --------------------------------------------------
(function () { (function () {
const expectedCount = @intakeExpectedCount; const expectedCount = @intakeExpectedCount;
const partCountInput = document.getElementById('intakePartCount'); const partCountInput = document.getElementById('intakePartCount');