Add inventory bin filter, print bin, mobile login fixes, and QR scan fix

- Inventory: location filter dropdown + Print Bin page (line #, name, color, SKU)
- Fix: Prismatic Powders QR scan now extracts manufacturer/SKU/color from URL path
  and uses full LookupAsync pipeline instead of relying on page fetch alone
- Fix: iOS Safari 'Login / data Zero KB' download -- add OnRejected HTML response to rate limiter
- Fix: mobile session logout -- ConfigureApplicationCookie with 30-day MaxAge persistent cookie
- Help: new 'Location Filtering & Bin Print' section in Inventory help article
- Help: HelpKnowledgeBase updated with bin filter and print bin details

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 15:19:11 -04:00
parent 8c86eba4f2
commit dfb1d34af3
8 changed files with 443 additions and 48 deletions
@@ -123,10 +123,11 @@
@{
var lowStockOnly = (bool)(ViewBag.LowStockOnly ?? false);
var activeLocation = ViewBag.Location as string;
}
@if (!string.IsNullOrEmpty(ViewBag.SearchTerm) || !string.IsNullOrEmpty(ViewBag.Category) || lowStockOnly)
@if (!string.IsNullOrEmpty(ViewBag.SearchTerm) || !string.IsNullOrEmpty(ViewBag.Category) || !string.IsNullOrEmpty(activeLocation) || lowStockOnly)
{
<div class="alert @(lowStockOnly ? "alert-warning" : "alert-info") alert-permanent d-flex justify-content-between align-items-center">
<div class="alert @(lowStockOnly ? "alert-warning" : "alert-info") alert-permanent d-flex justify-content-between align-items-center flex-wrap gap-2">
<div>
<i class="bi bi-funnel-fill me-2"></i>
@if (lowStockOnly)
@@ -144,11 +145,24 @@
{
<span> in category "<strong>@ViewBag.Category</strong>"</span>
}
@if (!string.IsNullOrEmpty(activeLocation))
{
<span> in bin "<strong>@activeLocation</strong>"</span>
}
}
</div>
<a href="@Url.Action("Index")" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-x me-1"></i>Clear Filters
</a>
<div class="d-flex gap-2 flex-wrap">
@if (!string.IsNullOrEmpty(activeLocation))
{
<a href="@Url.Action("PrintBin", new { location = activeLocation })" target="_blank"
class="btn btn-sm btn-outline-primary" title="Print bin list">
<i class="bi bi-printer me-1"></i>Print Bin
</a>
}
<a href="@Url.Action("Index")" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-x me-1"></i>Clear Filters
</a>
</div>
</div>
}
@@ -161,14 +175,24 @@
<input type="hidden" name="sortColumn" value="@ViewBag.SortColumn" />
<input type="hidden" name="sortDirection" value="@ViewBag.SortDirection" />
<input type="hidden" name="pageSize" value="@Model.PageSize" />
<select name="category" class="form-select" style="max-width: 250px; min-width: 150px;" onchange="this.form.submit()">
<select name="category" class="form-select" style="max-width: 180px; min-width: 130px;" onchange="this.form.submit()">
<option value="">All Categories</option>
@foreach (var cat in ViewBag.Categories)
{
<option value="@cat" selected="@(cat == ViewBag.Category)">@cat</option>
}
</select>
<div class="input-group" style="max-width: 480px; min-width: 300px;">
@if (((IEnumerable<string?>)ViewBag.Locations).Any())
{
<select name="location" class="form-select" style="max-width: 180px; min-width: 130px;" onchange="this.form.submit()">
<option value="">All Locations</option>
@foreach (var loc in ViewBag.Locations)
{
<option value="@loc" selected="@(loc == activeLocation)">@loc</option>
}
</select>
}
<div class="input-group" style="max-width: 380px; min-width: 260px;">
<span class="input-group-text bg-white border-end-0">
<i class="bi bi-search text-muted"></i>
</span>
@@ -210,6 +234,7 @@
<th sortable="Name" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection" class="ps-4">Item Name</th>
<th sortable="Category" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Category</th>
<th sortable="ColorName" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Color</th>
<th>Location</th>
<th>Vendor</th>
<th sortable="QuantityOnHand" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Quantity</th>
<th>Reorder Point</th>
@@ -250,6 +275,21 @@
<span class="text-muted">&mdash;</span>
}
</td>
<td>
@if (!string.IsNullOrEmpty(item.Location))
{
<a href="@Url.Action("Index", new { location = item.Location })"
class="badge bg-info bg-opacity-10 text-info text-decoration-none"
onclick="event.stopPropagation();"
title="Filter by this location">
<i class="bi bi-geo-alt me-1"></i>@item.Location
</a>
}
else
{
<span class="text-muted">&mdash;</span>
}
</td>
<td>
@if (!string.IsNullOrEmpty(item.PrimaryVendorName))
{
@@ -352,6 +392,15 @@
<span class="mobile-card-value">@item.ColorName</span>
</div>
}
@if (!string.IsNullOrEmpty(item.Location))
{
<div class="mobile-card-row">
<span class="mobile-card-label">Location</span>
<span class="mobile-card-value">
<i class="bi bi-geo-alt me-1 text-info"></i>@item.Location
</span>
</div>
}
@if (!string.IsNullOrEmpty(item.PrimaryVendorName))
{
<div class="mobile-card-row">