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:
@@ -73,6 +73,49 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="bin-filter" class="mb-5">
|
||||
<h2 class="h4 fw-bold border-bottom pb-2 mb-3">
|
||||
<i class="bi bi-geo-alt text-primary me-2"></i>Filtering by Location & Printing a Bin List
|
||||
</h2>
|
||||
<p>
|
||||
Every inventory item has an optional <strong>Location</strong> field (for example "Shelf A",
|
||||
"Bin 3", or "Back Wall") that you can set when creating or editing an item. Once locations
|
||||
are filled in, the Inventory list gives you two shortcuts for physical counts and
|
||||
stocktaking:
|
||||
</p>
|
||||
|
||||
<h3 class="h6 fw-semibold mt-3 mb-2">Filtering by location</h3>
|
||||
<p>
|
||||
When at least one item has a location set, a <strong>Location</strong> dropdown appears in
|
||||
the filter bar at the top of the Inventory list, next to the Category filter. Select a
|
||||
location from the dropdown to instantly narrow the list to only the items stored there.
|
||||
You can combine the location filter with a keyword search or category filter at the same time.
|
||||
</p>
|
||||
<p>
|
||||
Each location badge shown in the Location column of the table is also a direct link —
|
||||
clicking it immediately filters to that bin without using the dropdown.
|
||||
</p>
|
||||
|
||||
<h3 class="h6 fw-semibold mt-4 mb-2">Printing a bin list</h3>
|
||||
<p>
|
||||
With a location filter active, a <strong>Print Bin</strong> button appears next to the
|
||||
Clear Filters button. Click it to open a printer-ready page (in a new tab) listing every
|
||||
item in that bin with its item number, name, color, and SKU. Click <strong>Print</strong>
|
||||
on that page or use your browser’s print shortcut to send it to a printer or save
|
||||
as PDF. The page has no site chrome — just the table — so it prints cleanly on a
|
||||
standard sheet.
|
||||
</p>
|
||||
|
||||
<div class="alert alert-permanent alert-info d-flex gap-2 mb-0" role="alert">
|
||||
<i class="bi bi-lightbulb-fill flex-shrink-0 mt-1"></i>
|
||||
<div>
|
||||
The Location dropdown only appears once at least one inventory item has a location set.
|
||||
If you don’t see it, open any item, fill in the <strong>Location</strong> field on the
|
||||
edit form, and save — the dropdown will appear on your next visit to the Inventory list.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="catalog-lookup" class="mb-5">
|
||||
<h2 class="h4 fw-bold border-bottom pb-2 mb-3">
|
||||
<i class="bi bi-search text-primary me-2"></i>Catalog Lookup & Label Scanner
|
||||
@@ -533,6 +576,7 @@
|
||||
<nav class="nav flex-column">
|
||||
<a class="nav-link py-1 px-3 small text-body" href="#overview">Overview</a>
|
||||
<a class="nav-link py-1 px-3 small text-body" href="#adding-items">Adding Inventory Items</a>
|
||||
<a class="nav-link py-1 px-3 small text-body" href="#bin-filter">Location Filtering & Bin Print</a>
|
||||
<a class="nav-link py-1 px-3 small text-body" href="#catalog-lookup">Catalog Lookup & Label Scanner</a>
|
||||
<a class="nav-link py-1 px-3 small text-body" href="#stock-levels">Stock Levels and Reorder Points</a>
|
||||
<a class="nav-link py-1 px-3 small text-body" href="#stock-adjustment">Stock Adjustment</a>
|
||||
|
||||
@@ -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">—</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">—</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">
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
@model IEnumerable<PowderCoating.Application.DTOs.Inventory.InventoryListDto>
|
||||
@{
|
||||
Layout = null;
|
||||
var location = ViewBag.Location as string ?? "";
|
||||
var printedAt = (DateTime)(ViewBag.PrintedAt ?? DateTime.Now);
|
||||
var items = Model.ToList();
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Bin @location — Inventory</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: Arial, Helvetica, sans-serif; font-size: 11pt; color: #111; background: #fff; padding: 20px; }
|
||||
.header { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 2px solid #333; padding-bottom: 10px; margin-bottom: 14px; }
|
||||
.header h1 { font-size: 18pt; }
|
||||
.header .meta { font-size: 9pt; color: #555; text-align: right; line-height: 1.6; }
|
||||
.summary { font-size: 9.5pt; color: #444; margin-bottom: 14px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
thead th { background: #f0f0f0; border-top: 1px solid #bbb; border-bottom: 1px solid #bbb; padding: 6px 8px; text-align: left; font-size: 9pt; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
|
||||
tbody td { padding: 5px 8px; border-bottom: 1px solid #e0e0e0; vertical-align: middle; font-size: 10pt; }
|
||||
tbody tr:last-child td { border-bottom: 2px solid #bbb; }
|
||||
.qty { text-align: right; font-weight: 600; }
|
||||
.qty.low { color: #c00; }
|
||||
.qty.out { color: #888; }
|
||||
.reorder { text-align: right; color: #555; }
|
||||
.cost { text-align: right; }
|
||||
.status-low { font-size: 8pt; color: #c00; font-weight: 600; }
|
||||
.status-out { font-size: 8pt; color: #888; font-weight: 600; }
|
||||
.footer { margin-top: 18px; font-size: 8.5pt; color: #888; border-top: 1px solid #ddd; padding-top: 8px; display: flex; justify-content: space-between; }
|
||||
@@media print {
|
||||
body { padding: 10px; }
|
||||
.no-print { display: none !important; }
|
||||
a { text-decoration: none; color: inherit; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="no-print" style="margin-bottom:16px;">
|
||||
<button onclick="window.print()" style="padding:6px 16px;font-size:11pt;cursor:pointer;margin-right:8px;">
|
||||
🖶 Print
|
||||
</button>
|
||||
<a href="javascript:history.back()" style="font-size:10pt;color:#0d6efd;">← Back</a>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1>Bin: @location</h1>
|
||||
<div style="font-size:10pt;color:#555;margin-top:4px;">Inventory count sheet</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
Powder Coating Logix<br />
|
||||
Printed: @printedAt.ToString("MMM d, yyyy h:mm tt")<br />
|
||||
@items.Count item@(items.Count == 1 ? "" : "s")
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!items.Any())
|
||||
{
|
||||
<p style="color:#888;margin-top:20px;">No active inventory items found in this location.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:5%">#</th>
|
||||
<th style="width:45%">Item Name</th>
|
||||
<th style="width:25%">Color</th>
|
||||
<th style="width:25%">SKU</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@{ var row = 0; }
|
||||
@foreach (var item in items)
|
||||
{
|
||||
row++;
|
||||
<tr>
|
||||
<td style="color:#aaa;font-size:9pt;">@row</td>
|
||||
<td><strong>@item.Name</strong></td>
|
||||
<td>@(item.ColorName ?? "—")</td>
|
||||
<td style="font-family:monospace;font-size:9.5pt;">@item.SKU</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
<div class="footer">
|
||||
<span>Bin: @location</span>
|
||||
<span>Powder Coating Logix — @printedAt.ToString("yyyy")</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user