diff --git a/src/app/Http/Controllers/EcommerceController.php b/src/app/Http/Controllers/EcommerceController.php index dbaa5bd..da876c6 100644 --- a/src/app/Http/Controllers/EcommerceController.php +++ b/src/app/Http/Controllers/EcommerceController.php @@ -258,4 +258,117 @@ public function analytics(Request $request) return response()->json(['error' => 'Failed to fetch WooCommerce data.'], 500); } } + + /** + * Customer Insights (Geographic Distribution, Retention). + */ + public function insights(Request $request) + { + $client = $this->wooClient(); + if (!$client) { + return response()->json(['error' => 'API credentials missing.'], 500); + } + + [$from, $to] = $this->dateRange($request); + + // Fetch up to 100 recent orders for insights + $params = [ + 'per_page' => 100, + 'orderby' => 'date', + 'order' => 'desc', + 'status' => ['completed', 'processing'] + ]; + + if ($from) $params['after'] = $from . 'T00:00:00'; + if ($to) $params['before'] = $to . 'T23:59:59'; + + try { + $resp = $client->get('orders', $params); + $orders = $resp->successful() ? $resp->json() : []; + + $provinces = []; + $guests = 0; + $newCustomers = 0; + $returningCustomers = 0; + + // Simple map for ZA provinces (WooCommerce standard abbreviations) + $zaProvinces = [ + 'GT' => 'Gauteng', + 'WC' => 'Western Cape', + 'KZN' => 'KwaZulu-Natal', + 'EC' => 'Eastern Cape', + 'FS' => 'Free State', + 'MP' => 'Mpumalanga', + 'NW' => 'North West', + 'NC' => 'Northern Cape', + 'NL' => 'Limpopo', + 'LP' => 'Limpopo' // Sometimes LP is used + ]; + + // Local cache to count orders per returning customer ID + $customerOrderCounts = []; + + foreach ($orders as $order) { + // Geographic + if (isset($order['billing']) && !empty($order['billing']['state'])) { + $stateCode = strtoupper($order['billing']['state']); + $stateName = $zaProvinces[$stateCode] ?? $stateCode; + + if (!isset($provinces[$stateName])) { + $provinces[$stateName] = 0; + } + $provinces[$stateName] += (float) $order['total']; + } + + // Retention + $custId = (int) $order['customer_id']; + if ($custId === 0) { + $guests++; + } else { + if (!isset($customerOrderCounts[$custId])) { + // Optimistic assumption: if we haven't seen them in this batch, + // we can check if they have previous orders (or simplistically + // assume all first occurrences in this 100 order batch are 'New', + // and subsequent are 'Returning' in the timeframe). + // Since we sort by date descending, the FIRST time we process them is their latest order. + $customerOrderCounts[$custId] = 1; + } else { + $customerOrderCounts[$custId]++; + } + } + } + + // Refine Returners: Anyone with > 1 order in this batch is considered a returner for this simplified metric. + // Ideally, you'd check WC Customer data to see 'orders_count', but this requires a separate API call per user + // To keep it fast, we estimate based on the batch. Or, better yet, we hit the customer endpoint if we have few unique customers. + // For now, let's use the batch estimation: + foreach ($customerOrderCounts as $id => $count) { + if ($count > 1) { + $returningCustomers++; + } else { + $newCustomers++; + } + } + + // Sort provinces by total ZAR DESC + arsort($provinces); + // Cap at top 6 regions + $topProvinces = array_slice($provinces, 0, 6, true); + + return response()->json([ + 'geographic' => [ + 'labels' => array_keys($topProvinces), + 'values' => array_values($topProvinces) + ], + 'retention' => [ + 'labels' => ['New Accounts', 'Returning Accounts', 'Guest Checkouts'], + 'values' => [$newCustomers, $returningCustomers, $guests] + ] + ]); + + } catch (\Exception $e) { + Log::error('WooCommerce API Error (insights): ' . $e->getMessage()); + return response()->json(['error' => 'Failed to fetch WooCommerce insights data.'], 500); + } + } } diff --git a/src/resources/views/ecommerce.blade.php b/src/resources/views/ecommerce.blade.php index 414d127..a8620ec 100644 --- a/src/resources/views/ecommerce.blade.php +++ b/src/resources/views/ecommerce.blade.php @@ -144,32 +144,6 @@
| Order # | -Customer | -Date | -Status | -Total | -
|---|