feat: Mobile chart optimization + VIP customer settings
## Task 4: Mobile Chart Optimization ✅ **Problem:** Too many data points = tight/crowded lines on mobile **Solution:** Horizontal scroll container **Implementation:** - ChartCard component enhanced with mobile scroll - Calculates minimum width based on data points (40px per point) - Desktop: Full width responsive - Mobile: Fixed width chart in scrollable container ```tsx // ChartCard.tsx const mobileMinWidth = Math.max(600, dataPoints * 40); <div className="overflow-x-auto -mx-6 px-6 md:mx-0 md:px-0"> <div style={{ minWidth: `${mobileMinWidth}px` }}> {children} </div> </div> ``` **Benefits:** - ✅ All data visible (no loss) - ✅ Natural swipe gesture - ✅ Readable spacing - ✅ Works for all chart types - ✅ No data aggregation needed --- ## Task 5: VIP Customer Settings ✅ **New Feature:** Configure VIP customer qualification criteria ### Backend (PHP) **Files Created:** - `includes/Compat/CustomerSettingsProvider.php` - VIP settings management - VIP detection logic - Customer stats calculation **API Endpoints:** - `GET /store/customer-settings` - Fetch settings - `POST /store/customer-settings` - Save settings **Settings:** ```php woonoow_vip_min_spent = 1000 woonoow_vip_min_orders = 10 woonoow_vip_timeframe = 'all' | '30' | '90' | '365' woonoow_vip_require_both = true woonoow_vip_exclude_refunded = true ``` **VIP Detection:** ```php CustomerSettingsProvider::is_vip_customer($customer_id) CustomerSettingsProvider::get_vip_stats($customer_id) ``` ### Frontend (React) **Files Created:** - `admin-spa/src/routes/Settings/Customers.tsx` **Features:** - 💰 Minimum total spent (currency input) - �� Minimum order count (number input) - 📅 Timeframe selector (all-time, 30/90/365 days) - ⚙️ Require both criteria toggle - 🚫 Exclude refunded orders toggle - 👑 Live preview of VIP qualification **Navigation:** - Added to Settings menu - Route: `/settings/customers` - Position: After Tax, before Notifications --- ## Summary ✅ **Mobile Charts:** Horizontal scroll for readable spacing ✅ **VIP Settings:** Complete backend + frontend implementation **Mobile Chart Strategy:** - Minimum 600px width - 40px per data point - Smooth horizontal scroll - Desktop remains responsive **VIP Customer System:** - Flexible qualification criteria - Multiple timeframes - AND/OR logic support - Refunded order exclusion - Ready for customer list integration **All tasks complete!** 🎉
This commit is contained in:
187
includes/Compat/CustomerSettingsProvider.php
Normal file
187
includes/Compat/CustomerSettingsProvider.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/**
|
||||
* Customer Settings Provider
|
||||
*
|
||||
* Provides customer-related settings including VIP qualification criteria.
|
||||
*
|
||||
* @package WooNooW
|
||||
*/
|
||||
|
||||
namespace WooNooW\Compat;
|
||||
|
||||
class CustomerSettingsProvider {
|
||||
|
||||
/**
|
||||
* Get customer settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_settings() {
|
||||
return [
|
||||
// VIP Customer Qualification
|
||||
'vip_min_spent' => floatval(get_option('woonoow_vip_min_spent', 1000)),
|
||||
'vip_min_orders' => intval(get_option('woonoow_vip_min_orders', 10)),
|
||||
'vip_timeframe' => get_option('woonoow_vip_timeframe', 'all'), // all, 30, 90, 365
|
||||
'vip_require_both' => get_option('woonoow_vip_require_both', 'yes') === 'yes',
|
||||
'vip_exclude_refunded' => get_option('woonoow_vip_exclude_refunded', 'yes') === 'yes',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update customer settings
|
||||
*
|
||||
* @param array $settings
|
||||
* @return bool
|
||||
*/
|
||||
public static function update_settings($settings) {
|
||||
$updated = true;
|
||||
|
||||
// VIP settings
|
||||
if (isset($settings['vip_min_spent'])) {
|
||||
$updated = $updated && update_option('woonoow_vip_min_spent', floatval($settings['vip_min_spent']));
|
||||
}
|
||||
|
||||
if (isset($settings['vip_min_orders'])) {
|
||||
$updated = $updated && update_option('woonoow_vip_min_orders', intval($settings['vip_min_orders']));
|
||||
}
|
||||
|
||||
if (isset($settings['vip_timeframe'])) {
|
||||
$timeframe = in_array($settings['vip_timeframe'], ['all', '30', '90', '365'])
|
||||
? $settings['vip_timeframe']
|
||||
: 'all';
|
||||
$updated = $updated && update_option('woonoow_vip_timeframe', $timeframe);
|
||||
}
|
||||
|
||||
if (isset($settings['vip_require_both'])) {
|
||||
$updated = $updated && update_option('woonoow_vip_require_both', $settings['vip_require_both'] ? 'yes' : 'no');
|
||||
}
|
||||
|
||||
if (isset($settings['vip_exclude_refunded'])) {
|
||||
$updated = $updated && update_option('woonoow_vip_exclude_refunded', $settings['vip_exclude_refunded'] ? 'yes' : 'no');
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a customer is VIP based on current settings
|
||||
*
|
||||
* @param int $customer_id
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_vip_customer($customer_id) {
|
||||
if (!$customer_id || $customer_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$settings = self::get_settings();
|
||||
|
||||
// Build query args
|
||||
$query_args = [
|
||||
'customer_id' => $customer_id,
|
||||
'status' => ['wc-completed', 'wc-processing'],
|
||||
'limit' => -1, // Get all orders
|
||||
];
|
||||
|
||||
// Apply timeframe filter
|
||||
if ($settings['vip_timeframe'] !== 'all') {
|
||||
$days = intval($settings['vip_timeframe']);
|
||||
$query_args['date_created'] = '>' . date('Y-m-d', strtotime("-{$days} days"));
|
||||
}
|
||||
|
||||
// Exclude refunded orders if setting is enabled
|
||||
if ($settings['vip_exclude_refunded']) {
|
||||
$query_args['status'] = array_diff($query_args['status'], ['wc-refunded']);
|
||||
}
|
||||
|
||||
// Get orders
|
||||
$orders = wc_get_orders($query_args);
|
||||
|
||||
// Calculate totals
|
||||
$total_spent = 0;
|
||||
$order_count = count($orders);
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$total_spent += floatval($order->get_total());
|
||||
}
|
||||
|
||||
// Check qualification
|
||||
$meets_spent = $total_spent >= $settings['vip_min_spent'];
|
||||
$meets_orders = $order_count >= $settings['vip_min_orders'];
|
||||
|
||||
if ($settings['vip_require_both']) {
|
||||
// Must meet both criteria
|
||||
return $meets_spent && $meets_orders;
|
||||
} else {
|
||||
// Must meet at least one criterion
|
||||
return $meets_spent || $meets_orders;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VIP customer stats for a customer
|
||||
*
|
||||
* @param int $customer_id
|
||||
* @return array
|
||||
*/
|
||||
public static function get_vip_stats($customer_id) {
|
||||
if (!$customer_id || $customer_id <= 0) {
|
||||
return [
|
||||
'is_vip' => false,
|
||||
'total_spent' => 0,
|
||||
'order_count' => 0,
|
||||
'meets_spent' => false,
|
||||
'meets_orders' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$settings = self::get_settings();
|
||||
|
||||
// Build query args
|
||||
$query_args = [
|
||||
'customer_id' => $customer_id,
|
||||
'status' => ['wc-completed', 'wc-processing'],
|
||||
'limit' => -1,
|
||||
];
|
||||
|
||||
// Apply timeframe filter
|
||||
if ($settings['vip_timeframe'] !== 'all') {
|
||||
$days = intval($settings['vip_timeframe']);
|
||||
$query_args['date_created'] = '>' . date('Y-m-d', strtotime("-{$days} days"));
|
||||
}
|
||||
|
||||
// Exclude refunded if enabled
|
||||
if ($settings['vip_exclude_refunded']) {
|
||||
$query_args['status'] = array_diff($query_args['status'], ['wc-refunded']);
|
||||
}
|
||||
|
||||
// Get orders
|
||||
$orders = wc_get_orders($query_args);
|
||||
|
||||
// Calculate totals
|
||||
$total_spent = 0;
|
||||
$order_count = count($orders);
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$total_spent += floatval($order->get_total());
|
||||
}
|
||||
|
||||
// Check criteria
|
||||
$meets_spent = $total_spent >= $settings['vip_min_spent'];
|
||||
$meets_orders = $order_count >= $settings['vip_min_orders'];
|
||||
|
||||
$is_vip = $settings['vip_require_both']
|
||||
? ($meets_spent && $meets_orders)
|
||||
: ($meets_spent || $meets_orders);
|
||||
|
||||
return [
|
||||
'is_vip' => $is_vip,
|
||||
'total_spent' => $total_spent,
|
||||
'order_count' => $order_count,
|
||||
'meets_spent' => $meets_spent,
|
||||
'meets_orders' => $meets_orders,
|
||||
'required_spent' => $settings['vip_min_spent'],
|
||||
'required_orders' => $settings['vip_min_orders'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user