Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
@@ -0,0 +1,532 @@
@model PowderCoating.Application.DTOs.Job.UpdateJobDto
@using PowderCoating.Core.Entities
@{
ViewData["Title"] = "Edit Job";
ViewData["PageIcon"] = "bi-pencil-square";
}
<div class="container-fluid mt-4">
<div class="d-flex justify-content-end mb-4">
<a asp-action="Details" asp-route-id="@Model.Id" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i>Back to Job
</a>
</div>
<form asp-action="Edit" method="post" id="jobEditForm">
@Html.AntiForgeryToken()
<input type="hidden" asp-for="Id" />
<partial name="_ValidationSummary" />
<!-- Job Details Card -->
<div class="card mb-4">
<div class="card-header">
<div class="d-flex align-items-center gap-2">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Job Details</h5>
<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.">
<i class="bi bi-question-circle"></i>
</a>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label asp-for="CustomerId" class="form-label">Customer <span class="text-danger">*</span></label>
<select asp-for="CustomerId" asp-items="@ViewBag.Customers" id="customerSelect">
<option value="">-- Select Customer --</option>
</select>
<span asp-validation-for="CustomerId" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="JobStatusId" class="form-label">Status
<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.">
<i class="bi bi-question-circle"></i>
</a>
</label>
<select asp-for="JobStatusId" class="form-select" asp-items="@ViewBag.Statuses"></select>
<span asp-validation-for="JobStatusId" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="JobPriorityId" class="form-label">Priority
<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.">
<i class="bi bi-question-circle"></i>
</a>
</label>
<select asp-for="JobPriorityId" class="form-select" asp-items="@ViewBag.Priorities"></select>
<span asp-validation-for="JobPriorityId" class="text-danger"></span>
</div>
<div class="col-12">
<label asp-for="Description" class="form-label">Description <span class="text-danger">*</span></label>
<textarea asp-for="Description" class="form-control" rows="3" placeholder="Enter job description"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="ScheduledDate" class="form-label">Scheduled Date
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Scheduled Date"
data-bs-content="The date the shop plans to work on this job. Used for planning and the shop floor board. Does not affect customer-facing documents.">
<i class="bi bi-question-circle"></i>
</a>
</label>
<input asp-for="ScheduledDate" type="date" class="form-control" />
</div>
<div class="col-md-6">
<label asp-for="DueDate" class="form-label">Due Date
<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.">
<i class="bi bi-question-circle"></i>
</a>
</label>
<input asp-for="DueDate" type="date" class="form-control" />
</div>
<div class="col-md-6">
<label asp-for="AssignedUserId" class="form-label">Assigned Worker</label>
<select asp-for="AssignedUserId" class="form-select" asp-items="@ViewBag.Workers">
<option value="">Not assigned</option>
</select>
</div>
<div class="col-md-6">
<label asp-for="CustomerPO" class="form-label">Customer PO</label>
<input asp-for="CustomerPO" class="form-control" placeholder="Enter PO number" />
</div>
<div class="col-md-7">
<label asp-for="SpecialInstructions" class="form-label">Special Instructions</label>
<textarea asp-for="SpecialInstructions" class="form-control" rows="3" placeholder="Any special instructions"></textarea>
</div>
<div class="col-md-5">
<label class="form-label fw-semibold">Tags <span class="badge rounded-pill bg-warning text-dark ms-1" style="font-size:0.65rem;">Recommended</span></label>
<p class="text-muted small mb-1">Tags are used to improve smart predictions and assistance over time. The more consistently you tag, the smarter the system gets.</p>
<input type="hidden" asp-for="Tags" id="jobTags" />
<div id="jobTagsContainer"></div>
<small class="text-muted">Press <kbd>Enter</kbd> or <kbd>,</kbd> to add a tag. Click &times; to remove.</small>
</div>
<div class="col-md-6">
<div class="form-check mt-2">
<input asp-for="RequiresCustomerApproval" class="form-check-input" type="checkbox" />
<label class="form-check-label" asp-for="RequiresCustomerApproval">
Requires Customer Approval Before Starting
</label>
</div>
</div>
</div>
</div>
</div>
<!-- Preparation Services -->
@if (ViewBag.PrepServices != null && ((List<PrepService>)ViewBag.PrepServices).Any())
{
<div class="card mb-4">
<div class="card-header">
<div class="d-flex align-items-center gap-2">
<h5 class="mb-0"><i class="bi bi-tools me-2"></i>Preparation Services</h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Preparation Services"
data-bs-content="Job-level prep services that apply to all items (e.g., full-job sandblasting, chemical strip). For prep that applies to only one item, use the per-item prep settings in the item wizard instead.">
<i class="bi bi-question-circle"></i>
</a>
</div>
</div>
<div class="card-body">
<div class="row g-3">
@foreach (var service in (List<PrepService>)ViewBag.PrepServices)
{
var isChecked = Model.PrepServiceIds != null && Model.PrepServiceIds.Contains(service.Id);
<div class="col-sm-6 col-lg-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="PrepServiceIds" value="@service.Id" id="prepService_@service.Id" @(isChecked ? "checked" : "")>
<label class="form-check-label" for="prepService_@service.Id">
<strong>@service.ServiceName</strong>
@if (!string.IsNullOrWhiteSpace(service.Description))
{
<small class="text-muted d-block">@service.Description</small>
}
</label>
</div>
</div>
}
</div>
</div>
</div>
}
<!-- Items Card -->
<div class="card mb-4">
<div class="card-header d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center gap-2">
<h5 class="mb-0"><i class="bi bi-list-ul me-2"></i>Job Items</h5>
<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.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<button type="button" class="btn btn-primary" onclick="openWizard()">
<i class="bi bi-plus-circle me-1"></i>Add Item
</button>
</div>
<div class="card-body">
<div id="itemsEmptyMessage" class="text-center py-5 text-muted" style="display: none;">
<i class="bi bi-box-seam display-4 d-block mb-2 opacity-25"></i>
<p class="mb-0">No items added yet.</p>
<p class="small">Click <strong>Add Item</strong> to get started.</p>
</div>
<div id="itemCardsContainer"></div>
<div id="hiddenFieldsContainer"></div>
<div id="aiPhotoTempIdsContainer"></div>
</div>
</div>
<!-- Pricing Options (Rush / Discount) -->
<div class="card mb-4">
<div class="card-header">
<div class="d-flex align-items-center gap-2">
<h5 class="mb-0"><i class="bi bi-tag me-2"></i>Pricing Options <small class="text-muted fw-normal">(optional)</small></h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Pricing Options"
data-bs-content="Apply a rush surcharge or a one-off discount to this job. Tier discounts for the customer are applied automatically. These settings are preserved through future edits.">
<i class="bi bi-question-circle"></i>
</a>
</div>
</div>
<div class="card-body">
<div class="mb-3">
<div class="form-check">
<input asp-for="IsRushJob" class="form-check-input" type="checkbox" id="IsRushJob" onchange="scheduleAutoPricing()">
<label class="form-check-label" for="IsRushJob">
<strong>Rush Job</strong> <small class="text-muted">(additional fee applies)</small>
</label>
</div>
</div>
<div class="row g-3">
<div class="col-md-4">
<label asp-for="DiscountType" class="form-label fw-semibold">Discount Type</label>
<select asp-for="DiscountType" class="form-select" id="discountTypeSelect" onchange="onDiscountTypeChange(); scheduleAutoPricing()">
<option value="None">No Discount</option>
<option value="Percentage">Percentage (%)</option>
<option value="FixedAmount">Fixed Amount ($)</option>
</select>
</div>
<div class="col-md-4" id="discountValueSection" style="display: @(Model.DiscountType == "None" ? "none" : "block")">
<label asp-for="DiscountValue" class="form-label fw-semibold">Discount Value</label>
<input asp-for="DiscountValue" type="number" class="form-control" id="discountValueInput"
min="0" step="0.01" onchange="scheduleAutoPricing()" />
</div>
<div class="col-md-4" id="discountReasonSection" style="display: @(Model.DiscountType == "None" ? "none" : "block")">
<label asp-for="DiscountReason" class="form-label fw-semibold">Reason</label>
<input asp-for="DiscountReason" class="form-control" />
</div>
</div>
</div>
</div>
<!-- Pricing Summary -->
<div class="card mb-4">
<div class="card-header bg-primary text-white d-flex align-items-center justify-content-between">
<h5 class="mb-0"><i class="bi bi-calculator me-2"></i>Pricing Summary</h5>
<span id="pricingSpinner" class="spinner-border spinner-border-sm text-white d-none" role="status"></span>
</div>
<div class="card-body">
<div class="text-end" id="pricingSummary">
<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):
<strong id="ovenBatchCostDisplay">$0.00</strong>
</p>
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
Tier Discount (<span id="pricingTierDiscountPercentDisplay">0</span>%):
<strong id="pricingTierDiscountDisplay">-$0.00</strong>
</p>
<p class="mb-1 text-success d-none" id="quoteDiscountRow">
Discount (<span id="quoteDiscountPercentDisplay">0</span>%):
<strong id="quoteDiscountDisplay">-$0.00</strong>
</p>
<p class="mb-1 text-warning d-none" id="rushFeeRow">
<i class="bi bi-lightning-fill me-1"></i>Rush Fee: <strong id="rushFeeDisplay">$0.00</strong>
</p>
<p class="mb-1 d-none" id="shopSuppliesRow">Shop Supplies (<span id="shopSuppliesPercentDisplay">0</span>%): <strong id="shopSuppliesDisplay">$0.00</strong></p>
<p class="mb-1 d-none" id="subtotalRow">Subtotal: <strong id="subtotalDisplay">$0.00</strong></p>
<p class="mb-1 d-none" id="taxRow">Tax (<span id="taxPercentDisplay">0</span>%): <strong id="taxDisplay">$0.00</strong></p>
<hr class="my-2 d-none" id="pricingDivider" />
<h5 class="mb-0 d-none" id="totalRow">Total: <strong id="totalDisplay" class="text-primary">$0.00</strong></h5>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="card mb-4">
<div class="card-body d-flex flex-wrap align-items-center justify-content-between gap-3">
@{
var sendEmailDefault = (bool)(ViewBag.EmailDefaultOnStatusChange ?? Model.SendEmailOnStatusChange);
}
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" role="switch"
id="SendEmailOnStatusChange" name="SendEmailOnStatusChange" value="true"
checked="@(sendEmailDefault ? "checked" : null)" />
<label class="form-check-label small" for="SendEmailOnStatusChange">
<i class="bi bi-envelope me-1"></i>Notify customer if status changes
</label>
</div>
<div class="d-flex gap-2">
<a asp-action="Details" asp-route-id="@Model.Id" class="btn btn-outline-secondary btn-lg">
<i class="bi bi-x-circle me-1"></i>Cancel
</a>
<button type="submit" class="btn btn-primary btn-lg" id="submitBtn">
<i class="bi bi-check-circle me-1"></i>Save Changes
</button>
</div>
</div>
</div>
</form>
</div>
<!-- Surface Area Calculator Modal -->
<div class="modal fade" id="sqFtCalculatorModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-calculator me-2"></i>Surface Area Calculator <small class="text-muted">(per item)</small></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Shape</label>
<select id="calcShape" class="form-select" onchange="toggleShapeInputs()">
<option value="rectangle">Rectangle / Square</option>
<option value="cylinder">Cylinder (Tube)</option>
<option value="circle">Circle (Flat)</option>
</select>
</div>
<div id="rectangleInputs">
<div class="row g-2">
<div class="col-6"><label class="form-label">Length (@(ViewBag.UseMetric == true ? "cm" : "in"))</label>
<input type="number" id="rectLength" class="form-control" min="0" step="0.01" value="0" oninput="calculateSqFt()"></div>
<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>
</div>
<div id="cylinderInputs" style="display:none">
<div class="row g-2">
<div class="col-6"><label class="form-label">Diameter (@(ViewBag.UseMetric == true ? "cm" : "in"))</label>
<input type="number" id="cylDiameter" class="form-control" min="0" step="0.01" value="0" oninput="calculateSqFt()"></div>
<div class="col-6"><label class="form-label">Height (@(ViewBag.UseMetric == true ? "cm" : "in"))</label>
<input type="number" id="cylHeight" class="form-control" min="0" step="0.01" value="0" oninput="calculateSqFt()"></div>
</div>
</div>
<div id="circleInputs" style="display:none">
<label class="form-label">Diameter (@(ViewBag.UseMetric == true ? "cm" : "in"))</label>
<input type="number" id="circDiameter" class="form-control" min="0" step="0.01" value="0" oninput="calculateSqFt()">
</div>
<hr />
<div class="alert alert-info 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-primary" onclick="useSqFtResult()">
<i class="bi bi-check-circle me-1"></i>Use This Value
</button>
</div>
</div>
</div>
</div>
<!-- Item Wizard Modal -->
<div class="modal fade" id="itemWizardModal" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<div class="d-flex flex-column">
<h5 class="modal-title mb-0" id="wizardTitle">Add Item</h5>
<div class="text-muted small mb-1" id="wizardStepTitle">Choose Item Type</div>
<div class="d-flex align-items-center gap-2" id="wizardStepIndicator">
<span class="wizard-step-dot active" data-step="1" title="Item Type"></span>
<div class="wizard-step-line"></div>
<span class="wizard-step-dot" data-step="2" title="Item Details"></span>
<div class="wizard-step-line" id="step2Line"></div>
<span class="wizard-step-dot" data-step="3" title="Coating Layers" id="step3Dot"></span>
<div class="wizard-step-line" id="step3Line"></div>
<span class="wizard-step-dot" data-step="4" title="Prep Services" id="step4Dot"></span>
<span class="text-muted small ms-2" id="wizardStepLabel">Step 1 of 4</span>
</div>
</div>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="wizardBody" style="min-height: 300px;"></div>
<div class="modal-footer justify-content-between">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-secondary d-none" id="btnWizardBack" onclick="wizardBack()">
<i class="bi bi-arrow-left me-1"></i>Back
</button>
<button type="button" class="btn btn-primary" id="btnWizardNext" onclick="wizardNext()">
Next <i class="bi bi-arrow-right ms-1"></i>
</button>
<button type="button" class="btn btn-success d-none" id="btnWizardSave" onclick="wizardSave()">
<i class="bi bi-check-lg me-1"></i>Add Item
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Embedded data for JS -->
@if (ViewBag.InventoryCoatings != null)
{
<script id="inventoryPowdersData" type="application/json">
@Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.InventoryCoatings))
</script>
}
@if (ViewBag.CatalogItems != null)
{
<script id="catalogItemsData" type="application/json">
@Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.CatalogItems))
</script>
}
<script id="merchandiseItemsData" type="application/json">
@Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.MerchandiseItems ?? new List<object>()))
</script>
<script id="vendorsData" type="application/json">
@Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.Vendors ?? new List<object>()))
</script>
<script id="prepServicesData" type="application/json">
@Html.Raw(Json.Serialize(((List<PrepService>)(ViewBag.PrepServices ?? new List<PrepService>())).Select(p => new { id = p.Id, name = p.ServiceName, description = p.Description, requiresBlastSetup = p.RequiresBlastSetup })))
</script>
<script id="blastSetupsData" type="application/json">
@Html.Raw(Json.Serialize(ViewBag.BlastSetups ?? new List<object>()))
</script>
<!-- Existing items pre-fill -->
<script id="existingItemsData" type="application/json">
@Html.Raw(System.Text.Json.JsonSerializer.Serialize((Model.JobItems ?? new List<PowderCoating.Application.DTOs.Quote.CreateQuoteItemDto>()).Select(item => new {
description = item.Description,
quantity = item.Quantity,
surfaceAreaSqFt = item.SurfaceAreaSqFt,
estimatedMinutes = item.EstimatedMinutes,
catalogItemId = item.CatalogItemId,
manualUnitPrice = item.ManualUnitPrice,
powderCostOverride = item.PowderCostOverride,
complexity = item.Complexity,
isGenericItem = item.IsGenericItem,
isLaborItem = item.IsLaborItem,
requiresSandblasting = item.RequiresSandblasting,
requiresMasking = item.RequiresMasking,
notes = item.Notes,
includePrepCost = item.IncludePrepCost,
coats = item.Coats.Select(c => new {
coatName = c.CoatName,
sequence = c.Sequence,
inventoryItemId = c.InventoryItemId,
colorName = c.ColorName,
vendorId = c.VendorId,
colorCode = c.ColorCode,
finish = c.Finish,
coverageSqFtPerLb = c.CoverageSqFtPerLb,
transferEfficiency = c.TransferEfficiency,
powderCostPerLb = c.PowderCostPerLb,
powderToOrder = c.PowderToOrder,
notes = c.Notes
}),
prepServices = item.PrepServices.Select(ps => new {
prepServiceId = ps.PrepServiceId,
estimatedMinutes = ps.EstimatedMinutes,
blastSetupId = ps.BlastSetupId
})
})))
</script>
<script id="quoteMetaData" type="application/json">
{
"customerId": @Json.Serialize(Model.CustomerId),
"taxPercent": @(ViewBag.TaxPercent ?? 0),
"discountType": @Json.Serialize(Model.DiscountType),
"discountValue": @Model.DiscountValue,
"isRushJob": @Json.Serialize(Model.IsRushJob),
"ovenCostId": null,
"areaUnit": @Json.Serialize((string?)ViewBag.AreaUnit),
"useMetric": @Json.Serialize((bool)(ViewBag.UseMetric ?? false)),
"pricingUrl": "@Url.Action("CalculatePricing", "Jobs")",
"aiUploadUrl": "@Url.Action("UploadAiPhoto", "Quotes")",
"aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")",
"aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)),
"itemsFieldPrefix": "JobItems",
"aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")"
}
</script>
@section Styles {
<link rel="stylesheet" href="~/lib/tom-select/css/tom-select.bootstrap5.min.css">
<style>
.wizard-step-dot {
width: 22px; height: 22px; border-radius: 50%;
background: #dee2e6; display: inline-block; cursor: default;
border: 2px solid #dee2e6; transition: all .2s; flex-shrink: 0;
}
.wizard-step-dot.active { background: #0d6efd; border-color: #0d6efd; }
.wizard-step-dot.done { background: #198754; border-color: #198754; }
.wizard-step-dot.skip { background: #adb5bd; border-color: #adb5bd; }
.wizard-step-line { flex: 1; height: 2px; background: #dee2e6; min-width: 30px; }
.item-type-card {
border: 2px solid #dee2e6; border-radius: .75rem; padding: 1.25rem 1rem;
cursor: pointer; transition: all .15s; text-align: center;
background: #fff; user-select: none;
}
.item-type-card:hover { border-color: #86b7fe; background: #f0f6ff; }
.item-type-card.selected { border-color: #0d6efd; background: #eef3ff; }
.item-type-card .item-type-icon { font-size: 2rem; margin-bottom: .5rem; }
[data-bs-theme="dark"] .item-type-card { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }
[data-bs-theme="dark"] .item-type-card:hover { border-color: #86b7fe; background: var(--bs-secondary-bg); }
[data-bs-theme="dark"] .item-type-card.selected { border-color: #0d6efd; background: #1a2a4a; }
.catalog-list-item { cursor: pointer; border-bottom: 1px solid var(--bs-border-color); font-size: .9rem; transition: background .1s; }
.catalog-list-item:last-child { border-bottom: none; }
.catalog-list-item:hover { background: var(--bs-tertiary-bg); }
.catalog-list-item.selected { background: #eef3ff; color: #0d6efd; font-weight: 600; }
[data-bs-theme="dark"] .catalog-list-item.selected { background: #1a2a4a; color: #86b7fe; }
.quote-item-card {
border: 1px solid #dee2e6; border-radius: .5rem;
padding: .75rem 1rem; margin-bottom: .5rem; background: #fafafa;
}
.quote-item-card .item-badge { font-size: .7rem; }
.coat-row { border: 1px solid #dee2e6; border-radius: .5rem; padding: .75rem; margin-bottom: .5rem; }
[data-bs-theme="dark"] .quote-item-card { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }
[data-bs-theme="dark"] .coat-row { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); }
</style>
}
@section Scripts {
<script src="~/lib/tom-select/js/tom-select.complete.min.js"></script>
<script src="~/js/item-wizard.js?v=@DateTime.Now.Ticks"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
initTagInput('jobTags', 'jobTagsContainer');
new TomSelect('#customerSelect', {
placeholder: '-- Select Customer --',
openOnFocus: true,
maxOptions: false
});
});
function onDiscountTypeChange() {
const type = document.getElementById('discountTypeSelect').value;
const show = type !== 'None';
document.getElementById('discountValueSection').style.display = show ? 'block' : 'none';
document.getElementById('discountReasonSection').style.display = show ? 'block' : 'none';
}
</script>
}