Food Ordering System Part 28 | Building an Advanced Admin Order Management Panel in ASP.NET MVC Core

Learn how to manage orders in an Admin Panel using ASP.NET MVC Core. In Part 28 of our Food Ordering System series, we implement advanced filtering by status, search by customer, date range selection, and dynamic sorting for a professional dashboard experience.

In this tutorial, we are diving deep into the Admin Controller to manage customer orders efficiently. A robust Admin Panel is the backbone of any Food Ordering System, allowing staff to track, filter, and organize orders in real-time.

Key Features Explained:

  1. Authorization & Security: The IsAdmin() check ensures that only users with administrative privileges can access the order management data.

  2. Dynamic Sorting: Using ViewBag, we toggle between ascending and descending orders for dates, totals, and statuses.

  3. Advanced Filtering:

    • Status Filter: View orders by their current stage (e.g., Pending, Delivered).

    • Search Functionality: Quickly find orders by searching for a Customer's Full Name, Username, or Email.

    • Date Range: A crucial feature for accounting, allowing admins to see orders between two specific dates.

  4. Eager Loading: We use .Include() and .ThenInclude() to pull related User and Food Item data in a single query, optimizing performance.

  5. Dashboard Statistics: We calculate real-time stats like PendingCount and TotalRevenue to give the admin an instant overview of the business health.


 

public IActionResult Orders(string status, string searchString, DateTime? fromDate, DateTime? toDate, string sortOrder)
{
    if (!IsAdmin()) return RedirectToAction("Index", "Home");

    // Store current sort order for view
    ViewBag.CurrentSort = sortOrder;
    ViewBag.DateSortParam = sortOrder == "date_asc" ? "date_desc" : "date_asc";
    ViewBag.TotalSortParam = sortOrder == "total_asc" ? "total_desc" : "total_asc";
    ViewBag.StatusSortParam = sortOrder == "status" ? "status_desc" : "status";

    var orders = _context.Orders
        .Include(o => o.User)
        .Include(o => o.OrderItems)
        .ThenInclude(oi => oi.FoodItem)
        .AsQueryable();

    // Filter by Status
    if (!string.IsNullOrEmpty(status) && status != "All")
    {
        orders = orders.Where(o => o.Status == status);
        ViewBag.CurrentStatus = status;
    }

    // Filter by Customer Name
    if (!string.IsNullOrEmpty(searchString))
    {
        orders = orders.Where(o =>
            o.User.FullName.Contains(searchString) ||
            o.User.Username.Contains(searchString) ||
            o.User.Email.Contains(searchString));
        ViewBag.CurrentSearch = searchString;
    }

    // Filter by Date Range
    if (fromDate.HasValue)
    {
        orders = orders.Where(o => o.OrderDate >= fromDate.Value);
        ViewBag.FromDate = fromDate.Value.ToString("yyyy-MM-dd");
    }
    if (toDate.HasValue)
    {
        orders = orders.Where(o => o.OrderDate <= toDate.Value.AddDays(1));
        ViewBag.ToDate = toDate.Value.ToString("yyyy-MM-dd");
    }

    // Sorting
    orders = sortOrder switch
    {
        "date_asc" => orders.OrderBy(o => o.OrderDate),
        "date_desc" => orders.OrderByDescending(o => o.OrderDate),
        "total_asc" => orders.OrderBy(o => o.TotalAmount),
        "total_desc" => orders.OrderByDescending(o => o.TotalAmount),
        "status" => orders.OrderBy(o => o.Status),
        "status_desc" => orders.OrderByDescending(o => o.Status),
        _ => orders.OrderByDescending(o => o.OrderDate) // default
    };

    // Status counts for dashboard stats
    ViewBag.PendingCount = _context.Orders.Count(o => o.Status == "Pending");
    ViewBag.ConfirmedCount = _context.Orders.Count(o => o.Status == "Confirmed");
    ViewBag.TodayCount = _context.Orders.Count(o => o.OrderDate.Date == DateTime.Today);
    ViewBag.TotalRevenue = _context.Orders.Where(o => o.Status != "Cancelled").Sum(o => (decimal?)o.TotalAmount) ?? 0;

    ViewBag.StatusList = new List<string> { "All", "Pending", "Confirmed", "Preparing", "OutForDelivery", "Delivered", "Cancelled" };

    return View(orders.ToList());
}

Code Explanation for Your Readers

In this session, we focus on the Front-end UI (Razor View) of the Admin Order Panel. This view is designed to give restaurant owners a 360-degree view of their business operations.

Key UI Components:

  1. Dashboard Stats Cards: At a glance, the admin can see Pending Orders, Confirmed Orders, Today's Sales Volume, and Total Revenue.

  2. Advanced Filtering Form: A clean horizontal form allows users to filter the order list by Status, Customer Name (Search), and Specific Date Ranges without leaving the page.

  3. Active Filter Badges: If filters are applied, dynamic "badges" appear to show what is currently being viewed, along with a "Clear All" option for better UX.

  4. Dynamic Table Sorting: The table headers (Date, Total, Status) are clickable, allowing the admin to toggle the sort order dynamically.

  5. Conditional Styling: We implemented a C# switch expression inside the Razor view to automatically assign different Bootstrap colors to order statuses (e.g., Green for Delivered, Red for Cancelled).

  6. Print & Export: Integrated buttons for printing the order list or exporting data.


@model List<Order>
@{
    ViewData["Title"] = "All Orders";
}

<div class="container-fluid my-4">
    <h2 class="mb-4"><i class="bi bi-list-check"></i> Order Management</h2>

    <!-- Stats Cards -->
    <div class="row mb-4">
        <div class="col-md-3">
            <div class="card bg-warning text-dark">
                <div class="card-body d-flex justify-content-between">
                    <div>
                        <h6>Pending Orders</h6>
                        <h3>@ViewBag.PendingCount</h3>
                    </div>
                    <i class="bi bi-clock 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>Confirmed</h6>
                        <h3>@ViewBag.ConfirmedCount</h3>
                    </div>
                    <i class="bi bi-check-circle display-4"></i>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="card bg-primary text-white">
                <div class="card-body d-flex justify-content-between">
                    <div>
                        <h6>Today's Orders</h6>
                        <h3>@ViewBag.TodayCount</h3>
                    </div>
                    <i class="bi bi-calendar-check 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>Total Revenue</h6>
                        <h3>$@ViewBag.TotalRevenue.ToString("F0")</h3>
                    </div>
                    <i class="bi bi-cash-stack 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>
    }

    @if (TempData["Error"] != null)
    {
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            @TempData["Error"]
            <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 Orders</h5>
        </div>
        <div class="card-body">
            <form method="get" asp-action="Orders" class="row g-3 align-items-end">
                <div class="col-md-2">
                    <label class="form-label">Status</label>
                    <select name="status" class="form-select">
                        <option value="All">All Status</option>
                        @foreach (var s in ViewBag.StatusList)
                        {
                            if (s != "All")
                            {
                                <option value="@s" selected="@(ViewBag.CurrentStatus == s)">@s</option>
                            }
                        }
                    </select>
                </div>
                <div class="col-md-3">
                    <label class="form-label">Search Customer</label>
                    <input type="text" name="searchString" class="form-control"
                           value="@ViewBag.CurrentSearch" placeholder="Name, Username or Email..." />
                </div>
                <div class="col-md-2">
                    <label class="form-label">From Date</label>
                    <input type="date" name="fromDate" class="form-control" value="@ViewBag.FromDate" />
                </div>
                <div class="col-md-2">
                    <label class="form-label">To Date</label>
                    <input type="date" name="toDate" class="form-control" value="@ViewBag.ToDate" />
                </div>
                <div class="col-md-2">
                    <label class="form-label">Sort By</label>
                    <select name="sortOrder" class="form-select">
                        <option value="">Newest First</option>
                        <option value="date_asc" selected="@(ViewBag.CurrentSort == "date_asc")">Oldest First</option>
                        <option value="total_desc" selected="@(ViewBag.CurrentSort == "total_desc")">Highest Amount</option>
                        <option value="total_asc" selected="@(ViewBag.CurrentSort == "total_asc")">Lowest Amount</option>
                        <option value="status" selected="@(ViewBag.CurrentSort == "status")">Status (A-Z)</option>
                    </select>
                </div>
                <div class="col-md-1">
                    <button type="submit" class="btn btn-primary w-100">
                        <i class="bi bi-search"></i>
                    </button>
                </div>
            </form>

            @if (!string.IsNullOrEmpty(ViewBag.CurrentStatus) ||
                        !string.IsNullOrEmpty(ViewBag.CurrentSearch) ||
                        ViewBag.FromDate != null ||
                        ViewBag.ToDate != null)
            {
                <div class="mt-3">
                    <span class="text-muted">Active Filters:</span>
                    @if (!string.IsNullOrEmpty(ViewBag.CurrentStatus))
                    {
                        <span class="badge bg-primary me-1">Status: @ViewBag.CurrentStatus</span>
                    }
                    @if (!string.IsNullOrEmpty(ViewBag.CurrentSearch))
                    {
                        <span class="badge bg-secondary me-1">Search: @ViewBag.CurrentSearch</span>
                    }
                    @if (ViewBag.FromDate != null)
                    {
                        <span class="badge bg-info me-1">From: @ViewBag.FromDate</span>
                    }
                    @if (ViewBag.ToDate != null)
                    {
                        <span class="badge bg-info me-1">To: @ViewBag.ToDate</span>
                    }
                    <a href="/Admin/Orders" class="btn btn-sm btn-outline-danger ms-2">
                        <i class="bi bi-x-circle"></i> Clear All
                    </a>
                </div>
            }
        </div>
    </div>

    <!-- Orders Table -->
    <div class="card shadow">
        <div class="card-header d-flex justify-content-between align-items-center">
            <span>Showing @Model.Count Orders</span>
            <div>
                <a href="#" class="btn btn-sm btn-success" onclick="window.print()">
                    <i class="bi bi-printer"></i> Print
                </a>
                <a href="#" class="btn btn-sm btn-outline-primary">
                    <i class="bi bi-download"></i> Export
                </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>Order ID</th>
                            <th>
                                <a href="@Url.Action("Orders", new { sortOrder = ViewBag.DateSortParam, status = ViewBag.CurrentStatus, searchString = ViewBag.CurrentSearch, fromDate = ViewBag.FromDate, toDate = ViewBag.ToDate })" class="text-white text-decoration-none">
                                    Date <i class="bi bi-arrow-down-up"></i>
                                </a>
                            </th>
                            <th>Customer</th>
                            <th>Contact</th>
                            <th>
                                <a href="@Url.Action("Orders", new { sortOrder = ViewBag.TotalSortParam, status = ViewBag.CurrentStatus, searchString = ViewBag.CurrentSearch, fromDate = ViewBag.FromDate, toDate = ViewBag.ToDate })" class="text-white text-decoration-none">
                                    Total <i class="bi bi-arrow-down-up"></i>
                                </a>
                            </th>
                            <th>
                                <a href="@Url.Action("Orders", new { sortOrder = ViewBag.StatusSortParam, status = ViewBag.CurrentStatus, searchString = ViewBag.CurrentSearch, fromDate = ViewBag.FromDate, toDate = ViewBag.ToDate })" class="text-white text-decoration-none">
                                    Status <i class="bi bi-arrow-down-up"></i>
                                </a>
                            </th>
                            <th>Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var order in Model)
                        {
                            <tr>
                                <td><strong>#@order.Id</strong></td>
                                <td>@order.OrderDate.ToString("MM/dd/yyyy HH:mm")</td>
                                <td>
                                    <div>@order.User?.FullName</div>
                                    <small class="text-muted">@order.User?.Username</small>
                                </td>
                                <td>@order.PhoneNumber</td>
                                <td class="fw-bold">$@order.TotalAmount.ToString("F2")</td>
                                <td>
                                    @{
                                        var badgeClass = order.Status switch
                                        {
                                            "Pending" => "bg-warning text-dark",
                                            "Confirmed" => "bg-info",
                                            "Preparing" => "bg-primary",
                                            "OutForDelivery" => "bg-secondary",
                                            "Delivered" => "bg-success",
                                            "Cancelled" => "bg-danger",
                                            _ => "bg-light text-dark"
                                        };
                                    }
                                    <span class="badge @badgeClass">@order.Status</span>
                                </td>
                                <td>
                                    <div class="btn-group" role="group">
                                        <a href="#" class="btn btn-sm btn-outline-info" title="View Details">
                                            <i class="bi bi-eye"></i>
                                        </a>
                                    </div>
                                </td>
                            </tr>
                        }
                    </tbody>
                </table>
            </div>

            @if (!Model.Any())
            {
                <div class="text-center py-5 text-muted">
                    <i class="bi bi-search display-4"></i>
                    <p class="mt-3">No orders found matching your criteria.</p>
                    <a href="/Admin/Orders" class="btn btn-outline-primary">Clear Filters</a>
                </div>
            }
        </div>
    </div>
</div>

Comments