Files
PowderCoatingLogix/src/PowderCoating.Web/Views/FormulaLibrary/Index.cshtml
T
spouliot ca7e905832 Add Community Formula Library feature
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>
2026-05-27 21:54:51 -04:00

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&hellip;" />
</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&hellip;" />
</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>&mdash; <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
&ldquo;@item.InspiredByName&rdquo; 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 &amp; 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&hellip;</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>
}