Fix: Display variant price and stock directly in Product Edit page

**Problem:**
- Variant price overrides and stock quantities were hidden in collapsible
  panels in the Product Edit page
- The Edit page was showing the VariantCollections system (VariantsJson)
  instead of the actual ProductVariant records created by text import
- User had to expand each variant panel to see price and stock values

**Solution:**
1. **ProductsController.cs (Lines 101-103):**
   - Added call to GetProductVariantsAsync() to load actual variant records
   - Added ViewData["ProductVariants"] to pass data to view

2. **Edit.cshtml (Lines 320-413):**
   - Added new collapsible section "Product Variants"
   - Displays variants in a table with directly visible columns:
     * Name, Type, Price, Stock Level, Sort Order, Status
   - No hidden panels - all information visible at a glance
   - Added quick summary with total variants, stock, and price range
   - Includes helpful links to ProductVariants management page

**Technical Details:**
- Price displays in green with £ symbol when override exists
- Stock shows color-coded badges (green=in stock, red=out of stock)
- Section only appears if variants exist (conditional rendering)
- Expanded by default (aria-expanded="true") for immediate visibility

**Impact:**
- User can now see all variant prices and stock quantities immediately
- No need to click/expand individual variant panels
- Better UX for products imported via text import format
- Maintains separation between VariantCollections system and ProductVariants

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
SysAdmin 2025-10-08 17:48:05 +01:00
parent 859dfd374d
commit 2c5815510d
2 changed files with 99 additions and 0 deletions

View File

@ -98,6 +98,10 @@ public class ProductsController : Controller
var productMultiBuys = await _productService.GetProductMultiBuysAsync(id);
ViewData["ProductMultiBuys"] = productMultiBuys;
// Load product variants
var productVariants = await _productService.GetProductVariantsAsync(id);
ViewData["ProductVariants"] = productVariants;
// TODO: Add ReviewService injection and retrieve actual reviews
// For now, providing mock review data for demonstration
ViewData["ProductReviews"] = new[]

View File

@ -317,6 +317,101 @@
</div>
</div>
<!-- Product Variants (from Text Import) Display Section -->
@{
var productVariants = ViewData["ProductVariants"] as IEnumerable<LittleShop.DTOs.ProductVariantDto>;
}
@if (productVariants != null && productVariants.Any())
{
<hr class="my-4">
<div class="card mb-3">
<div class="card-header p-0">
<button class="btn btn-link w-100 text-start d-flex justify-content-between align-items-center"
type="button" data-bs-toggle="collapse" data-bs-target="#productVariantsCollapse"
aria-expanded="true" aria-controls="productVariantsCollapse">
<span>
<i class="fas fa-list me-2"></i>Product Variants
<small class="text-muted ms-2">@productVariants.Count() variant(s) configured</small>
</span>
<i class="fas fa-chevron-down transition-transform"></i>
</button>
</div>
<div class="collapse show" id="productVariantsCollapse">
<div class="card-body">
<p class="text-muted">
<i class="fas fa-info-circle"></i> These variants were imported via text import format.
To manage them, use the <a href="@Url.Action("ProductVariants", new { id = productId })" class="alert-link">Product Variants page</a>
or re-import via <a href="@Url.Action("ImportText", "Products")" class="alert-link">Text Import</a>.
</p>
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Price</th>
<th>Stock Level</th>
<th>Sort Order</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@foreach (var variant in productVariants.OrderBy(v => v.SortOrder))
{
<tr>
<td><strong>@variant.Name</strong></td>
<td>
<span class="badge bg-secondary">@variant.VariantType</span>
</td>
<td>
@if (variant.Price.HasValue)
{
<strong class="text-success">£@variant.Price.Value.ToString("F2")</strong>
}
else
{
<span class="text-muted">£@Model?.Price.ToString("F2") (base)</span>
}
</td>
<td>
@if (variant.StockLevel > 0)
{
<span class="badge bg-success">@variant.StockLevel in stock</span>
}
else
{
<span class="badge bg-danger">Out of stock</span>
}
</td>
<td>@variant.SortOrder</td>
<td>
@if (variant.IsActive)
{
<span class="badge bg-success">Active</span>
}
else
{
<span class="badge bg-secondary">Inactive</span>
}
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="alert alert-info mt-3 mb-0">
<i class="fas fa-lightbulb"></i> <strong>Quick Summary:</strong>
Total variants: @productVariants.Count() |
Total stock: @productVariants.Sum(v => v.StockLevel) units |
Price range: £@productVariants.Where(v => v.Price.HasValue).Min(v => v.Price ?? 0).ToString("F2") - £@productVariants.Where(v => v.Price.HasValue).Max(v => v.Price ?? 0).ToString("F2")
</div>
</div>
</div>
</div>
}
<div class="mb-3">
<div class="form-check">
<input name="IsActive" type="checkbox" class="form-check-input" checked="@(Model?.IsActive == true)" value="true" />