Build a Custom WooCommerce Admin Orders Dashboard (full code)

Build a Custom WooCommerce Admin Orders Dashboard (full code)

Orders Dashboard for WooCommerce. The dashboard includes advanced filtering, status buttons, date ranges, seller search, bulk actions, CSV export, pagination, and performance-minded querying. The complete PHP template is included at the end so you can drop it into your theme and customize it.
It is fully customized WooCommerce admin panel dashboard with fully control each and every order action availability.


Why build a custom admin orders dashboard?

WooCommerce's default order list is functional, but many stores β€” especially multi-vendor or marketplace setups β€” need a more focused admin UI:

  • Friendly status labels and quick-status counts for faster triage.

  • Date range and seller filters to find orders quickly.

  • Bulk status updates and CSV export to speed up admin workflows.

  • A clean, responsive table UI that works inside a WordPress Page (so you can embed it in Elementor or theme templates).

This dashboard is designed with those needs in mind and ships with sensible defaults and security checks.


Features covered

  • Capability check (manage_woocommerce) and redirect for unauthorized users.

  • Last-login tracking for the visiting admin user (stored in user meta).

  • Safe input sanitization for all GET/POST parameters.

  • Server-side filters: status, free-text seller search, date range (with timestamps), and a default last-7-days window.

  • Bulk actions to set order statuses (with nonce protection).

  • CSV export of selected orders (UTF-8 BOM for Excel compatibility).

  • Manual seller-name filtering with pagination fallback.

  • Pretty status labels and colors.

  • Responsive table markup + JS utilities (select all, client-side validation before submit, status button UI state).

  • Clear CSS variables for easy theming.


Installation / Usage

  1. Place the file in your active theme folder (for example: wp-content/themes/your-theme/woocommerce-admin-orders.php).

  2. Create or edit a WordPress Page and select the template named Admin Orders Dashboard (it uses Template Name: Admin Orders Dashboard).

  3. Confirm you have helpers.php and widget.php (the template expects helpers.php for exports and widget.php for summary widgets). If you don't, either create them or remove the require_once lines.

  4. Test as a user with manage_woocommerce capability.

Important: always test on staging before deploying to production. Backup the database and files.


Security & hardening notes

  • The template checks current_user_can('manage_woocommerce'). Keep that β€” do not remove.

  • Nonces are used for bulk actions and export flows. When customizing, maintain wp_nonce_field and wp_verify_nonce usage.

  • The CSV export emits direct headers and php://output. Ensure this runs before other HTML output, or isolate the export into an admin-ajax endpoint.

  • If your store is high-traffic, consider turning the CSV export into a background job to avoid timeouts and memory limits.


Customization ideas

  • Add more seller metadata (display name, email) by joining to user data in your loop.

  • Support product SKU/variations in the table.

  • Add server-side caching for expensive queries.

  • Move CSV export into a dedicated admin-route (or use wp_send_json + client-side download).

  • Add role-based UI: show certain buttons only to specific roles.

Full Template β€” Copy & Paste

The complete PHP template follows. Paste into a new file in your theme and create a Page that uses the Admin Orders Dashboard template.

<?php

/**
 * Template Name: Admin Orders Dashboard
 * Template Post Type: page
 *
 * A professional WooCommerce admin dashboard with advanced filtering, bulk actions,
 * pagination, and CSV export functionality.
 *
 * @version 2.0.1 - Refactored for WooCommerce 7.x+ compatibility, enhanced filtering,
 *                and corrected bulk actions/error handling.
 */

// --- SECURITY & PERMISSIONS CHECK ---
// Ensure the current user has the appropriate capabilities.
if (!current_user_can('manage_woocommerce')) {
    wp_safe_redirect(home_url());
    exit;
}

// Track user login when they visit this page (moved to a more appropriate hook if possible)
// For this template, it runs on page load.
if (is_user_logged_in()) {
    $user_id = get_current_user_id();
    update_user_meta($user_id, 'last_login', current_time('mysql'));
}

$current_page = get_query_var('paged') ? absint(get_query_var('paged')) : (isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1);

// 2. Filter and Search Parameters
$orders_per_page = 10;
$status_filter   = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '';
$search_query    = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
$start_date      = isset($_GET['start_date']) ? sanitize_text_field($_GET['start_date']) : '';
$end_date        = isset($_GET['end_date']) ? sanitize_text_field($_GET['end_date']) : '';
$seller_name_filter = isset($_GET['seller_name']) ? sanitize_text_field($_GET['seller_name']) : '';

// --- HANDLE POST ACTIONS (BULK ACTIONS) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bulk_action_nonce'])) {
    if (wp_verify_nonce($_POST['bulk_action_nonce'], 'admin_orders_bulk_action')) {
        $action = sanitize_text_field($_POST['bulk_action'] ?? '');
        // Ensure order_ids is an array and contains integers
        $order_ids = array_map('intval', $_POST['order_ids'] ?? []);

        if ($action !== '-1' && !empty($order_ids)) {
            $updated_count = 0;
            foreach ($order_ids as $order_id) {
                $order = wc_get_order($order_id);
                if ($order && method_exists($order, 'update_status')) { // Check if update_status method exists
                    $order->update_status(str_replace('wc-', '', $action), 'Bulk action from admin dashboard.', true);
                    $updated_count++;
                }
            }
            // Redirect to prevent form resubmission and display success notice
            wp_redirect(add_query_arg('bulk_updated', $updated_count, remove_query_arg('paged', wp_get_referer() ?: get_permalink())));
            exit;
        }
    }
}

// Assuming helpers.php contains aod_export_orders_csv
// It's good practice to include it early if its functions are used before HTML output.
require_once get_template_directory() . '/helpers.php';

// --- HANDLE POST ACTIONS (CSV EXPORT) ---
if (
    isset($_POST['export_csv']) &&
    isset($_POST['bulk_action_nonce']) &&
    wp_verify_nonce($_POST['bulk_action_nonce'], 'admin_orders_bulk_action') &&
    isset($_POST['order_ids']) &&
    is_array($_POST['order_ids'])
) {
    $order_ids = array_map('intval', $_POST['order_ids']);

    if (empty($order_ids)) {
        wp_die('Please select at least one order to export.');
    }

    // Set headers for CSV download
    header('Content-Type: text/csv; charset=utf-8');
    header('Content-Disposition: attachment; filename=orders-export-' . date('Y-m-d') . '.csv');

    // Create output stream
    $output = fopen('php://output', 'w');

    // Add UTF-8 BOM for Excel compatibility
    fputs($output, "\xEF\xBB\xBF");

    // Add CSV headers
    fputcsv($output, [
        'Order ID',
        'Seller Name',
        'Status',
        'Net Profit',
        'Date Created',
        'Date Cleared'
    ]);

    // Export selected orders
    foreach ($order_ids as $order_id) {
        $order = wc_get_order($order_id);
        if ($order) {
            // Get seller name
            $customer_id = $order->get_user_id();
            $seller_name = 'Guest';
            if ($customer_id) {
                $user_info = get_userdata($customer_id);
                $seller_name = $user_info ? $user_info->user_login : 'Guest';
            }

            // Calculate net profit
            $net_profit = 0;
            foreach ($order->get_items() as $item) {
                $product = $item->get_product();
                $quantity = $item->get_quantity();

                // Get regular price
                $regular_price = $product ? floatval($product->get_regular_price()) : 0;

                // Get selling price
                $selling_price = floatval(wc_get_order_item_meta($item->get_id(), '_selling_price', true));
                if (!$selling_price && $item->get_total() > 0) {
                    $selling_price = $item->get_total() / $quantity;
                }

                $net_profit += ($selling_price - $regular_price) * $quantity;
            }

            // Get status with UI modifications
            $status = $order->get_status();
            switch ($status) {
                case 'completed':
                    $display_status = 'Paid';
                    break;
                case 'on-hold':
                    $display_status = 'Dispatched';
                    break;
                case 'failed':
                    $display_status = 'Delivered';
                    break;
                default:
                    $display_status = wc_get_order_status_name($status);
            }

            fputcsv($output, [
                $order->get_id(),
                $seller_name,
                $display_status,
                html_entity_decode(strip_tags(wc_price($net_profit))),
                $order->get_date_created()->date('Y-m-d H:i:s'),
                $order->get_date_completed() ? $order->get_date_completed()->date('Y-m-d H:i:s') : 'Not Cleared'
            ]);
        }
    }

    fclose($output);
    exit; // Important: exit after CSV export to prevent further output
}

$offset = ($current_page - 1) * $orders_per_page;
// --- BUILD QUERY ARGUMENTS FOR DISPLAYING ORDERS ---
// 3. Build Query Arguments
$args = [
    'limit'      => $orders_per_page,
    'paged'      => $current_page,  // Use paged instead of offset
    'orderby'    => 'date_created',
    'order'      => 'DESC',
    'paginate'   => true,
    'return'     => 'objects',
    'type'       => 'shop_order',
    'status'     => array_keys(wc_get_order_statuses()), // Default all statuses
];

// 4. Apply Optional Filters
if (!empty($status_filter)) {
    $args['status'] = $status_filter;
}

if (!empty($search_query)) {
    $args['search'] = '' . esc_attr($search_query) . ''; // WooCommerce search format
}

if (!empty($start_date) && !empty($end_date)) {
    $args['date_created'] = $start_date . '...' . $end_date; // Range filter
}


// Apply status filter
if (!empty($status_filter)) {
    // wc_get_orders expects 'wc-pending', 'wc-processing' etc.
    $args['status'] = ['wc-' . $status_filter];
} else {
    // If no status filter, get all except trash
    $args['status'] = array_keys(wc_get_order_statuses());
}

// Apply search query (customer details)
if (!empty($search_query)) {
    // Search in multiple customer fields
    $args['customer'] = $search_query;
    $args['customer_fields'] = array(
        'billing_first_name',
        'billing_last_name',
        'billing_company',
        'billing_email',
        'billing_phone'
    );
}

// Apply date range filter
if (!empty($start_date) || !empty($end_date)) {
    // Convert dates to timestamp for proper comparison
    $start_timestamp = !empty($start_date) ? strtotime($start_date . ' 00:00:00') : null;
    $end_timestamp = !empty($end_date) ? strtotime($end_date . ' 23:59:59') : null;

    if ($start_timestamp && $end_timestamp) {
        $args['date_created'] = $start_timestamp . '...' . $end_timestamp;
    } elseif ($start_timestamp) {
        $args['date_created'] = $start_timestamp . '...' . current_time('timestamp');
    } elseif ($end_timestamp) {
        // Start from beginning of time if no start date
        $args['date_created'] = '0...' . $end_timestamp;
    }
} else {
    // --- SHOW DATA FROM LAST 7 DAYS (DEFAULT) ---
    $seven_days_ago = strtotime('-7 days midnight');
    $today_midnight = strtotime('today 23:59:59');
    $args['date_created'] = $seven_days_ago . '...' . $today_midnight;
}

// 5. Fetch Orders
$result = wc_get_orders($args);
$orders = $result->orders;
$total_orders = $result->total;
$total_pages = $result->max_num_pages ?? ceil($total_orders / $orders_per_page);

if (!empty($seller_name_filter)) {
    // Filter orders by seller name
    $orders = array_filter($result->orders, function ($order) use ($seller_name_filter) {
        $customer_id = $order->get_user_id();
        if (!$customer_id) return false;

        $user_info = get_userdata($customer_id);
        if (!$user_info) return false;

        // Case-insensitive match for seller username
        return stripos($user_info->user_login, $seller_name_filter) !== false;
    });

    // Re-index array after filtering
    $orders = array_values($orders);

    // βœ… Manual pagination for seller-filtered results
    $total_orders = count($orders);
    $total_pages  = ceil($total_orders / $orders_per_page);

    $offset = ($current_page - 1) * $orders_per_page;
    $orders = array_slice($orders, $offset, $orders_per_page);
} else {
    // Normal WooCommerce pagination
    $orders = $result->orders;
    $total_orders = $result->total;
    $total_pages  = $result->max_num_pages;
}

// Get all order statuses for dropdowns and counts
$order_statuses = wc_get_order_statuses();

// Prepare counts for the status buttons (optional, but good for display)
$status_counts = [];
foreach ($order_statuses as $status_key => $status_name) {
    // wc_orders_count expects 'wc-pending' for the full status key
    $count_orders = wc_orders_count($status_key);
    $status_counts[$status_key] = $count_orders;
}

// === Fetch Current Logged-in User Info ===
$current_user = wp_get_current_user();
$user_id = get_current_user_id();

// Get WooCommerce default avatar
$avatar_url = get_avatar_url($user_id, ['size' => 120]);

// Use display name or fallback
$display_name = $current_user->display_name ?: 'Admin User';

// Short description or role
$description = get_user_meta($user_id, 'description', true);
if (empty($description)) {
    $description = ucfirst(implode(', ', $current_user->roles)); // e.g., "Administrator"
}

// Home page URL
$home_url = home_url('/');

// Initialize total profit/sale variables for widgets (if widgets.php uses them)
$total_admin_sale = 0;
$total_net_profit = 0;

?>

<div id="admin-orders-dashboard" class="elementor-section elementor-top-section" style="padding:20px; max-width:1420px; margin:0 auto;">
    <!-- Profile Card Bar -->
    <div style="display:flex; align-items:center; justify-content:space-between; padding:15px 20px; background:#f8f9fa; border:1px solid #ddd; border-radius:8px; margin-bottom:20px;">

        <!-- Left Section: Profile Info -->
        <div style="display:flex; align-items:center; gap:15px;">
            <!-- Profile Picture -->
            <img src="<?php echo esc_url($avatar_url); ?>" alt="Profile Picture"
                style="width:60px; height:60px; border-radius:50%; object-fit:cover; border:2px solid #ccc;">

            <!-- User Details -->
            <div>
                <h2 style="margin:0; font-size:18px; font-weight:600; color:#292B37;">
                    <?php echo esc_html($display_name); ?>
                </h2>
                <p style="margin:4px 0 0; font-size:14px; color:#666;">
                    <?php echo esc_html($description); ?>
                </p>
            </div>
        </div>

        <!-- Right Section: Home Button -->
        <div>
            <a href="<?php echo esc_url($home_url); ?>"
                style="background:#292B37; color:#fff; padding:10px 20px; border-radius:6px; text-decoration:none; font-weight:500; transition:background 0.3s ease;">
                Home
            </a>
        </div>
    </div>

    <h1 class="elementor-heading-title elementor-size-default" style="margin-bottom:20px; border-bottom:1px solid #eee; padding-bottom:10px;">Admin Overview</h1>
    <div class="dashboard-wrapper" style="padding:20px;">
    </div>
    <?php
    // Calculate total sales and profit for the widgets from the fetched orders
    foreach ($orders as $order) {
        foreach ($order->get_items() as $item) {
            $product = $item->get_product();
            $quantity = $item->get_quantity();

            // Ensure $product exists and is a WC_Product
            $regular_price = $product ? floatval($product->get_regular_price()) : 0;
            // Get selling price from item meta or calculate if not present (fallback)
            $selling_price = floatval(wc_get_order_item_meta($item->get_id(), '_selling_price', true));
            if (!$selling_price) {
                // Fallback: if _selling_price meta not found, use item total divided by quantity
                $selling_price = $item->get_total() / max(1, $quantity);
            }

            $total_admin_sale += $selling_price * $quantity;
            $total_net_profit += ($selling_price - $regular_price) * $quantity;
        }
    }
    ?>

    <?php
    // Include the widgets (widget.php needs to be able to access $total_admin_sale and $total_net_profit)
    // Ensure widget.php properly uses these variables.
    require_once get_template_directory() . '/widget.php';
    ?>


    <?php if (isset($_GET['bulk_updated'])): ?>
        <div class="elementor-alert elementor-alert-success" role="alert" style="margin-bottom:20px;">
            <?php echo sprintf('%d orders have been updated successfully.', intval($_GET['bulk_updated'])); ?>
        </div>
    <?php endif; ?>

    <!-- Separator line with theme color and spacing -->
    <hr style="border:0; height:2px; background-color:#C9AC52; margin:20px 0;">

    <!-- Unified Filter, Bulk Action & Export Bar -->
    <div class="elementor-container unified-bar"
        style="display:flex; align-items:center; justify-content:space-between; width:100%; border:1px solid #ddd; border-radius:4px; padding:6px 10px; gap:10px; white-space:nowrap; overflow-x:auto;">

        <!-- Status Filter Buttons -->
        <form id="statusFilterForm" method="GET" style="display:flex; align-items:center; gap:6px; margin:0; white-space:nowrap;">
            <input type="hidden" name="page_id" value="<?php echo get_the_ID(); ?>">
            <input type="hidden" name="paged" value="1">
            <input type="hidden" name="s" value="<?php echo esc_attr($search_query); ?>">
            <input type="hidden" name="start_date" value="<?php echo esc_attr($start_date); ?>">
            <input type="hidden" name="end_date" value="<?php echo esc_attr($end_date); ?>">

            <?php
            // "All" Button - Works as Reset
            $is_all_active = (empty($status_filter)) ? 'active-status' : '';
            ?>
            <button type="submit"
                class="status-btn <?php echo $is_all_active; ?>"
                name="status" value=""
                style="padding:6px 12px; border:1px solid #ccc; border-radius:4px; background:#f9f9f9; cursor:pointer; font-weight:300; white-space:nowrap;">
                All (<?php echo array_sum($status_counts); ?>)
            </button>

            <?php foreach ($order_statuses as $status_key => $status_name): ?>
                <?php

                if (in_array($status_key, ['wc-checkout-draft'])) continue;

                $clean_status = str_replace('wc-', '', $status_key);
                $is_active = ($status_filter === $clean_status) ? 'active-status' : '';

                // Modify the display text while keeping the backend value
                $display_name = $status_name;
                if ($clean_status === 'completed') {
                    $display_name = 'Paid';
                } elseif ($clean_status === 'on-hold') {
                    $display_name = 'Dispatched';
                } elseif ($clean_status === 'failed') {
                    $display_name = 'Delivered';
                }
                ?>
                <button type="submit"
                    class="status-btn <?php echo $is_active; ?>"
                    name="status" value="<?php echo esc_attr($clean_status); ?>"
                    style="padding:6px 12px; border:1px solid #ccc; border-radius:4px; background:#f9f9f9; cursor:pointer; font-weight:300; white-space:nowrap;">
                    <?php echo esc_html($display_name); ?> (<?php echo esc_html($status_counts[$status_key] ?? 0); ?>)
                </button>
            <?php endforeach; ?>
        </form>

        <!-- Search and Date Filter -->
        <form id="searchDateFilterForm" method="GET" style="display:flex; align-items:center; gap:6px; margin:0; white-space:nowrap;">
            <input type="hidden" name="page_id" value="<?php echo get_the_ID(); ?>">
            <input type="hidden" name="paged" value="1">
            <input type="hidden" name="status" value="<?php echo esc_attr($status_filter); ?>">

            <!-- Seller Name Search -->
            <input type="search" name="seller_name" placeholder="Search Seller..."
                value="<?php echo esc_attr($_GET['seller_name'] ?? ''); ?>"
                style="padding:6px 10px; border:1px solid #ccc; border-radius:4px; flex:0 1 auto; min-width:140px;">

            <input type="date" name="start_date" value="<?php echo esc_attr($start_date); ?>"
                style="padding:6px 10px; border:1px solid #ccc; border-radius:4px; flex:0 1 auto;">
            <input type="date" name="end_date" value="<?php echo esc_attr($end_date); ?>"
                style="padding:6px 10px; border:1px solid #ccc; border-radius:4px; flex:0 1 auto;">

            <button type="submit"
                style="background:#292B37; color:#fff; border:none; border-radius:4px; padding:6px 14px; cursor:pointer; font-weight:300;">
                Filter
            </button>
        </form>
    </div>


    <!-- Orders Table -->
    <div class="orders-table-wrapper" style="overflow-x:auto; margin-top:20px;">
        <!-- BULK ACTION FORM WRAPPER - MOVED AROUND THE ENTIRE TABLE -->
        <form method="POST" name="bulk_action_form">
            <?php wp_nonce_field('admin_orders_bulk_action', 'bulk_action_nonce'); ?>
            <input type="hidden" name="page_id" value="<?php echo get_the_ID(); ?>">

            <table class="orders-table elementor-table" style="width:100%; border-collapse:collapse;">
                <thead>
                    <tr style="background:#f8f9fa;">
                        <th><input type="checkbox" id="select-all-orders"></th>
                        <th>Order</th>
                        <th>Customer</th>
                        <th>Seller</th>
                        <th>Product Name</th>
                        <th>Quantity</th>
                        <th>Actual Price</th>
                        <th>Selling Price</th>
                        <th>Net Profit</th>
                        <th>Status</th>
                        <th>Date</th>
                        <th style="text-align:center;">View</th>
                    </tr>
                </thead>
                <tbody>
                    <?php if (!empty($orders)): ?>
                        <?php foreach ($orders as $order): ?>
                            <?php
                            // Skip if not a real order (e.g., WC_Order_Refund)
                            if (! $order instanceof WC_Order) {
                                continue;
                            }
                            // Get the WooCommerce customer (WordPress user) ID
                            $customer_id = $order->get_user_id();

                            // βœ… Get WooCommerce customer's username
                            if ($customer_id) {
                                $user_info = get_userdata($customer_id);
                                $um_username = $user_info ? $user_info->user_login : 'Guest';
                            } else {
                                $um_username = 'Guest';
                            }

                            $seller_name = $um_username;


                            $customer_name  = trim($order->get_billing_first_name() . ' ' . $order->get_billing_last_name()) ?: 'Guest';

                            $order_url      = add_query_arg('admin_order_summary', $order->get_id(), home_url('/'));
                            ?>

                            <?php foreach ($order->get_items() as $item_id => $item): ?>
                                <?php
                                $product = $item->get_product();
                                if (!$product) continue; // Skip deleted or invalid products

                                $product_name = $item->get_name();
                                $quantity = $item->get_quantity();

                                // Actual price (regular price)
                                $actual_price_single = floatval($product->get_regular_price());
                                $actual_price_total  = $actual_price_single * $quantity;

                                // Selling price (from order item meta or fallback to item total)
                                $selling_price_single = floatval(wc_get_order_item_meta($item_id, '_selling_price', true));
                                if (!$selling_price_single && $item->get_total() > 0) {
                                    $selling_price_single = $item->get_total() / $quantity;
                                } elseif (!$selling_price_single) {
                                    $selling_price_single = 0;
                                }
                                $selling_price_total = $selling_price_single * $quantity;

                                // Net profit
                                $net_profit_total = ($selling_price_single - $actual_price_single) * $quantity;
                                ?>

                                <tr style="border-bottom:1px solid #eee;">
                                    <td>
                                        <input type="checkbox" name="order_ids[]" value="<?php echo esc_attr($order->get_id()); ?>" class="order-checkbox">
                                    </td>
                                    <td>#<?php echo esc_html($order->get_id()); ?></td>
                                    <td><?php echo esc_html($customer_name); ?></td>
                                    <td><?php echo esc_html($seller_name); ?></td>
                                    <td><?php echo esc_html($product_name); ?></td>
                                    <td><?php echo esc_html($quantity); ?></td>
                                    <td>
                                        <?php echo wc_price($actual_price_total); ?>
                                        <?php if ($quantity > 1) echo ' (' . wc_price($actual_price_single) . ' x ' . $quantity . ')'; ?>
                                    </td>
                                    <td>
                                        <?php echo wc_price($selling_price_total); ?>
                                        <?php if ($quantity > 1) echo ' (' . wc_price($selling_price_single) . ' x ' . $quantity . ')'; ?>
                                    </td>
                                    <td><?php echo wc_price($net_profit_total); ?></td>
                                    <td>
                                        <span class="order-status status-<?php echo esc_attr($order->get_status()); ?>"
                                            style="padding:3px 8px; border-radius:12px; font-size:12px; font-weight:600; text-transform:capitalize; color:#fff; background:
                            <?php
                                switch ($order->get_status()) {
                                    case 'completed': // Display as Paid
                                        echo '#28a745';
                                        break;
                                    case 'processing':
                                        echo '#ffc107';
                                        break;
                                    case 'on-hold': // Display as Dispatched
                                        echo '#fd7e14';
                                        break;
                                    case 'draft': // Display as Delivered
                                        echo '#198754';
                                        break;
                                    case 'cancelled':
                                        echo '#c0392b';
                                        break;
                                    case 'refunded':
                                        echo '#17a2b8';
                                        break;
                                    case 'failed':
                                        echo '#198754';
                                        break;
                                    default:
                                        echo '#6c757d';
                                }
                            ?>;">
                                            <?php
                                            $status = $order->get_status();
                                            switch ($status) {
                                                case 'completed':
                                                    echo 'Paid';
                                                    break;
                                                case 'on-hold':
                                                    echo 'Dispatched';
                                                    break;
                                                case 'failed':
                                                    echo 'Delivered';
                                                    break;
                                                default:
                                                    echo esc_html(wc_get_order_status_name($status));
                                            }
                                            ?>
                                        </span>
                                    </td>
                                    <td><?php echo esc_html($order->get_date_created()->date('M j, Y, g:i a')); ?></td>
                                    <td style="text-align:center;">
                                        <a href="<?php echo esc_url($order_url); ?>" title="View Order" style="display:inline-block; cursor:pointer;">
                                            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#C9AC52" viewBox="0 0 24 24">
                                                <path d="M12 5c-7.633 0-12 7-12 7s4.367 7 12 7 12-7 12-7-4.367-7-12-7zm0 
                                         12c-2.761 0-5-2.239-5-5s2.239-5 5-5 
                                         5 2.239 5 5-2.239 5-5 5zm0-8c-1.657 
                                         0-3 1.343-3 3s1.343 3 3 3 
                                         3-1.343 3-3-1.343-3-3-3z" />
                                            </svg>
                                        </a>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                        <?php endforeach; ?>
                    <?php else: ?>
                        <tr>
                            <td colspan="13" style="text-align:center; padding:30px; background:#f8f9fa; border-radius:6px;">
                                No orders found for selected filters.
                            </td>
                        </tr>
                    <?php endif; ?>
                </tbody>
            </table>

            <!-- Bulk Actions + Apply + Export CSV (Moved inside the form) -->
            <div style="display:flex; align-items:center; padding:15px 0; gap:8px;">
                <select name="bulk_action"
                    style="padding:8px 12px; border:1px solid #ccc; border-radius:4px; background:#fff; font-weight:300; cursor:pointer;">
                    <option value="-1">Bulk Actions</option>
                    <?php
                    // Only show statuses that can be set as bulk actions
                    $bulk_action_statuses = array_filter(wc_get_order_statuses(), function ($key) {
                        return !in_array($key, ['']); // Exclude statuses that might not be suitable for setting directly from bulk
                    }, ARRAY_FILTER_USE_KEY);
                    // Include common status changes
                    echo '<option value="wc-pending">Pending</option>';
                    echo '<option value="wc-processing">Processing</option>';
                    echo '<option value="wc-on-hold">Dispatched</option>';
                    echo '<option value="wc-completed">Paid</option>';
                    // echo '<option value="wc-draft">Delivered</option>';
                    echo '<option value="wc-cancelled">Cancelled</option>';
                    echo '<option value="wc-refunded">Refunded</option>';
                    echo '<option value="wc-failed">Delivered</option>';
                    ?>
                </select>
                <button type="submit" name="apply_bulk_action"
                    style="background:#292B37; color:#fff; border:none; border-radius:4px; padding:8px 16px; cursor:pointer; font-weight:300;">
                    Apply
                </button>
                <button type="submit" name="export_csv" value="1"
                    style="background:#292B37; color:#fff; border:none; border-radius:4px; padding:8px 16px; cursor:pointer; font-weight:300;">
                    Export CSV
                </button>
                <?php wp_nonce_field('export_orders_csv', 'export_nonce'); ?>
            </div>
        </form>
    </div>

    <!-- Pagination -->
    <?php if ($total_pages > 1): ?>
        <div class="pagination-wrapper" style="margin-top:20px; text-align:right;">
            <?php
            // Prepare pagination arguments
            $pagination_args = [
                'base'         => trailingslashit(remove_query_arg('paged', get_permalink())) . '%_%',
                'format'       => '?paged=%#%',
                'current'      => $current_page,
                'total'        => $total_pages,
                'prev_text'    => '&laquo; Prev',
                'next_text'    => 'Next &raquo;',
                'type'         => 'list',
                'show_all'     => false,
                'end_size'     => 2,
                'mid_size'     => 2,
            ];

            // Prepare query parameters to preserve
            $query_args = array_filter([
                'status'      => $status_filter,
                's'           => $search_query,
                'start_date'  => $start_date,
                'end_date'    => $end_date,
                'seller_name' => $seller_name_filter, // βœ… Added this
            ]);


            // Add preserved parameters to pagination
            if (!empty($query_args)) {
                $pagination_args['add_args'] = $query_args;
            }

            // Render pagination links
            echo paginate_links($pagination_args);
            ?>
        </div>
    <?php endif; ?>


</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        // --- Select/Deselect All Checkboxes ---
        const selectAllCheckbox = document.getElementById('select-all-orders');
        if (selectAllCheckbox) {
            selectAllCheckbox.addEventListener('change', function() {
                const checkboxes = document.querySelectorAll('input[name="order_ids[]"]');
                checkboxes.forEach(cb => cb.checked = this.checked);
            });
        }

        // --- Bulk Action Form Submission ---
        const bulkActionForm = document.querySelector('form[name="bulk_action_form"]');
        if (bulkActionForm) {
            bulkActionForm.addEventListener('submit', function(event) {
                const selectedCheckboxes = document.querySelectorAll('.order-checkbox:checked');

                // If no orders are selected
                if (selectedCheckboxes.length === 0) {
                    // Check if this is a bulk action or export
                    if (event.submitter.name === 'export_csv') {
                        alert('Please select at least one order to export.');
                    } else if (event.submitter.name === 'apply_bulk_action') {
                        alert('Please select at least one order to apply bulk action.');
                    }
                    event.preventDefault();
                    return;
                }

                // If bulk action is selected but no action is chosen
                if (event.submitter.name === 'apply_bulk_action') {
                    const bulkAction = document.querySelector('select[name="bulk_action"]').value;
                    if (bulkAction === '-1') {
                        alert('Please select an action to apply to the selected orders.');
                        event.preventDefault();
                        return;
                    }
                }
            });
        }

        // --- Status Button Active State Styling ---
        const statusButtons = document.querySelectorAll('.status-btn');
        statusButtons.forEach(button => {
            button.addEventListener('click', function() {
                // Remove active class from all buttons
                statusButtons.forEach(btn => btn.classList.remove('active-status'));
                // Add active class to the clicked button
                this.classList.add('active-status');
            });
        });
    });
</script>


<style>
    /* --- Admin Dashboard Styles --- */
    .status-btn {
        transition: all 0.3s ease;
        background: transparent !important;
        color: #000000ff !important;
    }

    .status-btn:hover {
        background: #292B37 !important;
        color: #fff !important;
    }

    .status-btn.active-status {
        background: #C9AC52 !important;
        color: #fff !important;
        border-color: #C9AC52 !important;
    }

    :root {
        --primary-color: #2c3e50;
        --secondary-color: #292B37;
        --success-color: #27ae60;
        --danger-color: #c0392b;
        --light-gray-color: #ecf0f1;
        --medium-gray-color: #bdc3c7;
        --dark-gray-color: #7f8c8d;
        --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
        --border-radius: 6px;
    }

    #admin-orders-dashboard {
        font-family: var(--font-family);
        padding: 20px;
        max-width: 1420px;
        margin: 20px auto;
        background-color: #fff;
        border-radius: var(--border-radius);
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
    }

    #admin-orders-dashboard h1 {
        font-size: 28px;
        color: var(--primary-color);
        margin-bottom: 20px;
        border-bottom: 1px solid var(--light-gray-color);
        padding-bottom: 15px;
    }

    .dashboard-controls,
    .bulk-actions-bar {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        margin-bottom: 20px;
        align-items: center;
    }

    .dashboard-controls form {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        align-items: center;
        flex-grow: 1;
    }

    .aod-input,
    .aod-select,
    .aod-button {
        padding: 10px;
        border-radius: var(--border-radius);
        border: 1px solid var(--medium-gray-color);
        font-size: 14px;
        height: 40px;
        box-sizing: border-box;
    }

    .aod-input:focus,
    .aod-select:focus {
        border-color: var(--secondary-color);
        outline: none;
        box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
    }

    .aod-button {
        cursor: pointer;
        border: none;
        color: #fff;
        font-weight: 600;
        transition: background-color 0.2s ease, transform 0.1s ease;
    }

    .aod-button:active {
        transform: translateY(1px);
    }

    .aod-button.primary {
        background-color: var(--secondary-color);
    }

    .aod-button.success {
        background-color: var(--success-color);
    }

    .aod-button.secondary {
        background-color: var(--light-gray-color);
        color: var(--primary-color);
        border: 1px solid var(--medium-gray-color);
    }

    .orders-table-wrapper {
        overflow-x: auto;
    }

    .orders-table {
        width: 100%;
        border-collapse: collapse;
        margin-top: 0px;
    }

    .orders-table th,
    .orders-table td {
        border: 1px solid var(--light-gray-color);
        padding: 12px;
        text-align: left;
        font-size: 14px;
    }

    .orders-table th {
        background-color: #f8f9fa;
        font-weight: 600;
        color: var(--dark-gray-color);
    }

    .orders-table tr:nth-child(even) {
        background-color: #fdfdfd;
    }

    .orders-table tr:hover {
        background-color: #f1f5f8;
    }

    .order-status {
        padding: 4px 8px;
        border-radius: 15px;
        font-size: 12px;
        font-weight: 600;
        text-transform: capitalize;
        color: #fff;
        white-space: nowrap;
    }

    /* Status colors */
    .status-processing {
        background-color: #ffc107;
        color: #333;
    }

    .status-completed {
        background-color: #28a745;
    }

    .status-on-hold {
        background-color: #fd7e14;
    }

    .status-failed {
        background-color: #dc3545;
    }

    .status-cancelled {
        background-color: #6c757d;
    }

    .status-refunded {
        background-color: #17a2b8;
    }

    .status-pending {
        background-color: #6c757d;
    }

    .pagination-wrapper ul {
        display: inline-flex;
        list-style: none;
        padding: 0;
    }

    .pagination-wrapper li {
        margin: 0 5px;
    }

    .pagination-wrapper a,
    .pagination-wrapper span {
        display: block;
        padding: 8px 12px;
        border: 1px solid #ccc;
        color: #333;
        text-decoration: none;
        border-radius: 4px;
    }

    .pagination-wrapper .current {
        background: #292B37;
        color: #fff;
        border-color: #292B37;
    }


    .admin-notice {
        padding: 15px;
        margin-bottom: 20px;
        border-radius: var(--border-radius);
        border: 1px solid transparent;
        font-size: 15px;
    }

    .admin-notice.success {
        background-color: #d4edda;
        border-color: #c3e6cb;
        color: #155724;
    }

    .no-orders-found {
        text-align: center;
        padding: 40px;
        background-color: #f8f9fa;
        border: 1px dashed var(--medium-gray-color);
        border-radius: var(--border-radius);
    }
</style>

Share if you love this

And here is the widget.php code:

<?php
// --- Include helper functions ---
require_once get_template_directory() . '/helpers.php';

// ====== Fetch Data for Dashboard Widgets ======

// 1. Get WooCommerce Orders
$widget_orders = wc_get_orders([
    'status' => ['completed', 'processing', 'pending','refunded'],
    'limit'  => -1
]);

$total_admin_sale = 0;
$total_net_profit = 0;
$total_paid_profit = 0;
$completed_orders = 0;
$pending_payment = 0;
$refunded_orders = 0;
$today_revenue = 0;

foreach ($widget_orders as $order) {
    $order_date = $order->get_date_created()->format('Y-m-d');
    $order_status = $order->get_status();

    foreach ($order->get_items() as $item) {
        $product = $item->get_product();
        $quantity = $item->get_quantity();
        $regular_price = $product ? floatval($product->get_regular_price()) : 0;
        $selling_price = floatval(wc_get_order_item_meta($item->get_id(), '_selling_price', true));
        $selling_price = $selling_price ?: $item->get_total() / max(1, $quantity);
        
        // Calculate total paid profit for completed orders
        if ($order_status === 'completed') {
            $total_paid_profit += ($selling_price - $regular_price) * $quantity;
        }

        $total_admin_sale += $selling_price * $quantity;
        $total_net_profit += ($selling_price - $regular_price) * $quantity;

        // Today revenue
        if ($order_date === date('Y-m-d')) {
            $today_revenue += $item->get_total();
        }
    }

    // Count orders by status
    if ($order_status === 'completed') {
        $completed_orders++;
    } elseif ($order_status === 'pending') {
        $pending_payment++;
    } elseif ($order_status === 'refunded') {
        $refunded_orders++;
    }
}

// ===== Users Data (Last 7 Days) =====
global $wpdb;
$today = new DateTime();
$users_data = [];
$active_users_data = [];

for ($i = 6; $i >= 0; $i--) {
    $day = clone $today;
    $date = $day->modify("-$i day")->format('Y-m-d');

    // New users
    $count = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(ID) FROM {$wpdb->users} WHERE DATE(user_registered) = %s",
        $date
    ));
    $users_data[] = intval($count);

    // Active users
    $date_start = $day->format('Y-m-d 00:00:00');
    $count_active = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(user_id) FROM {$wpdb->usermeta} WHERE meta_key = '_um_last_login' AND meta_value >= %s",
        $date_start
    ));
    $active_users_data[] = intval($count_active);
}

$total_users = $wpdb->get_var("SELECT COUNT(ID) FROM {$wpdb->users}");
$active_today_users = $active_users_data[6]; // last day in array

// ===== Orders Data for the Last 7 Days =====
$orders_data = [];
$pending_payment_data = [];
$cancelled_orders_data = [];
$refunded_orders_data = [];

for ($i = 6; $i >= 0; $i--) {
    $day = clone $today;
    $date = $day->modify("-$i day")->format('Y-m-d');

    // Total orders for the day
    $daily_orders = wc_get_orders([
        'date_created' => $date,
        'limit' => -1
    ]);
    $orders_data[] = count($daily_orders);

    // Count by status
    $pending_count = 0;
    $cancelled_count = 0;
    $refunded_count = 0;

    foreach ($daily_orders as $order) {
        $status = $order->get_status();
        if ($status === 'pending') $pending_count++;
        if ($status === 'cancelled') $cancelled_count++;
        if ($status === 'refunded') $refunded_count++;
    }

    $pending_payment_data[] = $pending_count; // updated
    $cancelled_orders_data[] = $cancelled_count;
    $refunded_orders_data[] = $refunded_count;
}

// ===== Final Widgets Array =====
$widgets = [
    ['label' => 'Active Users', 'value' => $active_today_users, 'icon' => 'M16 11c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 3-1.34 3-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 2.04 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z', 'data' => $active_users_data],
    ['label' => 'Total Orders', 'value' => count($widget_orders), 'icon' => 'M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2-.9-2-2-2zM7.82 14h8.36l1.24-6H6.58l1.24 6zM18 6H6V4h12v2z', 'data' => $orders_data],
    ['label' => 'Pending Payment', 'value' => $pending_payment, 'icon' => 'M3 17h2v-7H3v7zm4 0h2v-4H7v4zm4 0h2v-9h-2v9zm4 0h2v-2h-2v2zm4 0h2v-5h-2v5z', 'data' => $pending_payment_data],
    ['label' => 'Total Paid Profit', 'value' => $total_paid_profit, 'icon' => 'M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z', 'data' => $orders_data],
    ['label' => 'Refunded Orders', 'value' => $refunded_orders, 'icon' => 'M12 1C5.925 1 1 5.925 1 12s4.925 11 11 11 11-4.925 11-11S18.075 1 12 1zm1 17h-2v-2h2v2zm1-4c0 1.105-.895 2-2 2h-2v-2h2c.552 0 1-.448 1-1s-.448-1-1-1h-2v-2h2c1.105 0 2 .895 2 2z', 'data' => $refunded_orders_data]
];
?>


<!-- ===== Load Chart.js ===== -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
    const lastSevenDays = [];
    for (let i = 6; i >= 0; i--) {
        const d = new Date();
        d.setDate(d.getDate() - i);
        lastSevenDays.push(d.toLocaleDateString('en-US', { weekday: 'short' }));
    }

    <?php foreach ($widgets as $index => $w): ?>
        <?php if (!empty($w['data'])): ?>
            const ctx<?php echo $index; ?> = document.getElementById('sparkline-<?php echo $index; ?>').getContext('2d');

            new Chart(ctx<?php echo $index; ?>, {
                type: 'line',
                data: {
                    labels: lastSevenDays,
                    datasets: [{
                        data: <?php echo json_encode($w['data']); ?>,
                        borderColor: '#C9AC52', // simple gold line
                        borderWidth: 2,
                        tension: 0.4, // smooth slope
                        pointRadius: 0,
                        fill: false // no background fill
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        legend: { display: false },
                        tooltip: {
                            enabled: true,
                            mode: 'index',
                            intersect: false,
                            backgroundColor: '#C9AC52',
                            titleColor: '#fff',
                            bodyColor: '#fff',
                            padding: 8,
                            displayColors: false,
                            titleFont: { size: 11 },
                            bodyFont: { size: 11 },
                            callbacks: {
                                title: function(tooltipItems) {
                                    return lastSevenDays[tooltipItems[0].dataIndex];
                                }
                            }
                        }
                    },
                    scales: {
                        x: { display: false },
                        y: { display: false }
                    },
                    interaction: {
                        intersect: false,
                        mode: 'index'
                    },
                    animation: {
                        duration: 600,
                        easing: 'easeOutQuart'
                    }
                }
            });
        <?php endif; ?>
    <?php endforeach; ?>
});
</script>



<!-- ===== Dashboard Widget UI ===== -->
<style>
    .dashboard-widgets {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        grid-template-rows: auto auto;
        gap: 40px;
        margin: 40px auto;
        margin-bottom: 80px;
        max-width: 1400px;
        padding: 20px;
    }

    .widget {
        display: flex;
        flex-direction: column;
        background: #ffffff;
        padding: 25px;
        border-radius: 16px;
        height: 160px;
        overflow: hidden;
        box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05),
                    0 20px 60px -10px rgba(0, 0, 0, 0.02),
                    0 1px 3px rgba(0, 0, 0, 0.05);
        transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        position: relative;
        border: 1px solid rgba(201, 172, 82, 0.08);
        width: 90%;
        transform: translateZ(0);
        margin: 0;
    }

    /* First two widgets larger */
    .widget:nth-child(1),
    .widget:nth-child(2) {
        grid-column: span 1;
        height: 200px;
    }

    /* Last three widgets span two columns each */
    .widget:nth-child(3),
    .widget:nth-child(4),
    .widget:nth-child(5) {
        grid-column: span 2;
    }

    @media (min-width: 1200px) {
        .dashboard-widgets {
            grid-template-columns: repeat(6, 1fr);
            gap: 30px;
            /* Ensure consistent spacing on larger screens */
        }

        .widget:nth-child(1),
        .widget:nth-child(2) {
            grid-column: span 3;
        }

        .widget:nth-child(3),
        .widget:nth-child(4),
        .widget:nth-child(5) {
            grid-column: span 2;
        }
    }

    /* Hover effects */
    .widget:hover {
        transform: translateY(-5px) translateZ(10px);
        box-shadow: 0 15px 35px -5px rgba(0, 0, 0, 0.08),
                    0 25px 65px -10px rgba(0, 0, 0, 0.03),
                    0 1px 3px rgba(0, 0, 0, 0.1);
        border-color: rgba(201, 172, 82, 0.2);
    }

    .widget::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        height: 3px;
        background: linear-gradient(90deg, #C9AC52, rgba(201, 172, 82, 0.3));
        opacity: 0;
        transition: opacity 0.3s ease;
    }

    .widget:hover::before {
        opacity: 1;
    }

    /* Widget header */
    .widget-header {
        display: flex;
        align-items: center;
        margin-bottom: 15px;
        position: relative;
        z-index: 1;
    }

    .widget-icon {
        margin-right: 12px;
        display: flex;
        align-items: center;
        justify-content: center;
        background: rgba(201, 172, 82, 0.1);
        padding: 6px;
        border-radius: 8px;
        transition: transform 0.3s ease;
    }

    .widget:hover .widget-icon {
        transform: scale(1.05);
    }

    .widget-icon svg {
        width: 24px;
        height: 24px;
        fill: #C9AC52;
    }

    /* Details */
    .widget-details {
        flex: 1;
    }

    .widget-label {
        display: block;
        font-size: 13px;
        color: #333;
        margin-bottom: 4px;
        text-transform: uppercase;
        letter-spacing: 0.5px;
        font-weight: 500;
    }

    .widget-value {
        font-size: 24px;
        font-weight: 600;
        color: #333;
        display: flex;
        align-items: baseline;
    }

    /* Graph */
    .widget-graph {
        flex: 1;
        position: relative;
        width: 100%;
        height: 60px;
        margin-top: 5px;
    }
</style>


<div class="dashboard-widgets">
    <?php foreach ($widgets as $index => $w): ?>
        <div class="widget">
            <div class="widget-header">
                <div class="widget-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                        <path d="<?php echo $w['icon']; ?>" />
                    </svg>
                </div>
                <div class="widget-details">
                    <span class="widget-label"><?php echo htmlspecialchars($w['label']); ?></span>
                    <span class="widget-value"><?php echo htmlspecialchars($w['value']); ?></span>
                </div>
            </div>
            <?php if (!empty($w['data'])): ?>
                <div class="widget-graph">
                    <canvas id="sparkline-<?php echo $index; ?>"></canvas>
                </div>
            <?php endif; ?>
        </div>
    <?php endforeach; ?>
</div>

Related Posts