- Added new 'Pending Payment' tab to show orders awaiting payment (4 orders)
- Rebranded admin panel from 'LittleShop Admin' to 'TeleShop Admin'
- Updated login page, layout, and dashboard with new branding
- Fixed visibility issue where PendingPayment orders had no tab
- All 13 orders are now visible across appropriate tabs
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
419 lines
24 KiB
Plaintext
419 lines
24 KiB
Plaintext
@{
|
||
ViewData["Title"] = "Order Management";
|
||
var orders = ViewData["Orders"] as IEnumerable<LittleShop.DTOs.OrderDto> ?? new List<LittleShop.DTOs.OrderDto>();
|
||
var currentTab = ViewData["CurrentTab"] as string ?? "accept";
|
||
var tabTitle = ViewData["TabTitle"] as string ?? "Orders";
|
||
|
||
var pendingCount = (int)(ViewData["PendingCount"] ?? 0);
|
||
var acceptCount = (int)(ViewData["AcceptCount"] ?? 0);
|
||
var packingCount = (int)(ViewData["PackingCount"] ?? 0);
|
||
var dispatchedCount = (int)(ViewData["DispatchedCount"] ?? 0);
|
||
var onHoldCount = (int)(ViewData["OnHoldCount"] ?? 0);
|
||
}
|
||
|
||
<div class="row mb-3">
|
||
<div class="col">
|
||
<h1 class="h3"><i class="fas fa-clipboard-list"></i> <span class="d-none d-md-inline">Order Management</span><span class="d-md-none">Orders</span></h1>
|
||
<p class="text-muted d-none d-md-block">Workflow-focused order fulfillment system</p>
|
||
</div>
|
||
<div class="col-auto">
|
||
<a href="@Url.Action("Create")" class="btn btn-primary btn-sm">
|
||
<i class="fas fa-plus"></i> <span class="d-none d-sm-inline">Create Order</span><span class="d-sm-none">New</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Workflow Tabs - Mobile Responsive -->
|
||
<ul class="nav nav-tabs mb-3 flex-nowrap overflow-auto" id="orderTabs" role="tablist" style="white-space: nowrap;">
|
||
<li class="nav-item" role="presentation">
|
||
<a class="nav-link @(currentTab == "pending" ? "active" : "")" href="@Url.Action("Index", new { tab = "pending" })">
|
||
<i class="fas fa-clock"></i>
|
||
<span class="d-none d-md-inline">Pending Payment</span>
|
||
<span class="d-md-none">Pending</span>
|
||
@if (pendingCount > 0)
|
||
{
|
||
<span class="badge bg-secondary ms-1">@pendingCount</span>
|
||
}
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<a class="nav-link @(currentTab == "accept" ? "active" : "")" href="@Url.Action("Index", new { tab = "accept" })">
|
||
<i class="fas fa-check-circle"></i>
|
||
<span class="d-none d-md-inline">Accept Orders</span>
|
||
<span class="d-md-none">Accept</span>
|
||
@if (acceptCount > 0)
|
||
{
|
||
<span class="badge bg-danger ms-1">@acceptCount</span>
|
||
}
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<a class="nav-link @(currentTab == "packing" ? "active" : "")" href="@Url.Action("Index", new { tab = "packing" })">
|
||
<i class="fas fa-box"></i>
|
||
<span class="d-none d-md-inline">Packing</span>
|
||
<span class="d-md-none">Pack</span>
|
||
@if (packingCount > 0)
|
||
{
|
||
<span class="badge bg-warning ms-1">@packingCount</span>
|
||
}
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<a class="nav-link @(currentTab == "dispatched" ? "active" : "")" href="@Url.Action("Index", new { tab = "dispatched" })">
|
||
<i class="fas fa-shipping-fast"></i>
|
||
<span class="d-none d-md-inline">Dispatched</span>
|
||
<span class="d-md-none">Ship</span>
|
||
@if (dispatchedCount > 0)
|
||
{
|
||
<span class="badge bg-info ms-1">@dispatchedCount</span>
|
||
}
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<a class="nav-link @(currentTab == "delivered" ? "active" : "")" href="@Url.Action("Index", new { tab = "delivered" })">
|
||
<i class="fas fa-check"></i>
|
||
<span class="d-none d-md-inline">Delivered</span>
|
||
<span class="d-md-none">Done</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<a class="nav-link @(currentTab == "onhold" ? "active" : "")" href="@Url.Action("Index", new { tab = "onhold" })">
|
||
<i class="fas fa-pause-circle"></i>
|
||
<span class="d-none d-md-inline">On Hold</span>
|
||
<span class="d-md-none">Hold</span>
|
||
@if (onHoldCount > 0)
|
||
{
|
||
<span class="badge bg-secondary ms-1">@onHoldCount</span>
|
||
}
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<a class="nav-link @(currentTab == "cancelled" ? "active" : "")" href="@Url.Action("Index", new { tab = "cancelled" })">
|
||
<i class="fas fa-times-circle"></i>
|
||
<span class="d-none d-md-inline">Cancelled</span>
|
||
<span class="d-md-none">Cancel</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5 class="mb-0">@tabTitle (@orders.Count())</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
@if (orders.Any())
|
||
{
|
||
<!-- Desktop Table View (hidden on mobile) -->
|
||
<div class="table-responsive d-none d-lg-block">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>Order ID</th>
|
||
<th>Customer</th>
|
||
<th>Items</th>
|
||
<th>Total</th>
|
||
<th>Status</th>
|
||
<th>Timeline</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@foreach (var order in orders)
|
||
{
|
||
<tr>
|
||
<td>
|
||
<strong>#@order.Id.ToString().Substring(0, 8)</strong>
|
||
<br><small class="text-muted">@order.CreatedAt.ToString("MMM dd, HH:mm")</small>
|
||
</td>
|
||
<td>
|
||
@if (order.Customer != null)
|
||
{
|
||
<strong>@order.Customer.DisplayName</strong>
|
||
<br><small class="text-muted">@order.Customer.CustomerType</small>
|
||
}
|
||
else
|
||
{
|
||
<strong>@order.ShippingName</strong>
|
||
<br><small class="text-muted">Anonymous</small>
|
||
}
|
||
</td>
|
||
<td>
|
||
@foreach (var item in order.Items.Take(2))
|
||
{
|
||
<div>@item.Quantity× @item.ProductName</div>
|
||
@if (!string.IsNullOrEmpty(item.ProductMultiBuyName))
|
||
{
|
||
<small class="text-muted">(@item.ProductMultiBuyName)</small>
|
||
}
|
||
}
|
||
@if (order.Items.Count > 2)
|
||
{
|
||
<small class="text-muted">+@(order.Items.Count - 2) more...</small>
|
||
}
|
||
</td>
|
||
<td>
|
||
<strong>£@order.TotalAmount</strong>
|
||
<br><small class="text-muted">@order.Currency</small>
|
||
</td>
|
||
<td>
|
||
@{
|
||
var statusClass = order.Status switch
|
||
{
|
||
LittleShop.Enums.OrderStatus.PendingPayment => "bg-warning",
|
||
LittleShop.Enums.OrderStatus.PaymentReceived => "bg-info",
|
||
LittleShop.Enums.OrderStatus.Accepted => "bg-primary",
|
||
LittleShop.Enums.OrderStatus.Packing => "bg-warning",
|
||
LittleShop.Enums.OrderStatus.Dispatched => "bg-info",
|
||
LittleShop.Enums.OrderStatus.Delivered => "bg-success",
|
||
LittleShop.Enums.OrderStatus.OnHold => "bg-secondary",
|
||
LittleShop.Enums.OrderStatus.Cancelled => "bg-danger",
|
||
_ => "bg-light"
|
||
};
|
||
}
|
||
<span class="badge @statusClass">@order.Status</span>
|
||
@if (!string.IsNullOrEmpty(order.TrackingNumber))
|
||
{
|
||
<br><small class="text-muted">@order.TrackingNumber</small>
|
||
}
|
||
</td>
|
||
<td>
|
||
<small>
|
||
@if (order.AcceptedAt.HasValue)
|
||
{
|
||
<div>✅ Accepted @order.AcceptedAt.Value.ToString("MMM dd, HH:mm")</div>
|
||
}
|
||
@if (order.PackingStartedAt.HasValue)
|
||
{
|
||
<div>📦 Packing @order.PackingStartedAt.Value.ToString("MMM dd, HH:mm")</div>
|
||
}
|
||
@if (order.DispatchedAt.HasValue)
|
||
{
|
||
<div>🚚 Dispatched @order.DispatchedAt.Value.ToString("MMM dd, HH:mm")</div>
|
||
}
|
||
@if (order.ExpectedDeliveryDate.HasValue)
|
||
{
|
||
<div class="text-muted">📅 Expected @order.ExpectedDeliveryDate.Value.ToString("MMM dd")</div>
|
||
}
|
||
@if (order.OnHoldAt.HasValue)
|
||
{
|
||
<div class="text-warning">⏸️ On Hold: @order.OnHoldReason</div>
|
||
}
|
||
</small>
|
||
</td>
|
||
<td>
|
||
<div class="btn-group btn-group-sm">
|
||
<a href="@Url.Action("Details", new { id = order.Id })" class="btn btn-outline-primary" title="View Details">
|
||
<i class="fas fa-eye"></i>
|
||
</a>
|
||
|
||
@* Workflow-specific actions *@
|
||
@if (order.Status == LittleShop.Enums.OrderStatus.PaymentReceived)
|
||
{
|
||
<form method="post" action="@Url.Action("AcceptOrder", new { id = order.Id })" class="d-inline">
|
||
@Html.AntiForgeryToken()
|
||
<button type="submit" class="btn btn-success btn-sm" title="Accept Order">
|
||
<i class="fas fa-check"></i>
|
||
</button>
|
||
</form>
|
||
}
|
||
|
||
@if (order.Status == LittleShop.Enums.OrderStatus.Accepted)
|
||
{
|
||
<form method="post" action="@Url.Action("StartPacking", new { id = order.Id })" class="d-inline">
|
||
@Html.AntiForgeryToken()
|
||
<button type="submit" class="btn btn-warning btn-sm" title="Start Packing">
|
||
<i class="fas fa-box"></i>
|
||
</button>
|
||
</form>
|
||
}
|
||
|
||
@if (order.Status != LittleShop.Enums.OrderStatus.OnHold && order.Status != LittleShop.Enums.OrderStatus.Delivered && order.Status != LittleShop.Enums.OrderStatus.Cancelled)
|
||
{
|
||
<button type="button" class="btn btn-secondary btn-sm" title="Put On Hold" data-bs-toggle="modal" data-bs-target="#holdModal-@order.Id">
|
||
<i class="fas fa-pause"></i>
|
||
</button>
|
||
}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Mobile Card View (hidden on desktop) -->
|
||
<div class="d-lg-none">
|
||
@foreach (var order in orders)
|
||
{
|
||
<div class="card mb-3 border-start border-3 @(order.Status switch {
|
||
LittleShop.Enums.OrderStatus.PaymentReceived => "border-warning",
|
||
LittleShop.Enums.OrderStatus.Accepted => "border-primary",
|
||
LittleShop.Enums.OrderStatus.Packing => "border-info",
|
||
LittleShop.Enums.OrderStatus.Dispatched => "border-success",
|
||
LittleShop.Enums.OrderStatus.OnHold => "border-secondary",
|
||
_ => "border-light"
|
||
})">
|
||
<div class="card-body">
|
||
<div class="row align-items-center">
|
||
<div class="col">
|
||
<h6 class="card-title mb-1">
|
||
<strong>#@order.Id.ToString().Substring(0, 8)</strong>
|
||
<span class="badge @(order.Status switch {
|
||
LittleShop.Enums.OrderStatus.PendingPayment => "bg-warning",
|
||
LittleShop.Enums.OrderStatus.PaymentReceived => "bg-info",
|
||
LittleShop.Enums.OrderStatus.Accepted => "bg-primary",
|
||
LittleShop.Enums.OrderStatus.Packing => "bg-warning",
|
||
LittleShop.Enums.OrderStatus.Dispatched => "bg-info",
|
||
LittleShop.Enums.OrderStatus.Delivered => "bg-success",
|
||
LittleShop.Enums.OrderStatus.OnHold => "bg-secondary",
|
||
LittleShop.Enums.OrderStatus.Cancelled => "bg-danger",
|
||
_ => "bg-light"
|
||
}) ms-2">@order.Status</span>
|
||
</h6>
|
||
|
||
<div class="small text-muted mb-2">
|
||
@if (order.Customer != null)
|
||
{
|
||
<text><strong>@order.Customer.DisplayName</strong> - @order.Customer.CustomerType</text>
|
||
}
|
||
else
|
||
{
|
||
<text><strong>@order.ShippingName</strong> - Anonymous</text>
|
||
}
|
||
</div>
|
||
|
||
<div class="small mb-2">
|
||
<strong>£@order.TotalAmount</strong>
|
||
@if (order.Items.Any())
|
||
{
|
||
var firstItem = order.Items.First();
|
||
<text> - @firstItem.Quantity x @firstItem.ProductName</text>
|
||
@if (!string.IsNullOrEmpty(firstItem.ProductMultiBuyName))
|
||
{
|
||
<span class="text-muted">(@firstItem.ProductMultiBuyName)</span>
|
||
}
|
||
@if (order.Items.Count > 1)
|
||
{
|
||
<span class="text-muted"> +@(order.Items.Count - 1) more</span>
|
||
}
|
||
}
|
||
</div>
|
||
|
||
@if (!string.IsNullOrEmpty(order.TrackingNumber))
|
||
{
|
||
<div class="small text-muted">
|
||
📦 @order.TrackingNumber
|
||
</div>
|
||
}
|
||
|
||
<!-- Timeline for mobile -->
|
||
<div class="small text-muted mt-2">
|
||
@if (order.AcceptedAt.HasValue)
|
||
{
|
||
<div>✅ @order.AcceptedAt.Value.ToString("MMM dd, HH:mm")</div>
|
||
}
|
||
@if (order.PackingStartedAt.HasValue)
|
||
{
|
||
<div>📦 @order.PackingStartedAt.Value.ToString("MMM dd, HH:mm")</div>
|
||
}
|
||
@if (order.DispatchedAt.HasValue)
|
||
{
|
||
<div>🚚 @order.DispatchedAt.Value.ToString("MMM dd, HH:mm")</div>
|
||
}
|
||
@if (order.ExpectedDeliveryDate.HasValue)
|
||
{
|
||
<div>📅 Expected @order.ExpectedDeliveryDate.Value.ToString("MMM dd")</div>
|
||
}
|
||
@if (order.OnHoldAt.HasValue)
|
||
{
|
||
<div class="text-warning">⏸️ On Hold: @order.OnHoldReason</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-auto">
|
||
<!-- Mobile Action Buttons -->
|
||
<div class="d-grid gap-1" style="min-width: 100px;">
|
||
<a href="@Url.Action("Details", new { id = order.Id })" class="btn btn-outline-primary btn-sm">
|
||
<i class="fas fa-eye"></i> View
|
||
</a>
|
||
|
||
@if (order.Status == LittleShop.Enums.OrderStatus.PaymentReceived)
|
||
{
|
||
<form method="post" action="@Url.Action("AcceptOrder", new { id = order.Id })">
|
||
@Html.AntiForgeryToken()
|
||
<button type="submit" class="btn btn-success btn-sm w-100">
|
||
<i class="fas fa-check"></i> Accept
|
||
</button>
|
||
</form>
|
||
}
|
||
|
||
@if (order.Status == LittleShop.Enums.OrderStatus.Accepted)
|
||
{
|
||
<form method="post" action="@Url.Action("StartPacking", new { id = order.Id })">
|
||
@Html.AntiForgeryToken()
|
||
<button type="submit" class="btn btn-warning btn-sm w-100">
|
||
<i class="fas fa-box"></i> Pack
|
||
</button>
|
||
</form>
|
||
}
|
||
|
||
@if (order.Status != LittleShop.Enums.OrderStatus.OnHold && order.Status != LittleShop.Enums.OrderStatus.Delivered && order.Status != LittleShop.Enums.OrderStatus.Cancelled)
|
||
{
|
||
<button type="button" class="btn btn-secondary btn-sm w-100" data-bs-toggle="modal" data-bs-target="#holdModal-@order.Id">
|
||
<i class="fas fa-pause"></i> Hold
|
||
</button>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
else
|
||
{
|
||
<div class="text-center py-4">
|
||
<i class="fas fa-clipboard-list fa-3x text-muted mb-3"></i>
|
||
<p class="text-muted">No orders found in this category.</p>
|
||
@if (currentTab == "accept")
|
||
{
|
||
<p class="text-muted">Orders will appear here when payment is received.</p>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
@* Hold Modals for each order *@
|
||
@foreach (var order in orders.Where(o => o.Status != LittleShop.Enums.OrderStatus.OnHold && o.Status != LittleShop.Enums.OrderStatus.Delivered && o.Status != LittleShop.Enums.OrderStatus.Cancelled))
|
||
{
|
||
<div class="modal fade" id="holdModal-@order.Id" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<form method="post" action="@Url.Action("PutOnHold", new { id = order.Id })">
|
||
@Html.AntiForgeryToken()
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Put Order On Hold</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<label for="reason-@order.Id" class="form-label">Reason for Hold</label>
|
||
<input name="reason" id="reason-@order.Id" class="form-control" placeholder="e.g., Awaiting stock, Customer query" required />
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="notes-@order.Id" class="form-label">Additional Notes</label>
|
||
<textarea name="notes" id="notes-@order.Id" class="form-control" rows="2" placeholder="Optional additional details..."></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="submit" class="btn btn-warning">Put On Hold</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
} |