ca7e905832
Companies can now share their custom formula templates to a platform-wide community library. Other tenants can browse, preview, and import formulas as independent local copies. Includes attribution (source company name), "Inspired by" lineage for re-contributed formulas, import counts, own-formula badge, cascade diagram nullification, and AI assistant + help docs updates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
224 lines
11 KiB
Plaintext
224 lines
11 KiB
Plaintext
@model IEnumerable<PowderCoating.Application.DTOs.Company.FormulaLibraryCardDto>
|
|
@using PowderCoating.Application.DTOs.Company
|
|
@{
|
|
ViewData["Title"] = "Community Formula Library";
|
|
var search = ViewBag.Search as string;
|
|
var outputMode = ViewBag.OutputMode as string;
|
|
var industryHint = ViewBag.IndustryHint as string;
|
|
var totalCount = (int)(ViewBag.TotalCount ?? 0);
|
|
}
|
|
|
|
<div class="container-fluid px-4">
|
|
<div class="d-flex align-items-center justify-content-between mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">
|
|
<i class="bi bi-collection me-2 text-primary"></i>Community Formula Library
|
|
</h1>
|
|
<p class="text-muted mb-0">Browse and import pricing formulas shared by the Powder Coating Logix community.</p>
|
|
</div>
|
|
<a asp-controller="CompanySettings" asp-action="Index" asp-fragment="custom-formulas"
|
|
class="btn btn-outline-secondary">
|
|
<i class="bi bi-gear me-1"></i>My Formulas
|
|
</a>
|
|
</div>
|
|
|
|
@* Search + Filter Bar *@
|
|
<div class="card mb-4 border-0 shadow-sm">
|
|
<div class="card-body py-3">
|
|
<form method="get" asp-action="Index" class="row g-2 align-items-end">
|
|
<div class="col-md-5">
|
|
<label class="form-label small fw-semibold mb-1">Search</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
|
<input type="text" name="search" value="@search"
|
|
class="form-control" placeholder="Name, description, tags, company…" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label small fw-semibold mb-1">Output Mode</label>
|
|
<select name="outputMode" class="form-select">
|
|
<option value="">All modes</option>
|
|
<option value="FixedRate" selected="@(outputMode == "FixedRate")">Fixed Rate</option>
|
|
<option value="SurfaceAreaSqFt" selected="@(outputMode == "SurfaceAreaSqFt")">Surface Area (sq ft)</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label small fw-semibold mb-1">Industry</label>
|
|
<input type="text" name="industryHint" value="@industryHint"
|
|
class="form-control" placeholder="HVAC, Automotive…" />
|
|
</div>
|
|
<div class="col-md-1">
|
|
<button type="submit" class="btn btn-primary w-100">
|
|
<i class="bi bi-funnel-fill"></i>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
@* Results header *@
|
|
<div class="d-flex align-items-center mb-3">
|
|
<span class="text-muted small">
|
|
@totalCount formula@(totalCount == 1 ? "" : "s") in the library
|
|
@if (!string.IsNullOrWhiteSpace(search) || !string.IsNullOrWhiteSpace(outputMode) || !string.IsNullOrWhiteSpace(industryHint))
|
|
{
|
|
<span>— <a asp-action="Index" class="text-decoration-none">clear filters</a></span>
|
|
}
|
|
</span>
|
|
</div>
|
|
|
|
@if (!Model.Any())
|
|
{
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-collection display-4 text-muted mb-3 d-block"></i>
|
|
<h5 class="text-muted">No formulas found</h5>
|
|
@if (!string.IsNullOrWhiteSpace(search) || !string.IsNullOrWhiteSpace(outputMode))
|
|
{
|
|
<p class="text-muted mb-0">Try broadening your search or <a asp-action="Index">view all formulas</a>.</p>
|
|
}
|
|
else
|
|
{
|
|
<p class="text-muted mb-0">Be the first to share a formula from <a asp-controller="CompanySettings" asp-action="Index" asp-fragment="custom-formulas">your templates</a>!</p>
|
|
}
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="row g-3" id="libraryGrid">
|
|
@foreach (var item in Model)
|
|
{
|
|
<div class="col-md-6 col-xl-4">
|
|
<div class="card h-100 border-0 shadow-sm formula-card @(item.IsOwnFormula ? "border-start border-warning border-3" : item.AlreadyImported ? "border-start border-success border-3" : "")">
|
|
<div class="card-body d-flex flex-column">
|
|
|
|
@* Header row *@
|
|
<div class="d-flex align-items-start gap-2 mb-2">
|
|
<div class="flex-grow-1 min-w-0">
|
|
<h6 class="fw-semibold mb-0 text-truncate" title="@item.Name">@item.Name</h6>
|
|
<small class="text-muted">
|
|
<i class="bi bi-building me-1"></i>@item.SourceCompanyName
|
|
</small>
|
|
</div>
|
|
<div class="d-flex flex-column align-items-end gap-1 flex-shrink-0">
|
|
@if (item.OutputMode == "FixedRate")
|
|
{
|
|
<span class="badge bg-primary-subtle text-primary border border-primary-subtle">Fixed Rate</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-info-subtle text-info border border-info-subtle">Surface Area</span>
|
|
}
|
|
@if (item.IsOwnFormula)
|
|
{
|
|
<span class="badge bg-warning-subtle text-warning border border-warning-subtle">
|
|
<i class="bi bi-star-fill me-1"></i>Your Formula
|
|
</span>
|
|
}
|
|
else if (item.AlreadyImported)
|
|
{
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle">
|
|
<i class="bi bi-check-lg me-1"></i>Imported
|
|
</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@* Description *@
|
|
@if (!string.IsNullOrWhiteSpace(item.Description))
|
|
{
|
|
<p class="text-muted small mb-2 flex-grow-1" style="display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">
|
|
@item.Description
|
|
</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="flex-grow-1"></div>
|
|
}
|
|
|
|
@* Inspired by *@
|
|
@if (!string.IsNullOrWhiteSpace(item.InspiredByName))
|
|
{
|
|
<p class="text-muted small mb-2 fst-italic">
|
|
<i class="bi bi-diagram-2 me-1"></i>Inspired by
|
|
“@item.InspiredByName” from @item.InspiredByCompanyName
|
|
</p>
|
|
}
|
|
|
|
@* Tags *@
|
|
@if (!string.IsNullOrWhiteSpace(item.Tags))
|
|
{
|
|
<div class="mb-2">
|
|
@foreach (var tag in item.Tags.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
|
{
|
|
<span class="badge bg-secondary-subtle text-secondary border border-secondary-subtle me-1">@tag.Trim()</span>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@* Footer row *@
|
|
<div class="d-flex align-items-center justify-content-between mt-auto pt-2 border-top">
|
|
<small class="text-muted">
|
|
<i class="bi bi-download me-1"></i>@item.ImportCount import@(item.ImportCount == 1 ? "" : "s")
|
|
</small>
|
|
@if (item.IsOwnFormula)
|
|
{
|
|
<a asp-controller="CompanySettings" asp-action="Index" asp-fragment="custom-formulas"
|
|
class="btn btn-sm btn-outline-warning">
|
|
<i class="bi bi-gear me-1"></i><span>Manage</span>
|
|
</a>
|
|
}
|
|
else
|
|
{
|
|
<button type="button"
|
|
class="btn btn-sm @(item.AlreadyImported ? "btn-outline-success" : "btn-outline-primary") btn-import"
|
|
data-item-id="@item.Id"
|
|
data-item-name="@item.Name">
|
|
@if (item.AlreadyImported)
|
|
{
|
|
<i class="bi bi-check-lg me-1"></i><span>Already Imported</span>
|
|
}
|
|
else
|
|
{
|
|
<i class="bi bi-cloud-download me-1"></i><span>Preview & Import</span>
|
|
}
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@* Import Preview Modal *@
|
|
<div class="modal fade" id="importModal" tabindex="-1" aria-labelledby="importModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="importModalLabel">
|
|
<i class="bi bi-cloud-download me-2"></i>Import Formula
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body" id="importModalBody">
|
|
<div class="text-center py-4">
|
|
<div class="spinner-border text-primary" role="status"></div>
|
|
<p class="mt-2 text-muted">Loading formula details…</p>
|
|
</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" id="btnConfirmImport" disabled>
|
|
<i class="bi bi-cloud-download me-1"></i>Import to My Formulas
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script src="~/js/formula-library.js" asp-append-version="true"></script>
|
|
}
|