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:
dwindown
2025-11-11 00:49:07 +07:00
parent 8fd3691975
commit 9c31b4ce6c
9 changed files with 550 additions and 6 deletions

View File

@@ -222,6 +222,7 @@ export default function CouponsReport() {
<ChartCard
title={__('Coupon Usage Over Time')}
description={__('Daily coupon usage and discount amount')}
dataPoints={chartData.length}
>
{chartData.length === 0 || chartData.every((d: any) => d.uses === 0) ? (
<div className="flex items-center justify-center h-[300px]">

View File

@@ -205,6 +205,7 @@ export default function OrdersAnalytics() {
<ChartCard
title={__('Orders Over Time')}
description={__('Daily order count and status breakdown')}
dataPoints={chartData.length}
>
{chartData.length === 0 || chartData.every((d: any) => d.orders === 0) ? (
<div className="flex items-center justify-center h-[300px]">

View File

@@ -382,10 +382,11 @@ export default function RevenueAnalytics() {
{/* Revenue Chart */}
<ChartCard
title={__('Revenue Over Time')}
description={__('Gross revenue, net revenue, and refunds')}
description={__('Gross and net revenue trends')}
dataPoints={chartData.length}
actions={
<Select value={granularity} onValueChange={(v: any) => setGranularity(v)}>
<SelectTrigger className="w-[120px]">
<Select value={granularity} onValueChange={(value: any) => setGranularity(value)}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>

View File

@@ -8,6 +8,8 @@ interface ChartCardProps {
actions?: ReactNode;
loading?: boolean;
height?: number;
enableMobileScroll?: boolean; // Enable horizontal scroll on mobile
dataPoints?: number; // Number of data points (for calculating mobile width)
}
export function ChartCard({
@@ -16,8 +18,14 @@ export function ChartCard({
children,
actions,
loading = false,
height = 300
height = 300,
enableMobileScroll = true,
dataPoints = 30
}: ChartCardProps) {
// Calculate minimum width for mobile based on data points
// Each data point needs ~40px for readability
const mobileMinWidth = Math.max(600, dataPoints * 40);
if (loading) {
return (
<div className="rounded-lg border bg-card p-6">
@@ -46,7 +54,23 @@ export function ChartCard({
</div>
{actions && <div className="flex gap-2">{actions}</div>}
</div>
{children}
{/* Chart container with mobile scroll */}
{enableMobileScroll ? (
<div className="overflow-x-auto -mx-6 px-6 md:mx-0 md:px-0">
<div
className="md:w-full"
style={{
minWidth: `${mobileMinWidth}px`,
width: '100%'
}}
>
{children}
</div>
</div>
) : (
children
)}
</div>
);
}