From 8fd3691975afa282921dc394c9517981580efa8b Mon Sep 17 00:00:00 2001 From: dwindown Date: Tue, 11 Nov 2025 00:37:22 +0700 Subject: [PATCH] feat: Fill missing dates in charts + no-data states MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Task 1: Fill Missing Dates in Chart Data ✅ **Issue:** Charts only show dates with actual data, causing: - Gaps in timeline - Tight/crowded lines on mobile - Inconsistent X-axis **Solution:** Backend now fills ALL dates in range with zeros **Files Updated:** - `includes/Api/AnalyticsController.php` - `calculate_revenue_metrics()` - Revenue chart - `calculate_orders_metrics()` - Orders chart - `calculate_coupons_metrics()` - Coupons chart **Implementation:** ```php // Create data map from query results $data_map = []; foreach ($chart_data_raw as $row) { $data_map[$row->date] = [...]; } // Fill ALL dates in range for ($i = $days - 1; $i >= 0; $i--) { $date = date('Y-m-d', strtotime("-{$i} days")); if (isset($data_map[$date])) { // Use real data } else { // Fill with zeros } } ``` **Result:** - ✅ Consistent X-axis with all dates - ✅ No gaps in timeline - ✅ Better mobile display (evenly spaced) --- ## Task 2: No-Data States for Charts ✅ **Issue:** Charts show broken/empty state when no data **Solution:** Show friendly message like Overview does **Files Updated:** - `admin-spa/src/routes/Dashboard/Revenue.tsx` - `admin-spa/src/routes/Dashboard/Orders.tsx` - `admin-spa/src/routes/Dashboard/Coupons.tsx` **Implementation:** ```tsx {chartData.length === 0 || chartData.every(d => d.value === 0) ? (

No {type} data available

Data will appear once you have {action}

) : ( ... )} ``` **Result:** - ✅ Revenue: "No revenue data available" - ✅ Orders: "No orders data available" - ✅ Coupons: "No coupon usage data available" - ✅ Consistent with Overview page - ✅ User-friendly empty states --- ## Summary ✅ **Backend:** All dates filled in chart data ✅ **Frontend:** No-data states added to 3 charts ✅ **UX:** Consistent, professional empty states **Next:** VIP customer settings + mobile chart optimization --- admin-spa/src/routes/Dashboard/Coupons.tsx | 18 ++++- admin-spa/src/routes/Dashboard/Orders.tsx | 16 ++++- admin-spa/src/routes/Dashboard/Revenue.tsx | 18 ++++- includes/Api/AnalyticsController.php | 84 ++++++++++++++++++---- 4 files changed, 115 insertions(+), 21 deletions(-) diff --git a/admin-spa/src/routes/Dashboard/Coupons.tsx b/admin-spa/src/routes/Dashboard/Coupons.tsx index e27b3b4..6fbcef5 100644 --- a/admin-spa/src/routes/Dashboard/Coupons.tsx +++ b/admin-spa/src/routes/Dashboard/Coupons.tsx @@ -1,6 +1,6 @@ import React, { useState, useMemo } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; -import { Tag, DollarSign, TrendingUp, ShoppingCart } from 'lucide-react'; +import { Tag, DollarSign, TrendingUp, ShoppingCart, Package } from 'lucide-react'; import { __ } from '@/lib/i18n'; import { formatMoney, getStoreCurrency } from '@/lib/currency'; import { useDashboardPeriod } from '@/hooks/useDashboardPeriod'; @@ -223,8 +223,19 @@ export default function CouponsReport() { title={__('Coupon Usage Over Time')} description={__('Daily coupon usage and discount amount')} > - - + {chartData.length === 0 || chartData.every((d: any) => d.uses === 0) ? ( +
+
+ +

{__('No coupon usage data available')}

+

+ {__('Coupon data will appear once customers use coupons')} +

+
+
+ ) : ( + + + )} - - + {chartData.length === 0 || chartData.every((d: any) => d.orders === 0) ? ( +
+
+ +

{__('No orders data available')}

+

+ {__('Order data will appear once you have orders')} +

+
+
+ ) : ( + + @@ -287,6 +298,7 @@ export default function OrdersAnalytics() { /> + )}
{/* Two Column Layout */} diff --git a/admin-spa/src/routes/Dashboard/Revenue.tsx b/admin-spa/src/routes/Dashboard/Revenue.tsx index 65ebacd..c942932 100644 --- a/admin-spa/src/routes/Dashboard/Revenue.tsx +++ b/admin-spa/src/routes/Dashboard/Revenue.tsx @@ -1,6 +1,6 @@ import React, { useState, useMemo } from 'react'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; -import { DollarSign, TrendingUp, TrendingDown, CreditCard, Truck, RefreshCw } from 'lucide-react'; +import { DollarSign, TrendingUp, TrendingDown, CreditCard, Truck, RefreshCw, Package } from 'lucide-react'; import { __ } from '@/lib/i18n'; import { formatMoney, getStoreCurrency } from '@/lib/currency'; import { useDashboardPeriod } from '@/hooks/useDashboardPeriod'; @@ -396,8 +396,19 @@ export default function RevenueAnalytics() { } > - - + {chartData.length === 0 || chartData.every((d: any) => d.gross === 0 && d.net === 0) ? ( +
+
+ +

{__('No revenue data available')}

+

+ {__('Revenue data will appear once you have completed orders')} +

+
+
+ ) : ( + + @@ -467,6 +478,7 @@ export default function RevenueAnalytics() { /> + )} {/* Revenue Breakdown Tables */} diff --git a/includes/Api/AnalyticsController.php b/includes/Api/AnalyticsController.php index 210ff9f..c3eda50 100644 --- a/includes/Api/AnalyticsController.php +++ b/includes/Api/AnalyticsController.php @@ -534,20 +534,39 @@ class AnalyticsController { $total_tax = 0; $total_refunds = 0; $order_count = 0; - $chart_data = []; + // Create a map of existing data by date + $data_map = []; foreach ($chart_data_raw as $row) { - $gross = round(floatval($row->gross), 2); - $net = round(floatval($row->net), 2); - $tax = round(floatval($row->tax), 2); + $data_map[$row->date] = [ + 'gross' => round(floatval($row->gross), 2), + 'net' => round(floatval($row->net), 2), + 'tax' => round(floatval($row->tax), 2), + ]; + } + + // Fill in ALL dates in the range (including dates with no data) + $chart_data = []; + for ($i = $days - 1; $i >= 0; $i--) { + $date = date('Y-m-d', strtotime("-{$i} days")); + + if (isset($data_map[$date])) { + $gross = $data_map[$date]['gross']; + $net = $data_map[$date]['net']; + $tax = $data_map[$date]['tax']; + } else { + // No data for this date, fill with zeros + $gross = 0.00; + $net = 0.00; + $tax = 0.00; + } $total_gross += $gross; $total_net += $net; $total_tax += $tax; - // Format for frontend $chart_data[] = [ - 'date' => $row->date, + 'date' => $date, 'gross' => $gross, 'net' => $net, 'tax' => $tax, @@ -930,11 +949,10 @@ class AnalyticsController { LIMIT %d ", $days)); - // Format chart data - $formatted_chart = []; + // Format chart data - Create a map of existing data by date + $data_map = []; foreach ($chart_data as $row) { - $formatted_chart[] = [ - 'date' => $row->date, + $data_map[$row->date] = [ 'orders' => intval($row->orders), 'completed' => intval($row->completed), 'processing' => intval($row->processing), @@ -945,6 +963,28 @@ class AnalyticsController { ]; } + // Fill in ALL dates in the range (including dates with no data) + $formatted_chart = []; + for ($i = $days - 1; $i >= 0; $i--) { + $date = date('Y-m-d', strtotime("-{$i} days")); + + if (isset($data_map[$date])) { + $formatted_chart[] = array_merge(['date' => $date], $data_map[$date]); + } else { + // No data for this date, fill with zeros + $formatted_chart[] = [ + 'date' => $date, + 'orders' => 0, + 'completed' => 0, + 'processing' => 0, + 'pending' => 0, + 'cancelled' => 0, + 'refunded' => 0, + 'failed' => 0, + ]; + } + } + // Calculate average order value and rates $avg_order_value = $total_orders > 0 ? round($total_revenue / $total_orders, 2) : 0; @@ -1189,21 +1229,39 @@ class AnalyticsController { ORDER BY date ASC ", $days)); - $formatted_chart = []; + // Create a map of existing data by date + $data_map = []; $revenue_with_coupons = 0; foreach ($usage_chart as $row) { $revenue = round(floatval($row->revenue), 2); $revenue_with_coupons += $revenue; - $formatted_chart[] = [ - 'date' => $row->date, + $data_map[$row->date] = [ 'uses' => intval($row->uses), 'discount' => round(floatval($row->discount), 2), 'revenue' => $revenue, ]; } + // Fill in ALL dates in the range (including dates with no data) + $formatted_chart = []; + for ($i = $days - 1; $i >= 0; $i--) { + $date = date('Y-m-d', strtotime("-{$i} days")); + + if (isset($data_map[$date])) { + $formatted_chart[] = array_merge(['date' => $date], $data_map[$date]); + } else { + // No data for this date, fill with zeros + $formatted_chart[] = [ + 'date' => $date, + 'uses' => 0, + 'discount' => 0.00, + 'revenue' => 0.00, + ]; + } + } + $avg_discount_per_order = $total_uses > 0 ? round($total_discount / $total_uses, 2) : 0; return [