e23b006139
Adds an 'All Colors' dropdown to the inventory filter bar populated from the ColorFamilies values already stored on inventory items. Selecting a family (e.g. 'Red') returns only items tagged with that family. Also refactors the 16-branch if/else filter builder into a single composable predicate, making future filter additions trivial. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
563 lines
30 KiB
Plaintext
563 lines
30 KiB
Plaintext
@model PagedResult<PowderCoating.Application.DTOs.Inventory.InventoryListDto>
|
|
|
|
@{
|
|
ViewData["Title"] = "Inventory";
|
|
ViewData["PageIcon"] = "bi-box-seam";
|
|
ViewData["PageHelpTitle"] = "Inventory";
|
|
ViewData["PageHelpContent"] = "Track powder coatings, consumables, and other shop materials. Items show as Low Stock when quantity falls at or below the Reorder Point — the Low Stock count at the top is your reorder alert. Click any row to view full details or edit. Use the search box and category filter to narrow the list. Low Stock filter shows only items needing attention.";
|
|
var lowStockCount = (int)(ViewBag.StatsLowStockCount ?? 0);
|
|
var activeCount = (int)(ViewBag.StatsActiveCount ?? 0);
|
|
var totalValue = (decimal)(ViewBag.StatsTotalValue ?? 0m);
|
|
}
|
|
|
|
<div class="d-flex justify-content-end align-items-center mb-4">
|
|
<div class="d-flex gap-2">
|
|
<a asp-action="Ledger" class="btn btn-outline-secondary">
|
|
<i class="bi bi-journal-text me-2"></i>Inventory Activity
|
|
</a>
|
|
<a asp-controller="PowderInsights" asp-action="Index" class="btn btn-outline-secondary" title="Powder usage analytics">
|
|
<i class="bi bi-graph-up me-2"></i>Powder Insights
|
|
</a>
|
|
<a asp-action="SamplePanels" class="btn btn-outline-primary">
|
|
<i class="bi bi-palette me-2"></i>Manage Sample Panels
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Cards - Desktop -->
|
|
<div class="stats-cards-desktop">
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1" style="font-size: 0.875rem;">Total Items</p>
|
|
<h3 class="mb-0 fw-bold">@Model.TotalCount</h3>
|
|
</div>
|
|
<div class="rounded-circle p-3" style="background: #dbeafe;">
|
|
<i class="bi bi-box-seam text-primary" style="font-size: 1.5rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1" style="font-size: 0.875rem;">Low Stock Items</p>
|
|
<h3 class="mb-0 fw-bold @(lowStockCount > 0 ? "text-danger" : "")">@lowStockCount</h3>
|
|
</div>
|
|
<div class="rounded-circle p-3" style="background: #fee2e2;">
|
|
<i class="bi bi-exclamation-triangle text-danger" style="font-size: 1.5rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1" style="font-size: 0.875rem;">Active Items</p>
|
|
<h3 class="mb-0 fw-bold">@activeCount</h3>
|
|
</div>
|
|
<div class="rounded-circle p-3" style="background: #d1fae5;">
|
|
<i class="bi bi-check-circle text-success" style="font-size: 1.5rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1" style="font-size: 0.875rem;">Total Value</p>
|
|
<h3 class="mb-0 fw-bold">@totalValue.ToString("C")</h3>
|
|
</div>
|
|
<div class="rounded-circle p-3" style="background: #fef3c7;">
|
|
<i class="bi bi-currency-dollar text-warning" style="font-size: 1.5rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Compact Stats - Mobile -->
|
|
<div class="mobile-stats-compact">
|
|
<div class="card">
|
|
<div class="stats-grid">
|
|
<div class="stat-item">
|
|
<div class="stat-icon"><i class="bi bi-box-seam text-primary"></i></div>
|
|
<div class="stat-value">@Model.TotalCount</div>
|
|
<div class="stat-label">Total</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-icon"><i class="bi bi-exclamation-triangle text-danger"></i></div>
|
|
<div class="stat-value @(lowStockCount > 0 ? "text-danger" : "")">@lowStockCount</div>
|
|
<div class="stat-label">Low Stock</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-icon"><i class="bi bi-check-circle text-success"></i></div>
|
|
<div class="stat-value">@activeCount</div>
|
|
<div class="stat-label">Active</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-icon"><i class="bi bi-currency-dollar text-warning"></i></div>
|
|
<div class="stat-value">@totalValue.ToString("C0")</div>
|
|
<div class="stat-label">Value</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@{
|
|
var lowStockOnly = (bool)(ViewBag.LowStockOnly ?? false);
|
|
var activeLocation = ViewBag.Location as string;
|
|
var activeColorFamily = ViewBag.ColorFamily as string;
|
|
}
|
|
@if (!string.IsNullOrEmpty(ViewBag.SearchTerm) || !string.IsNullOrEmpty(ViewBag.Category) || !string.IsNullOrEmpty(activeLocation) || !string.IsNullOrEmpty(activeColorFamily) || lowStockOnly)
|
|
{
|
|
<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)
|
|
{
|
|
<span>Showing <strong>@Model.TotalCount</strong> low stock item@(Model.TotalCount == 1 ? "" : "s") — at or below reorder point</span>
|
|
}
|
|
else
|
|
{
|
|
<span>Showing <strong>@Model.TotalCount</strong> item(s)</span>
|
|
@if (!string.IsNullOrEmpty(ViewBag.SearchTerm))
|
|
{
|
|
<span> matching "<strong>@ViewBag.SearchTerm</strong>"</span>
|
|
}
|
|
@if (!string.IsNullOrEmpty(ViewBag.Category))
|
|
{
|
|
<span> in category "<strong>@ViewBag.Category</strong>"</span>
|
|
}
|
|
@if (!string.IsNullOrEmpty(activeLocation))
|
|
{
|
|
<span> in bin "<strong>@activeLocation</strong>"</span>
|
|
}
|
|
@if (!string.IsNullOrEmpty(activeColorFamily))
|
|
{
|
|
<span> in color family "<strong>@activeColorFamily</strong>"</span>
|
|
}
|
|
}
|
|
</div>
|
|
<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>
|
|
}
|
|
|
|
<!-- Inventory Table Card -->
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-start align-items-lg-center gap-3">
|
|
<div class="d-flex flex-column flex-sm-row gap-2 w-100 w-lg-auto">
|
|
<form asp-action="Index" method="get" class="d-flex flex-column flex-sm-row gap-2 flex-grow-1 flex-lg-grow-0">
|
|
<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: 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>
|
|
@if (((IEnumerable<string>)ViewBag.ColorFamilies).Any())
|
|
{
|
|
<select name="colorFamily" class="form-select" style="max-width: 160px; min-width: 120px;" onchange="this.form.submit()">
|
|
<option value="">All Colors</option>
|
|
@foreach (var family in ViewBag.ColorFamilies)
|
|
{
|
|
<option value="@family" selected="@(family == activeColorFamily)">@family</option>
|
|
}
|
|
</select>
|
|
}
|
|
@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>
|
|
<input type="text" name="searchTerm" class="form-control border-start-0"
|
|
placeholder="Search inventory..."
|
|
value="@ViewBag.SearchTerm">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-search"></i>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<a asp-action="Create" class="btn btn-primary text-nowrap">
|
|
<i class="bi bi-plus-circle me-2"></i>
|
|
<span class="d-none d-sm-inline">Add Item</span>
|
|
<span class="d-inline d-sm-none">Add</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
@if (!Model.Items.Any())
|
|
{
|
|
var isInventoryFiltered = !string.IsNullOrEmpty(ViewBag.SearchTerm as string) || !string.IsNullOrEmpty(ViewBag.Category as string) || !string.IsNullOrEmpty(activeColorFamily) || lowStockOnly;
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-inbox" style="font-size: 4rem; color: #d1d5db;"></i>
|
|
<h5 class="mt-3 text-muted">No inventory items found</h5>
|
|
<p class="text-muted mb-4">@(isInventoryFiltered ? "No items match your current filters." : "Get started by adding your first inventory item.")</p>
|
|
<a asp-action="Create" class="btn btn-primary">
|
|
<i class="bi bi-plus-circle me-2"></i>@(isInventoryFiltered ? "Add Item" : "Add Your First Item")
|
|
</a>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<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>
|
|
<th sortable="UnitCost" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Unit Cost</th>
|
|
<th>Stock Value</th>
|
|
<th sortable="IsActive" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Status</th>
|
|
<th class="text-end pe-4">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="inventoryTable">
|
|
@foreach (var item in Model.Items)
|
|
{
|
|
<tr class="inventory-row" data-item-id="@item.Id" style="cursor: pointer;">
|
|
<td class="ps-4">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<div class="rounded-circle d-flex align-items-center justify-content-center"
|
|
style="width: 40px; height: 40px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-weight: 600;">
|
|
<i class="bi bi-box"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-semibold">@item.Name</div>
|
|
<small class="text-muted">@item.SKU</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary bg-opacity-10 text-secondary">
|
|
@item.Category
|
|
</span>
|
|
</td>
|
|
<td>
|
|
@if (!string.IsNullOrEmpty(item.ColorName))
|
|
{
|
|
<span>@item.ColorName</span>
|
|
}
|
|
else
|
|
{
|
|
<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))
|
|
{
|
|
<span class="text-muted">@item.PrimaryVendorName</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">—</span>
|
|
}
|
|
</td>
|
|
<td>
|
|
<span class="@(item.IsOutOfStock ? "text-dark fw-semibold" : item.IsLowStock ? "text-danger fw-semibold" : "")">
|
|
@item.QuantityOnHand.ToString("N2") @item.UnitOfMeasure
|
|
@if (item.IsOutOfStock)
|
|
{
|
|
<i class="bi bi-x-circle ms-1"></i>
|
|
}
|
|
else if (item.IsLowStock)
|
|
{
|
|
<i class="bi bi-exclamation-triangle ms-1"></i>
|
|
}
|
|
</span>
|
|
</td>
|
|
<td>@item.ReorderPoint.ToString("N2") @item.UnitOfMeasure</td>
|
|
<td>@item.UnitCost.ToString("C")</td>
|
|
<td>
|
|
<span class="fw-semibold">@((item.QuantityOnHand * item.UnitCost).ToString("C"))</span>
|
|
</td>
|
|
<td>
|
|
@if (item.IsIncoming)
|
|
{
|
|
<span class="badge bg-warning bg-opacity-25 text-warning-emphasis">
|
|
<i class="bi bi-truck me-1"></i>Incoming
|
|
</span>
|
|
}
|
|
else if (item.IsActive)
|
|
{
|
|
<span class="badge bg-success bg-opacity-10 text-success">
|
|
<i class="bi bi-check-circle me-1"></i>Active
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-danger bg-opacity-10 text-danger">
|
|
<i class="bi bi-x-circle me-1"></i>Inactive
|
|
</span>
|
|
}
|
|
</td>
|
|
<td class="text-end pe-4">
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-secondary"
|
|
title="Print QR Label"
|
|
onclick="openQrLabelModal(@item.Id, event)">
|
|
<i class="bi bi-qr-code"></i>
|
|
</button>
|
|
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-outline-primary" title="View Details">
|
|
<i class="bi bi-eye"></i>
|
|
</a>
|
|
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-outline-warning" title="Edit">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-outline-danger" title="Delete">
|
|
<i class="bi bi-trash"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Mobile Card View -->
|
|
<div class="mobile-card-view">
|
|
<div class="mobile-card-list">
|
|
@foreach (var item in Model.Items)
|
|
{
|
|
<div class="mobile-data-card"
|
|
data-id="@item.Id"
|
|
onclick="window.location.href='@Url.Action("Details", new { id = item.Id })'">
|
|
|
|
<div class="mobile-card-header">
|
|
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
|
<i class="bi bi-box"></i>
|
|
</div>
|
|
<div class="mobile-card-title">
|
|
<h6>@item.Name</h6>
|
|
<small>@item.SKU</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mobile-card-body">
|
|
<div class="mobile-card-row">
|
|
<span class="mobile-card-label">Category</span>
|
|
<span class="mobile-card-value">
|
|
<span class="badge bg-secondary bg-opacity-10 text-secondary">
|
|
@item.Category
|
|
</span>
|
|
</span>
|
|
</div>
|
|
@if (!string.IsNullOrEmpty(item.ColorName))
|
|
{
|
|
<div class="mobile-card-row">
|
|
<span class="mobile-card-label">Color</span>
|
|
<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">
|
|
<span class="mobile-card-label">Vendor</span>
|
|
<span class="mobile-card-value">@item.PrimaryVendorName</span>
|
|
</div>
|
|
}
|
|
<div class="mobile-card-row">
|
|
<span class="mobile-card-label">Quantity</span>
|
|
<span class="mobile-card-value @(item.IsOutOfStock ? "text-dark fw-semibold" : item.IsLowStock ? "text-danger fw-semibold" : "")">
|
|
@item.QuantityOnHand.ToString("N2") @item.UnitOfMeasure
|
|
@if (item.IsOutOfStock)
|
|
{
|
|
<i class="bi bi-x-circle ms-1"></i>
|
|
}
|
|
else if (item.IsLowStock)
|
|
{
|
|
<i class="bi bi-exclamation-triangle ms-1"></i>
|
|
}
|
|
</span>
|
|
</div>
|
|
<div class="mobile-card-row">
|
|
<span class="mobile-card-label">Reorder Point</span>
|
|
<span class="mobile-card-value">@item.ReorderPoint.ToString("N2") @item.UnitOfMeasure</span>
|
|
</div>
|
|
<div class="mobile-card-row">
|
|
<span class="mobile-card-label">Unit Cost</span>
|
|
<span class="mobile-card-value">@item.UnitCost.ToString("C")</span>
|
|
</div>
|
|
<div class="mobile-card-row">
|
|
<span class="mobile-card-label">Stock Value</span>
|
|
<span class="mobile-card-value fw-semibold text-primary">@((item.QuantityOnHand * item.UnitCost).ToString("C"))</span>
|
|
</div>
|
|
<div class="mobile-card-row">
|
|
<span class="mobile-card-label">Status</span>
|
|
<span class="mobile-card-value">
|
|
@if (item.IsActive)
|
|
{
|
|
<span class="badge bg-success bg-opacity-10 text-success">
|
|
<i class="bi bi-check-circle me-1"></i>Active
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-danger bg-opacity-10 text-danger">
|
|
<i class="bi bi-x-circle me-1"></i>Inactive
|
|
</span>
|
|
}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mobile-card-footer">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
|
onclick="openQrLabelModal(@item.Id, event)">
|
|
<i class="bi bi-qr-code me-1"></i>QR Label
|
|
</button>
|
|
<a href="@Url.Action("Details", new { id = item.Id })"
|
|
class="btn btn-sm btn-outline-primary"
|
|
onclick="event.stopPropagation();">
|
|
<i class="bi bi-eye me-1"></i>View
|
|
</a>
|
|
<a href="@Url.Action("Edit", new { id = item.Id })"
|
|
class="btn btn-sm btn-outline-secondary"
|
|
onclick="event.stopPropagation();">
|
|
<i class="bi bi-pencil me-1"></i>Edit
|
|
</a>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
@if (Model.TotalCount > 0)
|
|
{
|
|
@await Html.PartialAsync("_Pagination", Model)
|
|
}
|
|
</div>
|
|
|
|
@* QR Label Modal (shared across all items — src set dynamically by JS) *@
|
|
<div class="modal fade" id="qrLabelModal" tabindex="-1" aria-labelledby="qrLabelModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header py-2">
|
|
<h6 class="modal-title" id="qrLabelModalLabel">
|
|
<i class="bi bi-qr-code me-2"></i>QR Label
|
|
</h6>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body p-0 d-flex justify-content-center" style="background:#f0f0f0;min-height:360px;">
|
|
<iframe id="qrLabelFrame"
|
|
src="about:blank"
|
|
style="width:100%;height:400px;border:none;"
|
|
title="QR Label Preview"></iframe>
|
|
</div>
|
|
<div class="modal-footer py-2">
|
|
<button type="button" class="btn btn-primary btn-sm"
|
|
onclick="document.getElementById('qrLabelFrame').contentWindow.print()">
|
|
<i class="bi bi-printer me-2"></i>Print Label
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script>
|
|
function openQrLabelModal(itemId, e) {
|
|
e.stopPropagation();
|
|
const frame = document.getElementById('qrLabelFrame');
|
|
frame.src = '@Url.Action("Label", "Inventory")/' + itemId + '?embed=true';
|
|
bootstrap.Modal.getOrCreateInstance(document.getElementById('qrLabelModal')).show();
|
|
}
|
|
|
|
// Make table rows clickable
|
|
document.querySelectorAll('.inventory-row').forEach(row => {
|
|
row.addEventListener('click', function(e) {
|
|
// Don't navigate if clicking on action buttons or links
|
|
if (e.target.closest('.btn-group') || e.target.closest('a') || e.target.closest('button')) {
|
|
return;
|
|
}
|
|
|
|
const itemId = this.getAttribute('data-item-id');
|
|
window.location.href = '@Url.Action("Details", "Inventory")/' + itemId;
|
|
});
|
|
|
|
// Add hover effect
|
|
row.addEventListener('mouseenter', function() {
|
|
this.style.backgroundColor = '#f8f9fa';
|
|
});
|
|
|
|
row.addEventListener('mouseleave', function() {
|
|
this.style.backgroundColor = '';
|
|
});
|
|
});
|
|
</script>
|
|
}
|