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:
@@ -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 ✓" : "Intake")
|
<i class="bi bi-box-seam me-2"></i>@Html.Raw(Model.IntakeDate.HasValue ? "Intake ✓" : "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');
|
||||||
|
|||||||
Reference in New Issue
Block a user