In Part 23, we are building a high-performance Menu Management System. This
ManageMenu method is significantly more advanced than a standard list; it features a full suite of filters and a dynamic sorting engine. This allows the Admin to quickly find specific dishes, check inventory, and manage pricing across hundreds of items.Step-by-Step Code Explanation
Security & Sort Setup: After the
IsAdmin()check, the code sets upViewBagparameters for sorting. It uses a "toggle" logic—if the current sort is "name", clicking it again will change it to "name_desc".AsQueryable()for Performance: We start with_context.FoodItems.AsQueryable(). This is crucial because it allows us to build the database query in stages without actually fetching data until the very end.Advanced Filtering:
Category: Filters items belonging to a specific menu section.
Search: Checks both the Name and Description for matches using
.Contains().Availability: Filters by whether the item is currently "In Stock" or "Out of Stock."
Price Range: Implements a
minPriceandmaxPricelogic to find items within a specific budget.
The Sorting Engine: Using a C# switch expression, the code reorders the list based on the user's choice (Name, Price, or Category). The
_(underscore) acts as a default fallback, sorting by Category and then Name.Admin Statistics: Before sending the data to the view, the code calculates quick stats (Total, Available, and Unavailable items) to give the Admin a "snapshot" of the current menu status.
Final Execution: Finally,
items.ToList()is called. This is the moment the fully filtered and sorted query is executed against the SQL database.
public IActionResult ManageMenu(int? categoryId, string searchString, bool? isAvailable, decimal? minPrice, decimal? maxPrice, string sortOrder)
{
if (!IsAdmin()) return RedirectToAction("Index", "Home");
// Store sort order for view
ViewBag.CurrentSort = sortOrder;
ViewBag.NameSortParam = sortOrder == "name_desc" ? "name" : "name_desc";
ViewBag.PriceSortParam = sortOrder == "price_asc" ? "price_desc" : "price_asc";
ViewBag.CategorySortParam = sortOrder == "category" ? "category_desc" : "category";
var items = _context.FoodItems
.Include(f => f.Category)
.AsQueryable();
// Filter by Category
if (categoryId.HasValue && categoryId > 0)
{
items = items.Where(f => f.CategoryId == categoryId);
ViewBag.CurrentCategory = categoryId;
}
// Filter by Search String (Name or Description)
if (!string.IsNullOrEmpty(searchString))
{
items = items.Where(f =>
f.Name.Contains(searchString) ||
f.Description.Contains(searchString));
ViewBag.CurrentSearch = searchString;
}
// Filter by Availability
if (isAvailable.HasValue)
{
items = items.Where(f => f.IsAvailable == isAvailable.Value);
ViewBag.CurrentAvailability = isAvailable.Value;
}
// Filter by Price Range
if (minPrice.HasValue)
{
items = items.Where(f => f.Price >= minPrice.Value);
ViewBag.MinPrice = minPrice.Value;
}
if (maxPrice.HasValue)
{
items = items.Where(f => f.Price <= maxPrice.Value);
ViewBag.MaxPrice = maxPrice.Value;
}
// Sorting
items = sortOrder switch
{
"name_asc" => items.OrderBy(f => f.Name),
"name_desc" => items.OrderByDescending(f => f.Name),
"price_asc" => items.OrderBy(f => f.Price),
"price_desc" => items.OrderByDescending(f => f.Price),
"category" => items.OrderBy(f => f.Category.Name),
"category_desc" => items.OrderByDescending(f => f.Category.Name),
_ => items.OrderBy(f => f.CategoryId).ThenBy(f => f.Name)
};
// Stats for dashboard
ViewBag.TotalItems = _context.FoodItems.Count();
ViewBag.AvailableItems = _context.FoodItems.Count(f => f.IsAvailable);
ViewBag.UnavailableItems = _context.FoodItems.Count(f => !f.IsAvailable);
ViewBag.CategoryCount = _context.Categories.Count();
ViewBag.Categories = _context.Categories.ToList();
return View(items.ToList());
}
In Part 23, we are creating a professional-grade Menu Management View. This UI isn't just a simple list; it’s a full administrative dashboard featuring real-time statistics, a multi-parameter filtering system, and a secure deletion workflow using Bootstrap Modals.
Step-by-Step Code Explanation
Summary Stats: At the top, we use four color-coded cards to display the current state of the menu. This gives the Admin instant insight into total inventory and availability without scrolling through the table.
Advanced Filter Form: This section uses a
GETmethod to send search parameters (Category, Search String, Availability, and Price Range) back to ourManageMenuaction. It also includes "Active Filter" badges that show the user exactly what criteria are currently applied.Dynamic Sorting Links: The table headers are clickable. By passing the
sortOrderback to the controller, the Admin can toggle between ascending and descending order for Names, Prices, and Categories.The Data Table:
Image Handling: Includes an
onerrorfallback to a placeholder image, ensuring the UI stays clean even if a link is broken.Status Badges: Uses green/red badges to visually highlight stock status.
Responsive Wrapper: The
table-responsiveclass ensures the table remains usable on mobile and tablet devices.
Delete Confirmation Logic: To prevent accidental data loss, the "Delete" button doesn't trigger a delete immediately. It calls a JavaScript function
confirmDelete()that opens a Bootstrap Modal.Modal & Script: The script dynamically injects the specific Item ID and Name into the modal's hidden form, ensuring the correct item is deleted only after the Admin clicks the final confirmation button.
@model List<FoodItem>
@{
ViewData["Title"] = "Manage Menu";
}
<div class="container-fluid my-4">
<h2 class="mb-4"><i class="bi bi-menu-up"></i> Menu Management</h2>
<!-- Stats Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body d-flex justify-content-between">
<div>
<h6>Total Items</h6>
<h3>@ViewBag.TotalItems</h3>
</div>
<i class="bi bi-box-seam display-4"></i>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body d-flex justify-content-between">
<div>
<h6>Available</h6>
<h3>@ViewBag.AvailableItems</h3>
</div>
<i class="bi bi-check-circle display-4"></i>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-dark">
<div class="card-body d-flex justify-content-between">
<div>
<h6>Unavailable</h6>
<h3>@ViewBag.UnavailableItems</h3>
</div>
<i class="bi bi-x-circle display-4"></i>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body d-flex justify-content-between">
<div>
<h6>Categories</h6>
<h3>@ViewBag.CategoryCount</h3>
</div>
<i class="bi bi-tags display-4"></i>
</div>
</div>
</div>
</div>
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
@TempData["Success"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
<!-- Filter Section -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="bi bi-funnel"></i> Filter Menu Items</h5>
</div>
<div class="card-body">
<form method="get" asp-action="ManageMenu" class="row g-3 align-items-end">
<div class="col-md-2">
<label class="form-label">Category</label>
<select name="categoryId" class="form-select">
<option value="0">All Categories</option>
@foreach (var cat in ViewBag.Categories)
{
<option value="@cat.Id" selected="@(ViewBag.CurrentCategory == cat.Id)">@cat.Name</option>
}
</select>
</div>
<div class="col-md-3">
<label class="form-label">Search</label>
<input type="text" name="searchString" class="form-control"
value="@ViewBag.CurrentSearch" placeholder="Name or description..." />
</div>
<div class="col-md-2">
<label class="form-label">Availability</label>
<select name="isAvailable" class="form-select">
<option value="">All</option>
<option value="true" selected="@(ViewBag.CurrentAvailability == true)">Available Only</option>
<option value="false" selected="@(ViewBag.CurrentAvailability == false)">Unavailable Only</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Min Price ($)</label>
<input type="number" name="minPrice" class="form-control" step="0.01"
value="@ViewBag.MinPrice" placeholder="0.00" />
</div>
<div class="col-md-2">
<label class="form-label">Max Price ($)</label>
<input type="number" name="maxPrice" class="form-control" step="0.01"
value="@ViewBag.MaxPrice" placeholder="Max" />
</div>
<div class="col-md-1">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-search"></i>
</button>
</div>
</form>
<div class="row mt-3">
<div class="col-md-6">
@if (ViewBag.CurrentCategory != null ||
!string.IsNullOrEmpty(ViewBag.CurrentSearch) ||
ViewBag.CurrentAvailability != null ||
ViewBag.MinPrice != null ||
ViewBag.MaxPrice != null)
{
<span class="text-muted">Active Filters:</span>
@if (!string.IsNullOrEmpty(ViewBag.CurrentSearch))
{
<span class="badge bg-secondary me-1">Search: @ViewBag.CurrentSearch</span>
}
@if (ViewBag.CurrentAvailability != null)
{
<span class="badge bg-info me-1">@(ViewBag.CurrentAvailability == true ? "Available" : "Unavailable")</span>
}
@if (ViewBag.MinPrice != null)
{
<span class="badge bg-warning me-1">Min: $@ViewBag.MinPrice</span>
}
@if (ViewBag.MaxPrice != null)
{
<span class="badge bg-warning me-1">Max: $@ViewBag.MaxPrice</span>
}
<a href="/Admin/ManageMenu" class="btn btn-sm btn-outline-danger ms-2">
<i class="bi bi-x-circle"></i> Clear
</a>
}
</div>
<div class="col-md-6 text-end">
<a href="/Admin/AddFoodItem" class="btn btn-success">
<i class="bi bi-plus-circle"></i> Add New Item
</a>
</div>
</div>
</div>
</div>
<!-- Items Table -->
<div class="card shadow">
<div class="card-header d-flex justify-content-between align-items-center">
<span>Showing @Model.Count Items</span>
<div class="btn-group">
<a href="@Url.Action("ManageMenu", new {
categoryId = ViewBag.CurrentCategory,
searchString = ViewBag.CurrentSearch,
isAvailable = ViewBag.CurrentAvailability,
minPrice = ViewBag.MinPrice,
maxPrice = ViewBag.MaxPrice,
sortOrder = ViewBag.NameSortParam
})" class="btn btn-sm btn-outline-secondary">
Name <i class="bi bi-arrow-down-up"></i>
</a>
<a href="@Url.Action("ManageMenu", new {
categoryId = ViewBag.CurrentCategory,
searchString = ViewBag.CurrentSearch,
isAvailable = ViewBag.CurrentAvailability,
minPrice = ViewBag.MinPrice,
maxPrice = ViewBag.MaxPrice,
sortOrder = ViewBag.PriceSortParam
})" class="btn btn-sm btn-outline-secondary">
Price <i class="bi bi-arrow-down-up"></i>
</a>
<a href="@Url.Action("ManageMenu", new {
categoryId = ViewBag.CurrentCategory,
searchString = ViewBag.CurrentSearch,
isAvailable = ViewBag.CurrentAvailability,
minPrice = ViewBag.MinPrice,
maxPrice = ViewBag.MaxPrice,
sortOrder = ViewBag.CategorySortParam
})" class="btn btn-sm btn-outline-secondary">
Category <i class="bi bi-arrow-down-up"></i>
</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover align-middle">
<thead class="table-dark">
<tr>
<th>Image</th>
<th>Name</th>
<th>Category</th>
<th>Price</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
<img src="@item.ImageUrl" alt="@item.Name"
style="width: 60px; height: 60px; object-fit: cover;"
class="rounded"
onerror="this.src='/images/placeholder-food.jpg'" />
</td>
<td>
<strong>@item.Name</strong>
<br />
<small class="text-muted">@item.Description</small>
</td>
<td>
<span class="badge bg-secondary">@item.Category?.Name</span>
</td>
<td class="fw-bold text-primary">$@item.Price.ToString("F2")</td>
<td>
@if (item.IsAvailable)
{
<span class="badge bg-success"><i class="bi bi-check-circle"></i> Available</span>
}
else
{
<span class="badge bg-danger"><i class="bi bi-x-circle"></i> Unavailable</span>
}
</td>
<td>
<button type="button" class="btn btn-sm btn-danger"
onclick="confirmDelete(@item.Id, '@item.Name')">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title">Delete Menu Item</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete <strong id="deleteItemName"></strong>?</p>
</div>
<div class="modal-footer">
<form id="deleteForm" method="post" asp-action="DeleteFoodItem">
<input type="hidden" name="id" id="deleteItemId" />
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
function confirmDelete(id, name) {
document.getElementById('deleteItemId').value = id;
document.getElementById('deleteItemName').textContent = name;
var modal = new bootstrap.Modal(document.getElementById('deleteModal'));
modal.show();
}
</script>
}

Comments
Post a Comment