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,193 @@
@model PowderCoating.Core.Entities.JobTemplate
@{
ViewData["Title"] = $"Template: {Model.Name}";
ViewData["PageIcon"] = "bi-layout-text-window-reverse";
}
<div class="d-flex justify-content-end align-items-center gap-2 mb-4">
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-warning">
<i class="bi bi-pencil me-2"></i>Edit
</a>
<a asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>Back to Templates
</a>
</div>
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-dismissible fade show">
<i class="bi bi-check-circle me-2"></i>@TempData["Success"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
<div class="row g-4">
<div class="col-lg-8">
<!-- Items -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0 py-3">
<h5 class="mb-0 fw-semibold">
<i class="bi bi-stack me-2 text-primary"></i>Items
<span class="badge bg-secondary ms-2">@Model.Items.Count(i => !i.IsDeleted)</span>
</h5>
</div>
<div class="card-body p-0">
@if (!Model.Items.Any(i => !i.IsDeleted))
{
<p class="text-muted p-4 mb-0">No items on this template.</p>
}
else
{
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Description</th>
<th class="text-center">Qty</th>
<th>Surface Area</th>
<th>Coatings</th>
<th>Prep</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Items.Where(i => !i.IsDeleted).OrderBy(i => i.DisplayOrder))
{
<tr>
<td>
<div class="fw-semibold">@item.Description</div>
@if (item.IsLaborItem)
{
<span class="badge bg-info-subtle text-info border border-info-subtle" style="font-size:.7rem">Labor</span>
}
@if (item.ManualUnitPrice.HasValue)
{
<small class="text-muted">Manual price: @item.ManualUnitPrice.Value.ToString("C")</small>
}
</td>
<td class="text-center">@((int)item.Quantity)</td>
<td>
@if (item.SurfaceAreaSqFt > 0)
{
@item.SurfaceAreaSqFt.ToString("0.##") <small class="text-muted">sq ft</small>
}
else
{
<span class="text-muted">—</span>
}
</td>
<td>
@foreach (var coat in item.Coats.Where(c => !c.IsDeleted).OrderBy(c => c.Sequence))
{
<div class="small">
<i class="bi bi-circle-fill me-1" style="color:#a371f7;font-size:.55rem"></i>
@coat.CoatName
@if (!string.IsNullOrEmpty(coat.ColorName))
{
<span class="text-muted">— @coat.ColorName</span>
}
</div>
}
@if (!item.Coats.Any(c => !c.IsDeleted))
{
<span class="text-muted small">—</span>
}
</td>
<td>
@if (item.RequiresSandblasting)
{
<span class="badge bg-warning-subtle text-warning border border-warning-subtle me-1" style="font-size:.7rem">
<i class="bi bi-tornado me-1"></i>Sandblast
</span>
}
@if (item.RequiresMasking)
{
<span class="badge bg-warning-subtle text-warning border border-warning-subtle" style="font-size:.7rem">
<i class="bi bi-mask me-1"></i>Masking
</span>
}
@foreach (var prep in item.PrepServices.Where(p => !p.IsDeleted))
{
<div class="small text-muted">@prep.PrepService?.ServiceName</div>
}
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
</div>
<!-- Create Job from this Template -->
<div class="card border-0 shadow-sm border-start border-4 border-primary">
<div class="card-body d-flex align-items-center justify-content-between gap-3">
<div>
<h6 class="fw-bold mb-1"><i class="bi bi-plus-circle me-2 text-primary"></i>Use This Template</h6>
<p class="text-muted mb-0 small">Create a new job pre-populated with this template's items and coatings.</p>
</div>
<a asp-controller="Jobs" asp-action="Create" asp-route-templateId="@Model.Id"
class="btn btn-primary flex-shrink-0">
<i class="bi bi-briefcase me-2"></i>Create Job
</a>
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Template Info -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0 py-3">
<h5 class="mb-0 fw-semibold">
<i class="bi bi-info-circle me-2 text-primary"></i>Template Info
</h5>
</div>
<div class="card-body">
<dl class="row g-2 mb-0">
<dt class="col-5 text-muted small">Status</dt>
<dd class="col-7">
@if (Model.IsActive)
{
<span class="badge bg-success">Active</span>
}
else
{
<span class="badge bg-secondary">Inactive</span>
}
</dd>
<dt class="col-5 text-muted small">Customer</dt>
<dd class="col-7">
@if (Model.Customer != null)
{
<span>@(Model.Customer.CompanyName ?? $"{Model.Customer.ContactFirstName} {Model.Customer.ContactLastName}".Trim())</span>
}
else
{
<span class="text-muted">Any customer</span>
}
</dd>
<dt class="col-5 text-muted small">Used</dt>
<dd class="col-7">@Model.UsageCount time@(Model.UsageCount != 1 ? "s" : "")</dd>
<dt class="col-5 text-muted small">Created</dt>
<dd class="col-7">@Model.CreatedAt.ToString("MMM d, yyyy")</dd>
</dl>
@if (!string.IsNullOrEmpty(Model.Description))
{
<hr>
<p class="small mb-0 text-muted">@Model.Description</p>
}
@if (!string.IsNullOrEmpty(Model.SpecialInstructions))
{
<hr>
<p class="small fw-semibold mb-1"><i class="bi bi-exclamation-circle text-warning me-1"></i>Special Instructions</p>
<p class="small mb-0">@Model.SpecialInstructions</p>
}
</div>
</div>
</div>
</div>
@@ -0,0 +1,78 @@
@model PowderCoating.Core.Entities.JobTemplate
@{
ViewData["Title"] = $"Edit Template: {Model.Name}";
ViewData["PageIcon"] = "bi-pencil-square";
}
<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-2"></i>Back
</a>
</div>
<div class="row justify-content-center">
<div class="col-lg-7">
<div class="card border-0 shadow-sm">
<div class="card-body p-4">
<form asp-action="Edit" asp-route-id="@Model.Id" method="post">
@Html.AntiForgeryToken()
<div class="mb-3">
<label class="form-label fw-semibold">Template Name <span class="text-danger">*</span></label>
<input type="text" name="name" class="form-control" value="@Model.Name" required maxlength="100">
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Description</label>
<textarea name="description" class="form-control" rows="2" maxlength="500">@Model.Description</textarea>
<div class="form-text">Briefly describe what this template is used for.</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Default Customer</label>
<select name="customerId" class="form-select">
<option value="">— Any customer —</option>
@foreach (SelectListItem item in (ViewBag.Customers as IEnumerable<SelectListItem> ?? Enumerable.Empty<SelectListItem>()))
{
if (item.Selected)
{
<option value="@item.Value" selected>@item.Text</option>
}
else
{
<option value="@item.Value">@item.Text</option>
}
}
</select>
<div class="form-text">Pre-selects this customer when creating a job from the template. Optional.</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Special Instructions</label>
<textarea name="specialInstructions" class="form-control" rows="3" maxlength="2000">@Model.SpecialInstructions</textarea>
</div>
<div class="mb-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="isActive" value="true" id="isActive" @(Model.IsActive ? "checked" : "")>
<label class="form-check-label fw-semibold" for="isActive">Active</label>
</div>
<div class="form-text">Inactive templates won't appear in the "Create from Template" picker.</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle me-2"></i>Save Changes
</button>
<a asp-action="Details" asp-route-id="@Model.Id" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
<div class="alert alert-info mt-3">
<i class="bi bi-info-circle me-2"></i>
To update the <strong>items and coatings</strong> on this template, create a new job with the desired configuration and save it as a template.
</div>
</div>
</div>
@@ -0,0 +1,102 @@
@model List<PowderCoating.Core.Entities.JobTemplate>
@{
ViewData["Title"] = "Job Templates";
ViewData["PageIcon"] = "bi-layout-text-window-reverse";
}
<div class="d-flex justify-content-end align-items-center mb-4">
<a asp-controller="Jobs" asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>Back to Jobs
</a>
</div>
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-dismissible fade show">
<i class="bi bi-check-circle me-2"></i>@TempData["Success"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
@if (!Model.Any())
{
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<i class="bi bi-layout-text-window-reverse text-muted" style="font-size:3rem"></i>
<h5 class="mt-3 text-muted">No Templates Yet</h5>
<p class="text-muted mb-4">Save a job as a template from the Job Details page to get started.</p>
<a asp-controller="Jobs" asp-action="Index" class="btn btn-primary">
<i class="bi bi-briefcase me-2"></i>Go to Jobs
</a>
</div>
</div>
}
else
{
<div class="row g-3">
@foreach (var template in Model)
{
var itemCount = template.Items.Count(i => !i.IsDeleted);
<div class="col-md-6 col-xl-4">
<div class="card border-0 shadow-sm h-100 @(template.IsActive ? "" : "opacity-50")">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="fw-bold mb-0">@template.Name</h6>
@if (!template.IsActive)
{
<span class="badge bg-secondary">Inactive</span>
}
else
{
<span class="badge bg-success-subtle text-success border border-success-subtle">Active</span>
}
</div>
@if (!string.IsNullOrEmpty(template.Description))
{
<p class="text-muted small mb-2">@template.Description</p>
}
<div class="d-flex flex-wrap gap-2 mb-3">
@if (template.Customer != null)
{
<span class="badge bg-light text-dark border">
<i class="bi bi-building me-1"></i>
@(template.Customer.CompanyName ?? $"{template.Customer.ContactFirstName} {template.Customer.ContactLastName}".Trim())
</span>
}
<span class="badge bg-light text-dark border">
<i class="bi bi-stack me-1"></i>@itemCount item@(itemCount != 1 ? "s" : "")
</span>
@if (template.UsageCount > 0)
{
<span class="badge bg-light text-dark border" title="Times used to create a job">
<i class="bi bi-arrow-repeat me-1"></i>Used @template.UsageCount×
</span>
}
</div>
<div class="d-flex gap-2">
<a asp-action="Details" asp-route-id="@template.Id" class="btn btn-sm btn-outline-primary flex-fill">
<i class="bi bi-eye me-1"></i>View
</a>
<a asp-action="Edit" asp-route-id="@template.Id" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-pencil"></i>
</a>
<form asp-action="Delete" asp-route-id="@template.Id" method="post" class="d-inline"
onsubmit="return confirm('Delete template \'@template.Name\'?')">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</div>
<div class="card-footer bg-transparent border-0 pt-0 pb-3 px-3">
<small class="text-muted">Created @template.CreatedAt.ToString("MMM d, yyyy")</small>
</div>
</div>
</div>
}
</div>
}