"Add-Multi-Buy-section-to-product-editor"
This commit is contained in:
parent
a9925cd61c
commit
c961dfa47a
@ -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[]
|
||||||
|
|||||||
@ -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>
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
8
TeleBot/.claude/settings.local.json
Normal file
8
TeleBot/.claude/settings.local.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"outputStyle": "mr-tickles",
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Read(//mnt/c/Production/Source/LittleShop/**)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user