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,118 @@
@using PowderCoating.Application.DTOs.Wizard
@model WizardCatalogStepDto
@{
ViewData["Title"] = "Setup Wizard — Service Catalog";
var progress = ViewBag.Progress as WizardProgressDto ?? new WizardProgressDto();
int step = ViewBag.Step as int? ?? 16;
}
@section Styles { @await Html.PartialAsync("_WizardStyles") }
<div class="wizard-layout">
@await Html.PartialAsync("_WizardProgress", progress)
<div class="wizard-content">
<div class="wizard-step-header">
<span class="wizard-step-badge">Step @step of @WizardProgressDto.TotalSteps</span>
<h2><i class="bi bi-grid me-2"></i>Service Catalog</h2>
<p class="text-secondary">Add your most common services as catalog items. These appear as quick-add options when building quotes and jobs, saving time on repetitive entries.</p>
</div>
<form asp-action="PostStep16" method="post">
@Html.AntiForgeryToken()
<input type="hidden" name="ItemsJson" id="catalogJson" value="[]" />
<div class="wizard-card">
<h5 class="wizard-card-title">Catalog Items</h5>
<p class="text-secondary small mb-3">
Examples: <em>Wheel (standard rim)</em>, <em>Bumper (full)</em>, <em>Roll bar</em>, <em>Frame section</em>.
You can set a default price and typical surface area so quotes calculate instantly.
</p>
<div id="catalogList"></div>
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addItem()">
<i class="bi bi-plus-circle me-1"></i>Add Catalog Item
</button>
<div class="alert alert-info alert-permanent d-flex gap-2 mt-3 mb-0" role="alert">
<i class="bi bi-info-circle flex-shrink-0 mt-1"></i>
<div class="small">
All items are created under a <strong>General Services</strong> category. You can reorganize them into custom categories later from <strong>Settings &rarr; Catalog Items</strong>.
</div>
</div>
</div>
@await Html.PartialAsync("_WizardFooter", step)
</form>
</div>
</div>
@section Scripts {
<script>
var catalogItems = [];
function renderItems() {
var container = document.getElementById('catalogList');
if (catalogItems.length === 0) {
container.innerHTML = '<p class="text-secondary small py-2">No catalog items added yet. You can skip this step and build your catalog later.</p>';
} else {
container.innerHTML = catalogItems.map(function (item, idx) {
return `<div class="wz-item-row">
<div class="row g-2 align-items-end">
<div class="col-md-4">
<label class="form-label small fw-semibold mb-1">Item Name <span class="text-danger">*</span></label>
<input class="form-control form-control-sm" value="${escHtml(item.name)}" onchange="updateItem(${idx},'name',this.value)" placeholder="e.g. Standard Wheel Rim" />
</div>
<div class="col-md-4">
<label class="form-label small fw-semibold mb-1">Description</label>
<input class="form-control form-control-sm" value="${escHtml(item.description)}" onchange="updateItem(${idx},'description',this.value)" placeholder="Optional short description" />
</div>
<div class="col-md-1">
<label class="form-label small fw-semibold mb-1">Price ($)</label>
<input class="form-control form-control-sm" type="number" min="0" step="0.01" value="${item.defaultPrice || 0}" onchange="updateNum(${idx},'defaultPrice',this.value)" />
</div>
<div class="col-md-2">
<label class="form-label small fw-semibold mb-1">Approx. Area (sq ft)</label>
<input class="form-control form-control-sm" type="number" min="0" step="0.1" value="${item.approximateArea || ''}" onchange="updateNum(${idx},'approximateArea',this.value)" placeholder="e.g. 4.5" />
</div>
<div class="col-md-1 text-end">
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeItem(${idx})" title="Remove">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>`;
}).join('');
}
document.getElementById('catalogJson').value = JSON.stringify(catalogItems);
}
function addItem() {
catalogItems.push({ name: '', description: '', defaultPrice: 0, approximateArea: null });
renderItems();
var inputs = document.querySelectorAll('.wz-item-row:last-child input');
if (inputs.length) inputs[0].focus();
}
function updateItem(idx, field, value) {
catalogItems[idx][field] = value;
document.getElementById('catalogJson').value = JSON.stringify(catalogItems);
}
function updateNum(idx, field, value) {
catalogItems[idx][field] = value === '' ? null : parseFloat(value);
document.getElementById('catalogJson').value = JSON.stringify(catalogItems);
}
function removeItem(idx) {
catalogItems.splice(idx, 1);
renderItems();
}
function escHtml(str) {
return (str || '').toString().replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
renderItems();
</script>
}