Button consistency sweep + mobile responsiveness patches

- Standardize modal dismiss/cancel buttons to btn-outline-secondary across 70+ views
- Remove btn-sm from page-level Create and Back buttons (Index + Detail pages)
- Fix Edit buttons on Details pages: btn-secondary -> btn-warning
- Fix form Cancel/Back links: btn-secondary -> btn-outline-secondary
- Add 10 CSS patches to site.css for mobile/tablet responsiveness:
  top-navbar overflow prevention, page-header flex-wrap at 575px,
  table action button min-height override, notification dropdown width cap,
  tablet content padding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 19:04:10 -04:00
parent 328b195127
commit e2f9e9ae4f
71 changed files with 553 additions and 422 deletions
+10 -10
View File
@@ -1,4 +1,4 @@
@model PowderCoating.Application.DTOs.Job.CreateJobDto
@model PowderCoating.Application.DTOs.Job.CreateJobDto
@using PowderCoating.Core.Entities
@{
@@ -19,7 +19,7 @@
<i class="bi bi-layout-text-window-reverse fs-5"></i>
<div>
Pre-filled from template <strong>@ViewBag.TemplateName</strong>.
Items and coatings have been loaded — review and adjust before saving.
Items and coatings have been loaded review and adjust before saving.
</div>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
</div>
@@ -50,7 +50,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Job Details"
data-bs-content="Core job information. Priority and due date are visible on the shop floor board and affect how work is sorted. Customer PO is the customer's own reference number for their purchase order — include it so it appears on invoices. Special Instructions go directly to the shop floor worker.">
data-bs-content="Core job information. Priority and due date are visible on the shop floor board and affect how work is sorted. Customer PO is the customer's own reference number for their purchase order include it so it appears on invoices. Special Instructions go directly to the shop floor worker.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -70,7 +70,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Job Priority"
data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint — overuse of Rush dilutes its meaning for the shop floor team.">
data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint overuse of Rush dilutes its meaning for the shop floor team.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -100,7 +100,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Due Date"
data-bs-content="The customer's deadline — when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
data-bs-content="The customer's deadline when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -130,7 +130,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Special Instructions"
data-bs-content="Free-text notes visible to the shop floor worker on the work order. Use this for masking requirements, handling notes, customer preferences, or anything that doesn't fit in the item-level notes — e.g., 'Keep brackets separated, customer allergic to zinc primer'.">
data-bs-content="Free-text notes visible to the shop floor worker on the work order. Use this for masking requirements, handling notes, customer preferences, or anything that doesn't fit in the item-level notes e.g., 'Keep brackets separated, customer allergic to zinc primer'.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -201,7 +201,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Job Items"
data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification — color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -276,7 +276,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">
@@ -337,7 +337,7 @@
<div class="col-6"><label class="form-label">Width (@(ViewBag.UseMetric == true ? "cm" : "in"))</label>
<input type="number" id="rectWidth" class="form-control" min="0" step="0.01" value="0" oninput="calculateSqFt()"></div>
</div>
<small class="text-muted">Formula: L × W ÷ @(ViewBag.UseMetric == true ? "10,000" : "144")</small>
<small class="text-muted">Formula: L × W ÷ @(ViewBag.UseMetric == true ? "10,000" : "144")</small>
</div>
<div id="cylinderInputs" style="display:none">
<div class="row g-2">
@@ -355,7 +355,7 @@
<div class="alert alert-info alert-permanent mb-0"><strong>Result:</strong> <span id="calcResult">0.00</span> @ViewBag.AreaUnit</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="useSqFtResult()">
<i class="bi bi-check-circle me-1"></i>Use This Value
</button>
+89 -89
View File
@@ -1,4 +1,4 @@
@model PowderCoating.Application.DTOs.Job.JobDto
@model PowderCoating.Application.DTOs.Job.JobDto
@{
ViewData["Title"] = $"Job {Model.JobNumber}";
@@ -57,7 +57,7 @@
}
else
{
<span>Shop work has started review the quote and apply any changes manually.</span>
<span>Shop work has started review the quote and apply any changes manually.</span>
}
</div>
<div class="d-flex gap-2 flex-wrap">
@@ -217,7 +217,7 @@
</button>
</div>
<div id="scheduledDate-saving" class="d-none mt-1 small text-muted">
<span class="spinner-border spinner-border-sm me-1"></span>Saving
<span class="spinner-border spinner-border-sm me-1"></span>Saving
</div>
</div>
</div>
@@ -263,7 +263,7 @@
<i class="bi bi-x-circle me-1"></i><small>Clear date</small>
</button>
<div id="dueDate-saving" class="d-none mt-1 small text-muted">
<span class="spinner-border spinner-border-sm me-1"></span>Saving
<span class="spinner-border spinner-border-sm me-1"></span>Saving
</div>
</div>
</div>
@@ -273,7 +273,7 @@
<div class="d-flex align-items-center gap-2">
<select id="workerAssignmentSelect" class="form-select form-select-sm"
onchange="updateWorkerAssignment(this)">
<option value=""> Unassigned </option>
<option value=""> Unassigned </option>
@foreach (var w in (IEnumerable<SelectListItem>)ViewBag.Workers)
{
if (w.Value == Model.AssignedUserId)
@@ -287,7 +287,7 @@
}
</select>
<span id="workerSaveIndicator" class="text-muted small d-none">
<span class="spinner-border spinner-border-sm me-1"></span>Saving
<span class="spinner-border spinner-border-sm me-1"></span>Saving
</span>
<span id="workerSavedTick" class="text-success small d-none">
<i class="bi bi-check-circle-fill"></i>
@@ -321,7 +321,7 @@
<div class="card-body">
@* ── Catalog Products ── *@
@* ── Catalog Products ── *@
@if (catalogItems.Any())
{
<h6 class="text-primary mb-3"><i class="bi bi-bag-check me-2"></i>Catalog Products</h6>
@@ -351,10 +351,10 @@
{
<br />
<small class="ms-3">
<strong>@coat.CoatName</strong>
<strong>@coat.CoatName</strong>
@if (!string.IsNullOrEmpty(coat.ColorName))
{
<text> @coat.ColorName</text>
<text> @coat.ColorName</text>
@if (!string.IsNullOrEmpty(coat.VendorName))
{
<text> (@coat.VendorName)</text>
@@ -373,7 +373,7 @@
<span class="badge bg-info ms-1" style="font-size:.7em;">@coat.PowderToOrder.Value.ToString("0.##") lbs</span>
@if (!coat.InventoryItemId.HasValue)
{
<span class="badge bg-warning text-dark ms-1" style="font-size:.7em;" title="Custom powder must be purchased before coating"><i class="bi bi-cart me-1"></i>ORDER POWDER</span>
<span class="badge bg-warning text-dark ms-1" style="font-size:.7em;" title="Custom powder must be purchased before coating"><i class="bi bi-cart me-1"></i>ORDER POWDER</span>
}
}
@if (!string.IsNullOrEmpty(coat.Notes))
@@ -390,7 +390,7 @@
@foreach (var ps in item.PrepServices)
{
<br />
<small class="ms-3"> <strong>@(ps.PrepServiceName ?? $"Service #{ps.PrepServiceId}")</strong> <span class="text-muted"> @ps.EstimatedMinutes min</span></small>
<small class="ms-3"> <strong>@(ps.PrepServiceName ?? $"Service #{ps.PrepServiceId}")</strong> <span class="text-muted"> @ps.EstimatedMinutes min</span></small>
}
}
@if (!string.IsNullOrEmpty(item.Notes))
@@ -414,7 +414,7 @@
</div>
}
@* ── Custom Work ── *@
@* ── Custom Work ── *@
@if (customItems.Any())
{
<h6 class="text-success mb-3"><i class="bi bi-calculator me-2"></i>Custom Work</h6>
@@ -478,10 +478,10 @@
{
<br />
<small class="ms-3">
<strong>@coat.CoatName</strong>
<strong>@coat.CoatName</strong>
@if (!string.IsNullOrEmpty(coat.ColorName))
{
<text> @coat.ColorName</text>
<text> @coat.ColorName</text>
@if (!string.IsNullOrEmpty(coat.VendorName))
{
<text> (@coat.VendorName)</text>
@@ -500,7 +500,7 @@
<span class="badge bg-info ms-1" style="font-size:.7em;">@coat.PowderToOrder.Value.ToString("0.##") lbs</span>
@if (!coat.InventoryItemId.HasValue)
{
<span class="badge bg-warning text-dark ms-1" style="font-size:.7em;" title="Custom powder must be purchased before coating"><i class="bi bi-cart me-1"></i>ORDER POWDER</span>
<span class="badge bg-warning text-dark ms-1" style="font-size:.7em;" title="Custom powder must be purchased before coating"><i class="bi bi-cart me-1"></i>ORDER POWDER</span>
}
}
@if (!string.IsNullOrEmpty(coat.Notes))
@@ -517,7 +517,7 @@
@foreach (var ps in item.PrepServices)
{
<br />
<small class="ms-3"> <strong>@(ps.PrepServiceName ?? $"Service #{ps.PrepServiceId}")</strong> <span class="text-muted"> @ps.EstimatedMinutes min</span></small>
<small class="ms-3"> <strong>@(ps.PrepServiceName ?? $"Service #{ps.PrepServiceId}")</strong> <span class="text-muted"> @ps.EstimatedMinutes min</span></small>
}
}
@if (!string.IsNullOrEmpty(item.Notes))
@@ -532,7 +532,7 @@
<text>@item.SurfaceAreaSqFt.ToString("F2") @ViewBag.AreaUnit</text>
<br /><small class="text-muted">per item</small>
}
else { <span class="text-muted"></span> }
else { <span class="text-muted"></span> }
</td>
<td class="text-center">
@if (item.EstimatedMinutes > 0)
@@ -540,7 +540,7 @@
<text>@item.EstimatedMinutes min</text>
<br /><small class="text-muted">per item</small>
}
else { <span class="text-muted"></span> }
else { <span class="text-muted"></span> }
</td>
<td class="text-center">
@if (totalPowderNeeded > 0)
@@ -548,7 +548,7 @@
<strong class="text-success">@totalPowderNeeded.ToString("F2") lbs</strong>
<br /><small class="text-muted">total batch</small>
}
else { <span class="text-muted"></span> }
else { <span class="text-muted"></span> }
</td>
<td class="text-end">@item.UnitPrice.ToString("C")</td>
<td class="text-end fw-semibold">@item.TotalPrice.ToString("C")</td>
@@ -565,7 +565,7 @@
</div>
}
@* ── Labor ── *@
@* ── Labor ── *@
@if (laborItems.Any())
{
<h6 class="text-warning mb-3"><i class="bi bi-person-gear me-2"></i>Labor</h6>
@@ -599,7 +599,7 @@
{
<text>@item.EstimatedMinutes min</text>
}
else { <span class="text-muted"></span> }
else { <span class="text-muted"></span> }
</td>
<td class="text-end">@item.UnitPrice.ToString("C")</td>
<td class="text-end fw-semibold">@item.TotalPrice.ToString("C")</td>
@@ -616,7 +616,7 @@
</div>
}
@* ── Mobile cards ── *@
@* ── Mobile cards ── *@
<div class="d-lg-none mt-2">
@foreach (var item in Model.Items)
{
@@ -653,7 +653,7 @@
<span class="mobile-card-value">
@foreach (var coat in item.Coats.OrderBy(c => c.Sequence))
{
<small class="d-block">@coat.CoatName@(!string.IsNullOrEmpty(coat.ColorName) ? $" {coat.ColorName}" : "")</small>
<small class="d-block">@coat.CoatName@(!string.IsNullOrEmpty(coat.ColorName) ? $" {coat.ColorName}" : "")</small>
}
</span>
</div>
@@ -704,7 +704,7 @@
<i class="bi bi-chevron-down collapse-chevron ms-1" style="transition:transform .2s;"></i>
</div>
<div class="d-flex align-items-center gap-3">
<span class="text-muted small">Total: <strong id="totalHoursDisplay"></strong></span>
<span class="text-muted small">Total: <strong id="totalHoursDisplay"></strong></span>
@{
var estimatedMins = Model.Items?.Sum(i => i.EstimatedMinutes * i.Quantity) ?? 0;
var estimatedHrs = estimatedMins / 60m;
@@ -741,7 +741,7 @@
<tfoot class="table-light fw-semibold">
<tr>
<td colspan="3">Total</td>
<td class="text-end" id="timeEntriesTotalHours"></td>
<td class="text-end" id="timeEntriesTotalHours"></td>
<td colspan="3"></td>
</tr>
</tfoot>
@@ -1099,7 +1099,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="intakeModalLabel">
<i class="bi bi-box-seam me-2 text-info"></i>Part Intake Check In
<i class="bi bi-box-seam me-2 text-info"></i>Part Intake Check In
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
@@ -1117,7 +1117,7 @@
value="@(Model.IntakePartCount.HasValue ? Model.IntakePartCount.Value.ToString() : "")"
placeholder="@intakeExpectedCount" />
<div id="intakeMismatchAlert" class="alert alert-warning alert-permanent mt-2 py-2 d-none">
<i class="bi bi-exclamation-triangle me-1"></i>Count doesn't match expected note the discrepancy below.
<i class="bi bi-exclamation-triangle me-1"></i>Count doesn't match expected note the discrepancy below.
</div>
</div>
<div class="mb-3">
@@ -1138,7 +1138,7 @@
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-info text-white" id="intakeSaveBtn">
<i class="bi bi-check-circle me-1"></i>@(Model.IntakeDate.HasValue ? "Update Intake" : "Complete Check-In")
</button>
@@ -1195,7 +1195,7 @@
<div id="depositFormError" class="alert alert-danger d-none"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success" id="saveDepositBtn">
<i class="bi bi-check-circle me-1"></i>Save & Generate Receipt
</button>
@@ -1310,7 +1310,7 @@
<a asp-action="Intake" asp-route-id="@Model.Id"
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")">
<i class=bi bi-box-seam me-2></i>@(Model.IntakeDate.HasValue ? "Intake " : "Intake")
<i class=bi bi-box-seam me-2></i>@(Model.IntakeDate.HasValue ? "Intake ?" : "Intake")
</a>
}
@{
@@ -1368,7 +1368,7 @@
</div>
</div>
<!-- Pricing Summary (internal d-print-none) -->
<!-- Pricing Summary (internal d-print-none) -->
@{
var jobPb = ViewBag.JobPricingBreakdown as PowderCoating.Application.DTOs.Quote.QuotePricingBreakdownDto;
}
@@ -1400,7 +1400,7 @@
@if (jobPb.OvenBatchCost > 0)
{
<div class="d-flex justify-content-between mb-2">
<span><i class="bi bi-fire me-1"></i>Oven (@jobPb.OvenBatches batch@(jobPb.OvenBatches != 1 ? "es" : "")@(jobPb.OvenCycleMinutes > 0 ? $" × {jobPb.OvenCycleMinutes} min" : "")):</span>
<span><i class="bi bi-fire me-1"></i>Oven (@jobPb.OvenBatches batch@(jobPb.OvenBatches != 1 ? "es" : "")@(jobPb.OvenCycleMinutes > 0 ? $" {jobPb.OvenCycleMinutes} min" : "")):</span>
<strong>@jobPb.OvenBatchCost.ToString("C")</strong>
</div>
}
@@ -1518,7 +1518,7 @@
}
else if (allCatalog)
{
<div class="text-muted small fst-italic">All items use fixed catalog pricing no per-category cost split available.</div>
<div class="text-muted small fst-italic">All items use fixed catalog pricing no per-category cost split available.</div>
}
else
{
@@ -1547,7 +1547,7 @@
@if (jobPb.FacilityOverheadCost > 0)
{
<div class="d-flex justify-content-between small mb-1">
<span class="text-muted">Facility overhead (@jobPb.FacilityOverheadRatePerHour.ToString("C2")/hr × estimated hours)</span>
<span class="text-muted">Facility overhead (@jobPb.FacilityOverheadRatePerHour.ToString("C2")/hr estimated hours)</span>
<span>@jobPb.FacilityOverheadCost.ToString("C")</span>
</div>
}
@@ -1712,11 +1712,11 @@
<div class="px-3 pt-3 pb-2">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="text-muted small">Revenue <span id="costingRevenueSource" class="badge bg-light text-secondary ms-1"></span></span>
<span class="fw-semibold" id="costingRevenue"></span>
<span class="fw-semibold" id="costingRevenue"></span>
</div>
<div class="d-flex justify-content-between small text-muted mb-1 ps-2">
<span>Powder / Materials <a href="#" class="text-muted ms-1" onclick="costing.toggleDetail('powder');return false;"><i class="bi bi-chevron-down" id="powderChevron"></i></a></span>
<span id="costingPowder"></span>
<span id="costingPowder"></span>
</div>
<div id="powderDetail" style="display:none;" class="ps-3 pb-1">
<table class="table table-sm table-borderless mb-0" style="font-size:0.78rem;">
@@ -1725,7 +1725,7 @@
</div>
<div class="d-flex justify-content-between small text-muted mb-1 ps-2">
<span>Labor (<span id="costingLaborHours">0</span> hrs) <a href="#" class="text-muted ms-1" onclick="costing.toggleDetail('labor');return false;"><i class="bi bi-chevron-down" id="laborChevron"></i></a></span>
<span id="costingLabor"></span>
<span id="costingLabor"></span>
</div>
<div id="laborDetail" style="display:none;" class="ps-3 pb-1">
<table class="table table-sm table-borderless mb-0" style="font-size:0.78rem;">
@@ -1734,12 +1734,12 @@
</div>
<div class="d-flex justify-content-between small text-muted mb-1 ps-2">
<span>Oven / Equipment <span id="costingOvenLabel" class="text-muted"></span></span>
<span id="costingOven"></span>
<span id="costingOven"></span>
</div>
<div id="costingReworkSection" style="display:none;">
<div class="d-flex justify-content-between small text-muted mb-1 ps-2">
<span>Rework Costs <a href="#" class="text-muted ms-1" onclick="costing.toggleDetail('rework');return false;"><i class="bi bi-chevron-down" id="reworkChevron"></i></a></span>
<span id="costingRework"></span>
<span id="costingRework"></span>
</div>
<div id="reworkDetail" style="display:none;" class="ps-3 pb-1">
<table class="table table-sm table-borderless mb-0" style="font-size:0.78rem;">
@@ -1748,25 +1748,25 @@
</div>
<div class="d-flex justify-content-between small text-success mb-1 ps-2">
<span>Billed to Customer</span>
<span id="costingReworkBilled"></span>
<span id="costingReworkBilled"></span>
</div>
</div>
<hr class="my-2" />
<div class="d-flex justify-content-between small mb-1 ps-2">
<span class="text-muted">Total Costs</span>
<span id="costingTotal" class="text-danger"></span>
<span id="costingTotal" class="text-danger"></span>
</div>
<div class="d-flex justify-content-between fw-bold mb-1">
<span>Gross Profit</span>
<span id="costingProfit"></span>
<span id="costingProfit"></span>
</div>
<div class="d-flex justify-content-between small text-muted mb-1">
<span>Gross Margin</span>
<span id="costingMargin"></span>
<span id="costingMargin"></span>
</div>
<div class="d-flex justify-content-between small text-muted">
<span>Margin vs Quote</span>
<span id="costingQuotedMargin"></span>
<span id="costingQuotedMargin"></span>
</div>
</div>
<div id="costingNotes" class="px-3 pb-3" style="font-size:0.75rem;"></div>
@@ -1869,7 +1869,7 @@
</div>
<div class="mb-3">
<label class="form-label">Tags
<small class="text-muted fw-normal ms-1"> colors, finish, or other keywords</small>
<small class="text-muted fw-normal ms-1"> colors, finish, or other keywords</small>
</label>
<input type="hidden" id="photoTagsHidden" name="tags" />
<div id="photoTagsContainer"></div>
@@ -1895,7 +1895,7 @@
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="jobPhotoModule.uploadPhoto()">
<i class="bi bi-upload me-1"></i>Upload
</button>
@@ -1948,7 +1948,7 @@
<textarea class="form-control" id="editPhotoCaption" rows="2" placeholder="Add a description or note..."></textarea>
</div>
<div class="mb-0">
<label class="form-label fw-semibold">Tags <small class="text-muted fw-normal ms-1"> colors, finish, keywords</small></label>
<label class="form-label fw-semibold">Tags <small class="text-muted fw-normal ms-1"> colors, finish, keywords</small></label>
<input type="hidden" id="editPhotoTagsHidden" />
<div id="editPhotoTagsContainer"></div>
</div>
@@ -1962,10 +1962,10 @@
<button type="button" class="btn btn-danger" onclick="jobPhotoModule.deletePhoto()">
<i class="bi bi-trash me-1"></i>Delete
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
<div id="editModeButtons" class="d-none d-flex gap-2 w-100 justify-content-end">
<button type="button" class="btn btn-secondary" onclick="jobPhotoModule.cancelPhotoEdit()">Cancel</button>
<button type="button" class="btn btn-outline-secondary" onclick="jobPhotoModule.cancelPhotoEdit()">Cancel</button>
<button type="button" class="btn btn-primary" onclick="jobPhotoModule.savePhotoEdit()">
<i class="bi bi-check-lg me-1"></i>Save Changes
</button>
@@ -2000,7 +2000,7 @@
<div class="mb-2">
<label class="form-label fw-semibold" for="smsMessageText">Message</label>
<textarea class="form-control" id="smsMessageText" rows="5"
placeholder="Type your message" maxlength="160"></textarea>
placeholder="Type your message" maxlength="160"></textarea>
<div class="d-flex justify-content-between mt-1">
<div id="smsStopWarning" class="text-warning small d-none">
<i class="bi bi-exclamation-triangle me-1"></i>"Reply STOP to opt out." will be appended automatically.
@@ -2012,7 +2012,7 @@
</div>
<div class="modal-footer justify-content-between">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" id="smsDismissBtn">
Skip don't send
Skip don't send
</button>
<button type="button" class="btn btn-info text-white" id="smsSendBtn">
<i class="bi bi-send me-1"></i>Send SMS
@@ -2059,7 +2059,7 @@
<p class="mb-0 small" id="deleteConfirmItemName"></p>
</div>
<div class="modal-footer gap-2">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger btn-sm" id="deleteConfirmBtn">
<i class="bi bi-trash me-1"></i>Delete
</button>
@@ -2110,7 +2110,7 @@
<div class="alert alert-info alert-permanent mb-0"><strong>Result:</strong> <span id="calcResult">0.00</span> @ViewBag.AreaUnit</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="useSqFtResult()">
<i class="bi bi-check-circle me-1"></i>Use This Value
</button>
@@ -2223,7 +2223,7 @@
<div class="col-md-6">
<label class="form-label">Specific Item (optional)</label>
<select class="form-select" id="rwJobItem">
<option value=""> Whole Job </option>
<option value=""> Whole Job </option>
@if (Model.Items != null)
{
@foreach (var item in Model.Items)
@@ -2285,9 +2285,9 @@
<div class="col-md-6">
<label class="form-label">Resolution</label>
<select class="form-select" id="rwResolution">
<option value=""> Pending </option>
<option value="0">Recoated No Charge</option>
<option value="1">Recoated Billed to Customer</option>
<option value=""> Pending </option>
<option value="0">Recoated No Charge</option>
<option value="1">Recoated Billed to Customer</option>
<option value="2">Customer Credited</option>
<option value="3">Written Off</option>
<option value="4">No Action Required</option>
@@ -2324,7 +2324,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-warning" id="reworkSaveBtn" onclick="rework.save()">
<i class="bi bi-floppy me-1"></i>Save
</button>
@@ -2346,7 +2346,7 @@
<div class="mb-3">
<label class="form-label fw-semibold">Worker <span class="text-danger">*</span></label>
<select class="form-select" id="teWorkerId">
<option value=""> Select worker </option>
<option value=""> Select worker </option>
@foreach (var w in (ViewBag.ShopWorkers as IEnumerable<dynamic> ?? []))
{
<option value="@w.Id">@w.Name</option>
@@ -2365,7 +2365,7 @@
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Stage / Task</label>
<input type="text" class="form-control" id="teStage" placeholder="e.g. Sandblasting, Coating, Masking" list="stageOptions" />
<input type="text" class="form-control" id="teStage" placeholder="e.g. Sandblasting, Coating, Masking" list="stageOptions" />
<datalist id="stageOptions">
<option value="Sandblasting"></option>
<option value="Masking & Taping"></option>
@@ -2380,7 +2380,7 @@
</div>
<div class="mb-3">
<label class="form-label">Notes</label>
<textarea class="form-control" id="teNotes" rows="2" placeholder="Optional notes"></textarea>
<textarea class="form-control" id="teNotes" rows="2" placeholder="Optional notes"></textarea>
</div>
<div class="text-danger small d-none" id="teError"></div>
</div>
@@ -2418,7 +2418,7 @@
<script src="~/js/job-photos.js" asp-append-version="true"></script>
<script src="~/js/customer-change.js" asp-append-version="true"></script>
<script>
// ── Inline date editing ──────────────────────────────────────────────
// ── Inline date editing ──────────────────────────────────────────────
const jobId = @Model.Id;
const antiForgeryToken = () => document.querySelector('input[name="__RequestVerificationToken"]')?.value ?? '';
@@ -2544,7 +2544,7 @@
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;
// Wrap wizardSave to set a flag before the modal hides
@@ -2562,12 +2562,12 @@
}
});
// ── Delete confirmation modal ─────────────────────────────────────
// ── Delete confirmation modal ─────────────────────────────────────
let pendingDeleteItemId = -1;
const deleteModal = new bootstrap.Modal(document.getElementById('deleteConfirmModal'));
const deleteItemToken = document.querySelector('input[name="__RequestVerificationToken"]').value;
// Delegated listener handles all delete buttons via data attributes
// Delegated listener handles all delete buttons via data attributes
document.addEventListener('click', function (e) {
const btn = e.target.closest('[data-delete-id]');
if (!btn) return;
@@ -2600,7 +2600,7 @@
});
</script>
<!-- ── Rework / Warranty ────────────────────────────────────────────── -->
<!-- ── Rework / Warranty ────────────────────────────────────────────── -->
<script>
const rework = (() => {
const jid = @Model.Id;
@@ -2645,12 +2645,12 @@
</div>
<div class="small mt-1 text-muted">${r.defectDescription}</div>
<div class="small text-muted mt-1">
Found: ${r.discoveredByDisplay} ${new Date(r.discoveredDate).toLocaleDateString()}
${r.reportedByName ? ' ' + r.reportedByName : ''}
Found: ${r.discoveredByDisplay} ${new Date(r.discoveredDate).toLocaleDateString()}
${r.reportedByName ? ' ' + r.reportedByName : ''}
${r.jobItemDescription ? ' | Item: ' + r.jobItemDescription : ''}
</div>
${r.reworkJobNumber ? `<div class="small mt-1"><i class="bi bi-briefcase me-1"></i>Rework Job: <a href="/Jobs/Details/${r.reworkJobId}" class="text-decoration-none fw-semibold">${r.reworkJobNumber}</a></div>` : ''}
${r.resolutionDisplay ? `<div class="small text-success mt-1"><i class="bi bi-check-circle me-1"></i>${r.resolutionDisplay}${r.actualReworkCost > 0 ? ' $' + r.actualReworkCost.toFixed(2) : ''}</div>` : ''}
${r.resolutionDisplay ? `<div class="small text-success mt-1"><i class="bi bi-check-circle me-1"></i>${r.resolutionDisplay}${r.actualReworkCost > 0 ? ' $' + r.actualReworkCost.toFixed(2) : ''}</div>` : ''}
</div>`).join('');
}
@@ -2756,7 +2756,7 @@
})();
</script>
<!-- ── Job Costing ──────────────────────────────────────────────────── -->
<!-- ── Job Costing ──────────────────────────────────────────────────── -->
<script>
const costing = (() => {
const jid = @Model.Id;
@@ -2796,7 +2796,7 @@
document.getElementById('costingReworkBilled').textContent = fmt(d.reworkBilledToCustomer);
const rBody = document.getElementById('reworkCostLines');
rBody.innerHTML = d.reworkLines.map(l => `<tr>
<td class="text-muted">${l.jobNumber ? `<a href="/Jobs/Details" class="text-decoration-none">${l.jobNumber}</a>` : 'No job'} ${l.reason}${l.isEstimate ? ' <span class="badge bg-secondary" style="font-size:0.65rem;">est.</span>' : ''}</td>
<td class="text-muted">${l.jobNumber ? `<a href="/Jobs/Details" class="text-decoration-none">${l.jobNumber}</a>` : 'No job'} ${l.reason}${l.isEstimate ? ' <span class="badge bg-secondary" style="font-size:0.65rem;">est.</span>' : ''}</td>
<td class="text-end text-nowrap">${l.billedToCustomer > 0 ? `<span class="text-success">${fmt(l.billedToCustomer)} billed</span>` : 'absorbed'}</td>
<td class="text-end text-nowrap fw-semibold">${fmt(l.cost)}</td></tr>`).join('');
} else {
@@ -2812,14 +2812,14 @@
document.getElementById('costingMargin').textContent = `${d.grossMargin}%`;
document.getElementById('costingQuotedMargin').textContent =
d.quotedPrice > 0 ? `${d.quotedMargin}% (quoted ${fmt(d.quotedPrice)})` : '';
d.quotedPrice > 0 ? `${d.quotedMargin}% (quoted ${fmt(d.quotedPrice)})` : '';
// Powder detail lines
const pBody = document.getElementById('powderLines');
pBody.innerHTML = d.hasPowderData
? d.powderLines.map(l => `<tr>
<td class="text-muted" style="max-width:160px;white-space:normal;">${l.description}${l.isActual ? ' <span class="badge bg-success" style="font-size:0.65rem;">actual</span>' : ''}</td>
<td class="text-end text-nowrap">${l.lbs} lbs × ${fmt(l.costPerLb)}/lb</td>
<td class="text-end text-nowrap">${l.lbs} lbs ${fmt(l.costPerLb)}/lb</td>
<td class="text-end text-nowrap fw-semibold">${fmt(l.total)}</td></tr>`).join('')
: '<tr><td colspan="3" class="text-muted">No powder cost data on coats.</td></tr>';
@@ -2827,16 +2827,16 @@
const lBody = document.getElementById('laborLines');
lBody.innerHTML = d.hasLaborData
? d.laborLines.map(l => `<tr>
<td class="text-muted">${l.worker}${l.stage ? ' ' + l.stage : ''}<br/><small>${l.workDate}</small></td>
<td class="text-end text-nowrap">${l.hours}h × ${fmt(l.rate)}/hr${l.usingFallback ? ' <span title="Using standard labor rate" class="text-muted">*</span>' : ''}</td>
<td class="text-muted">${l.worker}${l.stage ? ' ' + l.stage : ''}<br/><small>${l.workDate}</small></td>
<td class="text-end text-nowrap">${l.hours}h ${fmt(l.rate)}/hr${l.usingFallback ? ' <span title="Using standard labor rate" class="text-muted">*</span>' : ''}</td>
<td class="text-end text-nowrap fw-semibold">${fmt(l.total)}</td></tr>`).join('')
: '<tr><td colspan="3" class="text-muted">No time entries logged yet.</td></tr>';
// Notes
const notes = [];
if (!d.hasPowderData && d.hasPowderRateButNoQty) notes.push(' Surface area not set on one or more items edit the item and enter a surface area to calculate powder cost.');
else if (!d.hasPowderData) notes.push(' Add powder cost per lb on coat records to include material cost.');
if (!d.hasLaborData) notes.push(' Log time entries to include labor cost.');
if (!d.hasPowderData && d.hasPowderRateButNoQty) notes.push('? Surface area not set on one or more items edit the item and enter a surface area to calculate powder cost.');
else if (!d.hasPowderData) notes.push('? Add powder cost per lb on coat records to include material cost.');
if (!d.hasLaborData) notes.push('? Log time entries to include labor cost.');
if (d.laborLines?.some(l => l.usingFallback)) notes.push('* One or more workers using standard labor rate fallback.');
document.getElementById('costingNotes').innerHTML = notes.map(n => `<div class="text-muted">${n}</div>`).join('');
@@ -2865,7 +2865,7 @@
})();
</script>
<!-- ── Time Tracking ─────────────────────────────────────────────────── -->
<!-- ── Time Tracking ─────────────────────────────────────────────────── -->
<script>
const timeTracking = (() => {
const jid = @Model.Id;
@@ -2873,7 +2873,7 @@
const modal = new bootstrap.Modal(document.getElementById('timeEntryModal'));
let entries = [];
// ── Load ──────────────────────────────────────────────────────────
// ── Load ──────────────────────────────────────────────────────────
async function load() {
const r = await fetch(`/Jobs/GetTimeEntries?jobId=${jid}`);
entries = await r.json();
@@ -2904,7 +2904,7 @@
<td class="fw-semibold">${esc(e.workerName)}</td>
<td class="small">${d}</td>
<td class="text-end fw-semibold">${e.hoursWorked.toFixed(2)}</td>
<td class="small">${e.stage ? `<span class="badge bg-secondary-subtle text-secondary">${esc(e.stage)}</span>` : '<span class="text-muted"></span>'}</td>
<td class="small">${e.stage ? `<span class="badge bg-secondary-subtle text-secondary">${esc(e.stage)}</span>` : '<span class="text-muted"></span>'}</td>
<td class="small text-muted">${esc(e.notes ?? '')}</td>
<td class="text-end">
<button class="btn btn-xs btn-outline-secondary me-1 py-0 px-1" title="Edit" onclick="timeTracking.openEdit(${e.id})"><i class="bi bi-pencil"></i></button>
@@ -2916,12 +2916,12 @@
}
function updateTotals(total) {
const fmt = total > 0 ? total.toFixed(2) + ' hrs' : '';
const fmt = total > 0 ? total.toFixed(2) + ' hrs' : '';
document.getElementById('totalHoursDisplay').textContent = fmt;
document.getElementById('timeEntriesTotalHours').textContent = total > 0 ? total.toFixed(2) : '';
document.getElementById('timeEntriesTotalHours').textContent = total > 0 ? total.toFixed(2) : '';
}
// ── Modal helpers ─────────────────────────────────────────────────
// ── Modal helpers ─────────────────────────────────────────────────
function openAdd() {
document.getElementById('timeEntryModalTitle').textContent = 'Log Time';
document.getElementById('teEntryId').value = '0';
@@ -3027,7 +3027,7 @@
}
});
// ── Deposits ─────────────────────────────────────────────────────────────
// ── Deposits ─────────────────────────────────────────────────────────────
// Note: antiForgeryToken() is already defined above in this script block
document.getElementById('addDepositForm')?.addEventListener('submit', async function(e) {
e.preventDefault();
@@ -3041,7 +3041,7 @@
}
if (errEl) errEl.classList.add('d-none');
if (btn) { btn.disabled = true; btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Saving'; }
if (btn) { btn.disabled = true; btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Saving'; }
const params = new URLSearchParams(new FormData(form));
@@ -3083,7 +3083,7 @@
}
}
// ── Collapsible sections ──────────────────────────────────────────────────
// ── Collapsible sections ──────────────────────────────────────────────────
(function () {
const storageKey = 'jobDetailCollapse_@Model.Id';
const sections = ['collapseTimeTracking', 'collapsePartIntake', 'collapsePhotos', 'collapseDeposits', 'collapseMaterials'];
@@ -3122,7 +3122,7 @@
});
})();
// ── Part Intake Modal ─────────────────────────────────────────────────────
// ── Part Intake Modal ─────────────────────────────────────────────────────
(function () {
const expectedCount = @intakeExpectedCount;
const partCountInput = document.getElementById('intakePartCount');
@@ -3215,7 +3215,7 @@
<div class="mb-3">
<label class="form-label fw-semibold">Template Name <span class="text-danger">*</span></label>
<input type="text" name="templateName" class="form-control" required maxlength="100"
placeholder="e.g. Wheel Refinish Standard 4pc">
placeholder="e.g. Wheel Refinish Standard 4pc">
</div>
<div class="mb-3">
+9 -9
View File
@@ -1,4 +1,4 @@
@model PowderCoating.Application.DTOs.Job.UpdateJobDto
@model PowderCoating.Application.DTOs.Job.UpdateJobDto
@using PowderCoating.Core.Entities
@{
@@ -26,7 +26,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Job Details"
data-bs-content="Core job information. Priority and due date are visible on the shop floor board and job list. Customer PO is the customer's own reference number — it appears on invoices. Special Instructions go directly to the shop floor worker on the work order.">
data-bs-content="Core job information. Priority and due date are visible on the shop floor board and job list. Customer PO is the customer's own reference number it appears on invoices. Special Instructions go directly to the shop floor worker on the work order.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -45,7 +45,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Job Status"
data-bs-content="Tracks where the job is in the workflow: Pending → Approved → Sandblasting → Cleaning → Coating → Curing → QualityCheck → Completed → ReadyForPickup → Delivered. Status changes trigger customer email notifications (if enabled). Use OnHold to pause work without losing progress.">
data-bs-content="Tracks where the job is in the workflow: Pending Approved Sandblasting Cleaning Coating Curing QualityCheck Completed ReadyForPickup Delivered. Status changes trigger customer email notifications (if enabled). Use OnHold to pause work without losing progress.">
<i class="bi bi-question-circle"></i>
</a>
</label>
@@ -57,7 +57,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Job Priority"
data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint — overuse of Rush dilutes its meaning for the shop floor team.">
data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint overuse of Rush dilutes its meaning for the shop floor team.">
<i class="bi bi-question-circle"></i>
</a>
</label>
@@ -85,7 +85,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Due Date"
data-bs-content="The customer's deadline — when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
data-bs-content="The customer's deadline when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
<i class="bi bi-question-circle"></i>
</a>
</label>
@@ -170,7 +170,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Job Items"
data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification — color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -245,7 +245,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">
@@ -322,7 +322,7 @@
<div class="col-6"><label class="form-label">Width (@(ViewBag.UseMetric == true ? "cm" : "in"))</label>
<input type="number" id="rectWidth" class="form-control" min="0" step="0.01" value="0" oninput="calculateSqFt()"></div>
</div>
<small class="text-muted">Formula: L × W ÷ @(ViewBag.UseMetric == true ? "10,000" : "144")</small>
<small class="text-muted">Formula: L × W ÷ @(ViewBag.UseMetric == true ? "10,000" : "144")</small>
</div>
<div id="cylinderInputs" style="display:none">
<div class="row g-2">
@@ -340,7 +340,7 @@
<div class="alert alert-info alert-permanent mb-0"><strong>Result:</strong> <span id="calcResult">0.00</span> @ViewBag.AreaUnit</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="useSqFtResult()">
<i class="bi bi-check-circle me-1"></i>Use This Value
</button>
@@ -1,8 +1,8 @@
@model PowderCoating.Application.DTOs.Job.JobEditItemsViewModel
@model PowderCoating.Application.DTOs.Job.JobEditItemsViewModel
@using PowderCoating.Core.Entities
@{
ViewData["Title"] = $"Edit Items — {Model.JobNumber}";
ViewData["Title"] = $"Edit Items {Model.JobNumber}";
ViewData["PageIcon"] = "bi-list-check";
}
@@ -64,7 +64,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">
@@ -118,7 +118,7 @@
<div class="col-6"><label class="form-label">Width (@(ViewBag.UseMetric == true ? "cm" : "in"))</label>
<input type="number" id="rectWidth" class="form-control" min="0" step="0.01" value="0" oninput="calculateSqFt()"></div>
</div>
<small class="text-muted">Formula: L × W ÷ @(ViewBag.UseMetric == true ? "10,000" : "144")</small>
<small class="text-muted">Formula: L × W ÷ @(ViewBag.UseMetric == true ? "10,000" : "144")</small>
</div>
<div id="cylinderInputs" style="display:none">
<div class="row g-2">
@@ -136,7 +136,7 @@
<div class="alert alert-info alert-permanent mb-0"><strong>Result:</strong> <span id="calcResult">0.00</span> @ViewBag.AreaUnit</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="useSqFtResult()">
<i class="bi bi-check-circle me-1"></i>Use This Value
</button>
@@ -455,7 +455,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="saveWorkerAssignment">
<i class="bi bi-save me-2"></i>Save Assignment
</button>
@@ -491,7 +491,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="savePriority">
<i class="bi bi-save me-2"></i>Save Priority
</button>
@@ -540,7 +540,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="saveStatus">
<i class="bi bi-save me-2"></i>Save Status
</button>
@@ -527,7 +527,7 @@
<p class="text-muted small mb-0">This will update the job status immediately.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="btn-confirm-advance">
<i class="bi bi-check-lg me-2"></i>Yes, Advance
</button>
@@ -1,4 +1,4 @@
@model PowderCoating.Application.DTOs.Job.JobDto
@model PowderCoating.Application.DTOs.Job.JobDto
@{
var emailDefault = ViewBag.EmailDefaultOnComplete == true;
var preLoggedPowder = ViewBag.PreLoggedPowder as Dictionary<int, decimal> ?? new Dictionary<int, decimal>();
@@ -97,7 +97,7 @@
@if (preFilledLbs > 0)
{
<small class="text-success d-block mt-1">
<i class="bi bi-check-circle me-1"></i>Already logged — inventory adjusted
<i class="bi bi-check-circle me-1"></i>Already logged inventory adjusted
</small>
}
</td>
@@ -111,7 +111,7 @@
<td colspan="5">
<small class="text-muted fst-italic">
<i class="bi bi-info-circle me-1"></i>
@item.Description — No coat information available (legacy job item)
@item.Description No coat information available (legacy job item)
</small>
</td>
</tr>
@@ -122,7 +122,7 @@
</div>
<div class="alert alert-info alert-permanent mb-0">
<i class="bi bi-info-circle me-2"></i>
<small>Pre-filled values were already logged via scan — inventory is already adjusted for those. You can edit the amount; only the difference will be applied to inventory.</small>
<small>Pre-filled values were already logged via scan inventory is already adjusted for those. You can edit the amount; only the difference will be applied to inventory.</small>
</div>
</div>
}
@@ -152,7 +152,7 @@
}
</div>
<div>
<button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary me-2" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success">
<i class="bi bi-check-circle me-1"></i>Mark as Completed
</button>