Monthly Orders Calendar with Navigation

Advanced
✓ Tested
📅 September 22, 2025
⬇️ 1 Downloads
📋 1 Copies

This advanced widget creates a full monthly calendar view showing daily order counts and revenue with navigation arrows to browse different months. Perfect for getting a visual overview of your sales patterns throughout the month.

What does this code do?

The snippet adds an interactive monthly calendar to your WordPress dashboard where each day shows the number of orders and total revenue. You can navigate between months using arrow buttons and see monthly summaries.

Calendar Features:

  • Monthly View: Full calendar layout with proper dates
  • Navigation: Previous/Next month arrows and “Today” button
  • Daily Data: Orders count and revenue for each day
  • Visual Indicators: Today highlighted, days with orders colored
  • Month Summary: Total orders, revenue, active days, daily average
  • Click Details: Click any day for detailed tooltip

Visual Elements:

  • Header: Month/year with navigation buttons
  • Calendar Grid: 7-day week layout with proper dates
  • Daily Cells: Show order count and revenue if any
  • Today Marker: Blue border around current date
  • Active Days: Light blue background for days with orders
  • Summary Row: Monthly statistics at bottom

Navigation Features:

  • ‹ Prev button – go to previous month
  • Next › button – go to next month
  • Today button – jump back to current month
  • Click any day for order details tooltip

HPOS Compatible:

  • Uses WooCommerce functions (not direct SQL)
  • Works with High Performance Order Storage
  • Compatible with traditional post-based orders

⚠️ Important Warnings

⚠️ Always backup before adding calendar widgets
⚠️ Loading large months with many orders may be slow
⚠️ Navigation uses page reload (not AJAX) for simplicity
⚠️ Tooltip requires JavaScript - fallback is visual calendar only
⚠️ Mobile view compacts information significantly
⚠️ Large stores should test performance with busy months
⚠️ Calendar shows only completed/processing/on-hold orders by default
⚠️ Widget appears for all users with dashboard access
💻 PHP Code
/**
 * Monthly orders calendar widget for WordPress dashboard
 * Shows daily orders and revenue in calendar format with navigation
 * Prefix: tpsc_ (TP Snippet Collection)
 */

// ============================================
// ADD DASHBOARD WIDGET
// ============================================

// Add calendar widget to WordPress dashboard
add_action('wp_dashboard_setup', 'tpsc_add_calendar_orders_widget');
function tpsc_add_calendar_orders_widget() {
    wp_add_dashboard_widget(
        'tpsc_calendar_orders',
        '📅 Monthly Orders Calendar',
        'tpsc_calendar_orders_widget_content'
    );
}

// ============================================
// WIDGET CONTENT
// ============================================

// Widget content function
function tpsc_calendar_orders_widget_content() {
    
    // Check if WooCommerce is active
    if (!class_exists('WooCommerce')) {
        echo '<p>WooCommerce is not active.</p>';
        return;
    }
    
    // Start with current month/year
    $current_month = date('n');
    $current_year = date('Y');
    
    echo '<div class="tpsc-calendar-container">';
    echo '<div id="tpsc-calendar-content">';
    
    // Get and render initial calendar
    $calendar_data = tpsc_get_calendar_data($current_month, $current_year);
    echo tpsc_render_calendar_header($current_month, $current_year);
    echo tpsc_render_calendar_grid($calendar_data, $current_month, $current_year);
    echo tpsc_render_month_summary($calendar_data);
    
    echo '</div>';
    echo '<div id="tpsc-calendar-loading" style="display:none;">Loading...</div>';
    echo '</div>';
}

// ============================================
// DATA COLLECTION
// ============================================

// Get calendar data for specific month
function tpsc_get_calendar_data($month, $year) {
    $data = array();
    
    // Get number of days in month
    $days_in_month = date('t', mktime(0, 0, 0, $month, 1, $year));
    
    // Loop through each day
    for ($day = 1; $day <= $days_in_month; $day++) {
        $date = sprintf('%04d-%02d-%02d', $year, $month, $day);
        
        // Get orders for this date using WooCommerce functions (HPOS compatible)
        $orders = wc_get_orders(array(
            'status' => array('completed', 'processing', 'on-hold'),
            'date_created' => $date,
            'limit' => -1,
            'return' => 'ids'
        ));
        
        $orders_count = count($orders);
        $revenue = 0;
        
        // Calculate revenue
        foreach ($orders as $order_id) {
            $order = wc_get_order($order_id);
            if ($order) {
                $revenue += $order->get_total();
            }
        }
        
        $data[$day] = array(
            'date' => $date,
            'orders' => $orders_count,
            'revenue' => $revenue,
            'formatted_revenue' => wc_price($revenue)
        );
    }
    
    return $data;
}

// ============================================
// RENDER FUNCTIONS
// ============================================

// Render calendar header with navigation
function tpsc_render_calendar_header($month, $year) {
    $month_name = date('F Y', mktime(0, 0, 0, $month, 1, $year));
    
    // Check if we can navigate (don't go to future months)
    $current_month = date('n');
    $current_year = date('Y');
    
    $can_go_next = false;
    if ($year < $current_year || ($year == $current_year && $month < $current_month)) {
        $can_go_next = true;
    }
    
    $prev_disabled = '';
    $next_disabled = $can_go_next ? '' : 'disabled';
    
    return '<div class="tpsc-calendar-header">
        <button class="tpsc-nav-btn" id="tpsc-prev-btn" onclick="tpscNavigateMonth(-1)" ' . $prev_disabled . '>‹ Prev</button>
        <h3 class="tpsc-month-title">' . $month_name . '</h3>
        <button class="tpsc-nav-btn" id="tpsc-next-btn" onclick="tpscNavigateMonth(1)" ' . $next_disabled . '>Next ›</button>
        <button class="tpsc-today-btn" onclick="tpscGoToToday()">Today</button>
    </div>';
}

// Render calendar grid
function tpsc_render_calendar_grid($data, $month, $year) {
    $output = '<div class="tpsc-calendar-grid">';
    
    // Days of week header
    $output .= '<div class="tpsc-calendar-header-row">';
    $days = array('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
    foreach ($days as $day) {
        $output .= '<div class="tpsc-day-header">' . $day . '</div>';
    }
    $output .= '</div>';
    
    // Get first day of month and what day of week it is
    $first_day = mktime(0, 0, 0, $month, 1, $year);
    $start_day_of_week = date('w', $first_day); // 0 = Sunday
    $days_in_month = date('t', $first_day);
    
    $output .= '<div class="tpsc-calendar-body">';
    
    $day_count = 1;
    
    // Calendar weeks (6 weeks max)
    for ($week = 0; $week < 6; $week++) {
        $output .= '<div class="tpsc-calendar-week">';
        
        // Days in week
        for ($day_of_week = 0; $day_of_week < 7; $day_of_week++) {
            
            // Empty cells before first day
            if ($week == 0 && $day_of_week < $start_day_of_week) {
                $output .= '<div class="tpsc-calendar-day tpsc-empty-day"></div>';
            }
            // Days of the month
            elseif ($day_count <= $days_in_month) {
                $day_data = isset($data[$day_count]) ? $data[$day_count] : array('orders' => 0, 'revenue' => 0, 'formatted_revenue' => wc_price(0));
                
                $today_class = '';
                if ($day_count == date('j') && $month == date('n') && $year == date('Y')) {
                    $today_class = ' tpsc-today';
                }
                
                $has_orders_class = $day_data['orders'] > 0 ? ' tpsc-has-orders' : '';
                
                $output .= '<div class="tpsc-calendar-day' . $today_class . $has_orders_class . '" data-date="' . $day_data['date'] . '">';
                $output .= '<div class="tpsc-day-number">' . $day_count . '</div>';
                
                if ($day_data['orders'] > 0) {
                    $output .= '<div class="tpsc-day-orders">' . $day_data['orders'] . ' orders</div>';
                    $output .= '<div class="tpsc-day-revenue">' . $day_data['formatted_revenue'] . '</div>';
                }
                
                $output .= '</div>';
                $day_count++;
            }
            // Empty cells after last day
            else {
                $output .= '<div class="tpsc-calendar-day tpsc-empty-day"></div>';
            }
        }
        
        $output .= '</div>';
        
        // Stop if we've shown all days
        if ($day_count > $days_in_month) {
            break;
        }
    }
    
    $output .= '</div></div>';
    
    return $output;
}

// Render month summary
function tpsc_render_month_summary($data) {
    $total_orders = 0;
    $total_revenue = 0;
    $days_with_orders = 0;
    
    foreach ($data as $day_data) {
        $total_orders += $day_data['orders'];
        $total_revenue += $day_data['revenue'];
        if ($day_data['orders'] > 0) {
            $days_with_orders++;
        }
    }
    
    $avg_daily_orders = $days_with_orders > 0 ? round($total_orders / $days_with_orders, 1) : 0;
    
    return '<div class="tpsc-month-summary">
        <div class="tpsc-summary-item">
            <span class="tpsc-summary-label">Total Orders:</span>
            <span class="tpsc-summary-value">' . number_format($total_orders) . '</span>
        </div>
        <div class="tpsc-summary-item">
            <span class="tpsc-summary-label">Total Revenue:</span>
            <span class="tpsc-summary-value">' . wc_price($total_revenue) . '</span>
        </div>
        <div class="tpsc-summary-item">
            <span class="tpsc-summary-label">Active Days:</span>
            <span class="tpsc-summary-value">' . $days_with_orders . '</span>
        </div>
        <div class="tpsc-summary-item">
            <span class="tpsc-summary-label">Avg/Day:</span>
            <span class="tpsc-summary-value">' . $avg_daily_orders . '</span>
        </div>
    </div>';
}

// ============================================
// AJAX HANDLERS
// ============================================

// AJAX handler for calendar navigation
add_action('wp_ajax_tpsc_load_calendar_month', 'tpsc_ajax_load_calendar_month');
function tpsc_ajax_load_calendar_month() {
    // Check permissions
    if (!current_user_can('read')) {
        wp_die('Unauthorized');
    }
    
    // Check if WooCommerce is active
    if (!class_exists('WooCommerce')) {
        wp_send_json_error('WooCommerce is not active');
    }
    
    // Get and validate parameters
    $month = isset($_POST['month']) ? intval($_POST['month']) : date('n');
    $year = isset($_POST['year']) ? intval($_POST['year']) : date('Y');
    
    // Validate month/year
    if ($month < 1 || $month > 12) {
        wp_send_json_error('Invalid month');
    }
    if ($year < 2020 || $year > date('Y')) {
        wp_send_json_error('Invalid year');
    }
    
    // Don't allow future months
    $current_month = date('n');
    $current_year = date('Y');
    if ($year > $current_year || ($year == $current_year && $month > $current_month)) {
        wp_send_json_error('Cannot view future months');
    }
    
    // Get calendar data
    $calendar_data = tpsc_get_calendar_data($month, $year);
    
    // Generate HTML
    $html = tpsc_render_calendar_header($month, $year);
    $html .= tpsc_render_calendar_grid($calendar_data, $month, $year);
    $html .= tpsc_render_month_summary($calendar_data);
    
    wp_send_json_success(array(
        'html' => $html,
        'month' => $month,
        'year' => $year,
        'data' => $calendar_data
    ));
}

// Add widget styles and scripts
add_action('admin_enqueue_scripts', 'tpsc_calendar_widget_assets');
function tpsc_calendar_widget_assets($hook) {
    
    // Only load on dashboard
    if ($hook !== 'index.php') {
        return;
    }
    
    // Calendar CSS
    wp_add_inline_style('wp-admin', '
        .tpsc-calendar-container {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        }
        
        .tpsc-calendar-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding: 10px 0;
            border-bottom: 1px solid #ddd;
        }
        
        .tpsc-month-title {
            margin: 0;
            font-size: 18px;
            font-weight: 600;
            color: #1d2327;
        }
        
        .tpsc-nav-btn, .tpsc-today-btn {
            background: #2271b1;
            color: white;
            border: none;
            padding: 6px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            transition: background 0.2s;
        }
        
        .tpsc-nav-btn:hover, .tpsc-today-btn:hover {
            background: #135e96;
        }
        
        .tpsc-today-btn {
            background: #00a32a;
            margin-left: 10px;
        }
        
        .tpsc-today-btn:hover {
            background: #008a20;
        }
        
        .tpsc-nav-btn:disabled {
            background: #ddd !important;
            color: #999 !important;
            cursor: not-allowed !important;
        }
        
        .tpsc-nav-btn:disabled:hover {
            background: #ddd !important;
        }
        
        #tpsc-calendar-loading {
            text-align: center;
            padding: 40px;
            color: #666;
            font-style: italic;
        }
        
        .tpsc-calendar-fade {
            opacity: 0.5;
            pointer-events: none;
            transition: opacity 0.3s ease;
        }
        
        .tpsc-calendar-grid {
            border: 1px solid #ddd;
            border-radius: 6px;
            overflow: hidden;
        }
        
        .tpsc-calendar-header-row {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            background: #f6f7f7;
        }
        
        .tpsc-day-header {
            padding: 10px 5px;
            text-align: center;
            font-weight: 600;
            font-size: 12px;
            color: #50575e;
            border-right: 1px solid #ddd;
        }
        
        .tpsc-day-header:last-child {
            border-right: none;
        }
        
        .tpsc-calendar-body {
            background: white;
        }
        
        .tpsc-calendar-week {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            border-bottom: 1px solid #ddd;
        }
        
        .tpsc-calendar-week:last-child {
            border-bottom: none;
        }
        
        .tpsc-calendar-day {
            min-height: 80px;
            padding: 8px;
            border-right: 1px solid #ddd;
            position: relative;
            background: white;
            transition: background 0.2s;
        }
        
        .tpsc-calendar-day:last-child {
            border-right: none;
        }
        
        .tpsc-calendar-day:hover {
            background: #f8f9fa;
        }
        
        .tpsc-empty-day {
            background: #f9f9f9;
        }
        
        .tpsc-today {
            background: #e7f3ff !important;
            border: 2px solid #2271b1 !important;
        }
        
        .tpsc-has-orders {
            background: #f0f9ff;
        }
        
        .tpsc-day-number {
            font-weight: 600;
            font-size: 14px;
            color: #1d2327;
            margin-bottom: 4px;
        }
        
        .tpsc-today .tpsc-day-number {
            color: #2271b1;
        }
        
        .tpsc-day-orders {
            font-size: 11px;
            color: #2271b1;
            font-weight: 600;
            margin-bottom: 2px;
        }
        
        .tpsc-day-revenue {
            font-size: 10px;
            color: #50575e;
            font-weight: 500;
        }
        
        .tpsc-month-summary {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 10px;
            margin-top: 15px;
            padding: 15px;
            background: #f8f9fa;
            border-radius: 6px;
        }
        
        .tpsc-summary-item {
            text-align: center;
        }
        
        .tpsc-summary-label {
            display: block;
            font-size: 11px;
            color: #50575e;
            margin-bottom: 4px;
            font-weight: 500;
        }
        
        .tpsc-summary-value {
            display: block;
            font-size: 16px;
            font-weight: 600;
            color: #1d2327;
        }
        
        /* Mobile responsive */
        @media (max-width: 768px) {
            .tpsc-calendar-day {
                min-height: 60px;
                padding: 4px;
            }
            
            .tpsc-day-number {
                font-size: 12px;
            }
            
            .tpsc-day-orders {
                font-size: 9px;
            }
            
            .tpsc-day-revenue {
                font-size: 8px;
            }
            
            .tpsc-month-summary {
                grid-template-columns: repeat(2, 1fr);
            }
            
            .tpsc-calendar-header {
                flex-wrap: wrap;
                gap: 10px;
            }
            
            .tpsc-month-title {
                order: -1;
                width: 100%;
                text-align: center;
            }
        }
    ');
    
    // Calendar JavaScript
    wp_add_inline_script('jquery', '
        // Global variables
        window.tpscCurrentMonth = ' . date('n') . ';
        window.tpscCurrentYear = ' . date('Y') . ';
        window.tpscCalendarData = {};
        window.tpscIsLoading = false;
        
        // Make functions global
        window.tpscNavigateMonth = function(direction) {
            if (window.tpscIsLoading) return;
            
            var newMonth = window.tpscCurrentMonth + direction;
            var newYear = window.tpscCurrentYear;
            
            if (newMonth < 1) {
                newMonth = 12;
                newYear--;
            } else if (newMonth > 12) {
                newMonth = 1;
                newYear++;
            }
            
            // Don\'t allow future months
            var currentRealMonth = new Date().getMonth() + 1;
            var currentRealYear = new Date().getFullYear();
            
            if (newYear > currentRealYear || (newYear == currentRealYear && newMonth > currentRealMonth)) {
                return; // Block navigation to future
            }
            
            tpscLoadCalendarMonth(newMonth, newYear);
        };
        
        window.tpscGoToToday = function() {
            if (window.tpscIsLoading) return;
            
            var today = new Date();
            var currentMonth = today.getMonth() + 1;
            var currentYear = today.getFullYear();
            
            tpscLoadCalendarMonth(currentMonth, currentYear);
        };
        
        function tpscLoadCalendarMonth(month, year) {
            if (window.tpscIsLoading) return;
            
            window.tpscIsLoading = true;
            
            // Show loading state
            var content = document.getElementById("tpsc-calendar-content");
            var loading = document.getElementById("tpsc-calendar-loading");
            
            if (content) content.classList.add("tpsc-calendar-fade");
            if (loading) loading.style.display = "block";
            
            // AJAX request using jQuery
            jQuery.ajax({
                url: ajaxurl,
                type: "POST",
                data: {
                    action: "tpsc_load_calendar_month",
                    month: month,
                    year: year
                },
                success: function(response) {
                    window.tpscIsLoading = false;
                    if (loading) loading.style.display = "none";
                    
                    if (response.success) {
                        // Update calendar
                        if (content) {
                            content.innerHTML = response.data.html;
                            content.classList.remove("tpsc-calendar-fade");
                        }
                        
                        // Update global variables
                        window.tpscCurrentMonth = response.data.month;
                        window.tpscCurrentYear = response.data.year;
                        window.tpscCalendarData = response.data.data;
                        
                        // Re-add event handlers
                        tpscAddDayClickHandlers();
                    } else {
                        alert("Error: " + (response.data || "Unknown error"));
                        if (content) content.classList.remove("tpsc-calendar-fade");
                    }
                },
                error: function() {
                    window.tpscIsLoading = false;
                    if (loading) loading.style.display = "none";
                    if (content) content.classList.remove("tpsc-calendar-fade");
                    alert("Network error");
                }
            });
        }
        
        function tpscAddDayClickHandlers() {
            const calendarDays = document.querySelectorAll(".tpsc-calendar-day[data-date]");
            
            calendarDays.forEach(function(day) {
                day.addEventListener("click", function() {
                    const date = this.getAttribute("data-date");
                    tpscShowDayDetails(date, this);
                });
            });
        }
        
        function tpscShowDayDetails(date, element) {
            // Remove existing tooltips
            const existingTooltips = document.querySelectorAll(".tpsc-day-tooltip");
            existingTooltips.forEach(tooltip => tooltip.remove());
            
            // Get day data
            if (!window.tpscCalendarData) return;
            
            const day = parseInt(date.split("-")[2]);
            const dayData = window.tpscCalendarData[day];
            
            if (!dayData || dayData.orders === 0) return;
            
            // Create tooltip
            const tooltip = document.createElement("div");
            tooltip.className = "tpsc-day-tooltip";
            tooltip.innerHTML = `
                <strong>${date}</strong><br>
                Orders: ${dayData.orders}<br>
                Revenue: ${dayData.formatted_revenue}
            `;
            
            // Style tooltip
            tooltip.style.cssText = `
                position: absolute;
                background: #1d2327;
                color: white;
                padding: 10px;
                border-radius: 4px;
                font-size: 12px;
                z-index: 1000;
                box-shadow: 0 2px 8px rgba(0,0,0,0.3);
                pointer-events: none;
                white-space: nowrap;
            `;
            
            // Position tooltip
            document.body.appendChild(tooltip);
            
            const rect = element.getBoundingClientRect();
            tooltip.style.left = (rect.left + window.scrollX + rect.width / 2 - tooltip.offsetWidth / 2) + "px";
            tooltip.style.top = (rect.top + window.scrollY - tooltip.offsetHeight - 10) + "px";
            
            // Remove tooltip after 3 seconds
            setTimeout(() => {
                tooltip.remove();
            }, 3000);
        }
        
        // Initialize on page load
        jQuery(document).ready(function() {
            tpscAddDayClickHandlers();
        });
    ');
}

📝 Installation Instructions

QUICK START:

Copy the entire code
Paste in functions.php
Go to WordPress Dashboard
See your monthly orders calendar with navigation!

NAVIGATION:

‹ Prev: Go to previous month
Next ›: Go to next month
Today: Jump back to current month
Click Day: Show detailed tooltip

CUSTOMIZING COLORS:
Today highlight: Find background: #e7f3ff; border: 2px solid #2271b1;
Days with orders: Find background: #f0f9ff;
Navigation buttons: Find background: #2271b1;
CHANGING ORDER STATUSES:
Find: 'status' => array('completed', 'processing', 'on-hold')
Add/remove statuses:
'status' => array('completed', 'processing', 'on-hold', 'pending')
TOOLTIP CUSTOMIZATION:
Tooltip appears when clicking days with orders.
To change tooltip style, modify the tooltip.style.cssText section.
MOBILE RESPONSIVENESS:
Calendar automatically adjusts for mobile:

Smaller day cells
2-column summary instead of 4
Responsive navigation buttons

SUMMARY METRICS:
Bottom section shows:

Total Orders: Month total
Total Revenue: Month total
Active Days: Days with orders
Avg/Day: Average orders per active day

PERFORMANCE:

Loads one month at a time
Uses WooCommerce caching when available
Minimal database queries

YEAR RANGE:
Currently limited to 2020-2030 for safety.
To change: Modify the validation in widget_content function.
TESTING:

Create orders on different days
Navigate between months
Check that today is highlighted
Click days with orders for tooltips
Verify summary calculations

📸 Screenshots

💬 Let's Connect and Share!

Got a question about this snippet?

Something not working as expected? Need help customizing it for your specific needs?

💡

Want to request a custom snippet?

Have an idea for functionality you're missing on your site? Tell us what you're looking for!

🤝

Have an awesome snippet you're using?

Share it with us! Our community loves learning and growing together.

No comments yet.

Leave a Comment

Leave a Comment

To leave a comment, please enter your email address. We will send you a verification code to confirm your identity.

Email Verification

We sent a 6-digit verification code to your email address. Please check your inbox and enter the code below:

Write Your Comment

Great! Your email is verified. Now you can write your comment:

Comment Submitted!