"Add-Multi-Buy-section-to-product-editor"

This commit is contained in:
sysadmin 2025-10-03 14:41:00 +01:00
parent a9925cd61c
commit c961dfa47a
4 changed files with 289 additions and 1 deletions

View File

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

View File

@ -163,6 +163,160 @@
</div> </div>
</div> </div>
<!-- Product Multi-Buys Collapsible Section -->
<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="#multiBuysCollapse"
aria-expanded="false" aria-controls="multiBuysCollapse">
<span>
<i class="fas fa-tags me-2"></i>Multi-Buy Offers
<small class="text-muted ms-2">
@{
var productMultiBuys = ViewData["ProductMultiBuys"] as IEnumerable<LittleShop.DTOs.ProductMultiBuyDto>;
if (productMultiBuys != null && productMultiBuys.Any())
{
<span>@productMultiBuys.Count() offer(s) configured</span>
}
else
{
<span>No multi-buy offers</span>
}
}
</small>
</span>
<i class="fas fa-chevron-down transition-transform"></i>
</button>
</div>
<div class="collapse" id="multiBuysCollapse">
<div class="card-body">
<p class="text-muted">Configure quantity-based pricing (e.g., 1 for £10, 2 for £19, 3 for £25).</p>
@if (productMultiBuys != null && productMultiBuys.Any())
{
<div class="table-responsive mb-3">
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th>Quantity</th>
<th>Price</th>
<th>Per Unit</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var multiBuy in productMultiBuys.OrderBy(mb => mb.SortOrder))
{
<tr>
<td>@multiBuy.Name</td>
<td>@multiBuy.Quantity</td>
<td>£@multiBuy.Price.ToString("F2")</td>
<td>£@multiBuy.PricePerUnit.ToString("F2")</td>
<td>
@if (multiBuy.IsActive)
{
<span class="badge bg-success">Active</span>
}
else
{
<span class="badge bg-secondary">Inactive</span>
}
</td>
<td>
<button type="button" class="btn btn-sm btn-outline-primary edit-multibuy-btn"
data-id="@multiBuy.Id"
data-name="@multiBuy.Name"
data-description="@multiBuy.Description"
data-quantity="@multiBuy.Quantity"
data-price="@multiBuy.Price"
data-sortorder="@multiBuy.SortOrder"
data-isactive="@multiBuy.IsActive">
<i class="fas fa-edit"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-danger delete-multibuy-btn"
data-id="@multiBuy.Id"
data-name="@multiBuy.Name">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<div class="text-muted text-center py-3 mb-3">
<i class="fas fa-tags fa-2x mb-2"></i>
<p>No multi-buy offers configured yet.</p>
</div>
}
<!-- Add New Multi-Buy Form -->
<div class="card bg-light">
<div class="card-body">
<h6><i class="fas fa-plus"></i> Add Multi-Buy Offer</h6>
<div id="multibuy-form">
<div class="row">
<div class="col-md-6">
<div class="mb-2">
<label class="form-label">Name</label>
<input type="text" id="multibuy-name" class="form-control form-control-sm" placeholder="e.g., Twin Pack" />
</div>
</div>
<div class="col-md-6">
<div class="mb-2">
<label class="form-label">Description</label>
<input type="text" id="multibuy-description" class="form-control form-control-sm" placeholder="e.g., Best value!" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="mb-2">
<label class="form-label">Quantity</label>
<input type="number" id="multibuy-quantity" class="form-control form-control-sm" min="1" value="1" />
</div>
</div>
<div class="col-md-3">
<div class="mb-2">
<label class="form-label">Price (£)</label>
<input type="number" id="multibuy-price" class="form-control form-control-sm" min="0.01" step="0.01" />
</div>
</div>
<div class="col-md-3">
<div class="mb-2">
<label class="form-label">Sort Order</label>
<input type="number" id="multibuy-sortorder" class="form-control form-control-sm" min="0" value="0" />
</div>
</div>
<div class="col-md-3">
<div class="mb-2">
<label class="form-label">Active</label>
<div class="form-check">
<input type="checkbox" id="multibuy-isactive" class="form-check-input" checked />
</div>
</div>
</div>
</div>
<input type="hidden" id="multibuy-id" value="" />
<button type="button" id="save-multibuy-btn" class="btn btn-sm btn-primary">
<i class="fas fa-save"></i> Save Multi-Buy
</button>
<button type="button" id="cancel-multibuy-btn" class="btn btn-sm btn-secondary" style="display: none;">
<i class="fas fa-times"></i> Cancel
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mb-3"> <div class="mb-3">
<div class="form-check"> <div class="form-check">
<input name="IsActive" type="checkbox" class="form-check-input" checked="@(Model?.IsActive == true)" value="true" /> <input name="IsActive" type="checkbox" class="form-check-input" checked="@(Model?.IsActive == true)" value="true" />
@ -561,6 +715,128 @@
} }
}); });
}); });
// Multi-Buy Management
const productId = '@productId';
// Edit multi-buy button
document.querySelectorAll('.edit-multibuy-btn').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.dataset.id;
const name = this.dataset.name;
const description = this.dataset.description;
const quantity = this.dataset.quantity;
const price = this.dataset.price;
const sortorder = this.dataset.sortorder;
const isactive = this.dataset.isactive === 'True';
document.getElementById('multibuy-id').value = id;
document.getElementById('multibuy-name').value = name;
document.getElementById('multibuy-description').value = description;
document.getElementById('multibuy-quantity').value = quantity;
document.getElementById('multibuy-price').value = price;
document.getElementById('multibuy-sortorder').value = sortorder;
document.getElementById('multibuy-isactive').checked = isactive;
document.getElementById('save-multibuy-btn').innerHTML = '<i class="fas fa-save"></i> Update Multi-Buy';
document.getElementById('cancel-multibuy-btn').style.display = 'inline-block';
});
});
// Cancel edit
document.getElementById('cancel-multibuy-btn').addEventListener('click', function() {
document.getElementById('multibuy-id').value = '';
document.getElementById('multibuy-name').value = '';
document.getElementById('multibuy-description').value = '';
document.getElementById('multibuy-quantity').value = '1';
document.getElementById('multibuy-price').value = '';
document.getElementById('multibuy-sortorder').value = '0';
document.getElementById('multibuy-isactive').checked = true;
document.getElementById('save-multibuy-btn').innerHTML = '<i class="fas fa-save"></i> Save Multi-Buy';
this.style.display = 'none';
});
// Save multi-buy
document.getElementById('save-multibuy-btn').addEventListener('click', async function() {
const multibuyId = document.getElementById('multibuy-id').value;
const name = document.getElementById('multibuy-name').value.trim();
const description = document.getElementById('multibuy-description').value.trim();
const quantity = parseInt(document.getElementById('multibuy-quantity').value);
const price = parseFloat(document.getElementById('multibuy-price').value);
const sortOrder = parseInt(document.getElementById('multibuy-sortorder').value);
const isActive = document.getElementById('multibuy-isactive').checked;
if (!name || !quantity || !price) {
alert('Please fill in Name, Quantity, and Price');
return;
}
const data = {
name,
description,
quantity,
price,
sortOrder,
isActive
};
try {
let response;
if (multibuyId) {
// Update existing
response = await fetch(`/api/productmultibuys/${multibuyId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
} else {
// Create new
data.productId = productId;
response = await fetch('/api/productmultibuys', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
if (response.ok) {
location.reload();
} else {
const error = await response.text();
alert('Error: ' + error);
}
} catch (error) {
alert('Error saving multi-buy: ' + error.message);
}
});
// Delete multi-buy
document.querySelectorAll('.delete-multibuy-btn').forEach(btn => {
btn.addEventListener('click', async function() {
const id = this.dataset.id;
const name = this.dataset.name;
if (!confirm(`Delete multi-buy "${name}"?`)) {
return;
}
try {
const response = await fetch(`/api/productmultibuys/${id}`, {
method: 'DELETE'
});
if (response.ok) {
location.reload();
} else {
const error = await response.text();
alert('Error: ' + error);
}
} catch (error) {
alert('Error deleting multi-buy: ' + error.message);
}
});
});
}); });
</script> </script>
} }

View File

@ -7,7 +7,7 @@ namespace LittleShop.Controllers;
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "Bearer")] [Authorize(AuthenticationSchemes = "Bearer,Cookies")]
public class ProductMultiBuysController : ControllerBase public class ProductMultiBuysController : ControllerBase
{ {
private readonly IProductService _productService; private readonly IProductService _productService;

View File

@ -0,0 +1,8 @@
{
"outputStyle": "mr-tickles",
"permissions": {
"allow": [
"Read(//mnt/c/Production/Source/LittleShop/**)"
]
}
}