Commit Graph

18 Commits

Author SHA1 Message Date
Dwindi Ramadhana
ab0eb3ab28 fix(admin): shipping uses rate-level options from calculate_shipping
Removed static method-level fallback. Shipping method selector now:
1. Shows 'Enter shipping address to see available rates' when address incomplete
2. Calls calculate_shipping endpoint to get actual WC_Shipping_Rate objects
3. Displays rate-level options (e.g., JNE REG, JNE YES) not method-level

This ensures third-party shipping plugins like Rajaongkir, UPS, FedEx
display their courier rates correctly.
2026-01-08 09:56:12 +07:00
Dwindi Ramadhana
740cfcbb94 fix(admin): shipping fallback and variation attribute styling
1. Shipping method selector now shows static shippings list when
   address is not complete, instead of 'No shipping methods available'.
   Only shows the empty message when address IS complete but no methods
   matched.

2. Variation selector in Dialog and Drawer now displays attribute names
   (Size, Dispenser) in semibold and values (30ml, pump) in normal
   weight for better visual hierarchy.
2026-01-08 09:26:54 +07:00
Dwindi Ramadhana
a0e580878e fix(admin): mount popover portal inside app container
Instead of mounting to body (which breaks scoped styles), we now
mount the popover portal to #woonoow-admin-app. This ensures
dropdowns inherit the correct CSS variables and styling.
2026-01-07 23:40:03 +07:00
Dwindi Ramadhana
a52f5fc707 fix(admin): set explicit width for product search dropdown in order form
Prevents the search dropdown from shrinking or overflowing unpredictably
in the flex container. Also ensures better alignment.
2026-01-07 23:34:48 +07:00
dwindown
c8bba9a91b feat: Move customer registration to site-level setting
Moved 'Register as site member' from order-level to site-level setting

Frontend Changes:
1. Customer Settings - Added new General section
   - Auto-register customers as site members toggle
   - Clear description of functionality
   - Saved to backend via existing API

2. OrderForm - Removed checkbox
   - Removed registerAsMember state
   - Removed checkbox UI
   - Removed register_as_member from payload
   - Backend now uses site setting

Backend Changes:
1. CustomerSettingsProvider.php
   - Added auto_register_members setting
   - Default: false (no)
   - Stored as woonoow_auto_register_members option
   - Included in get_settings()
   - Handled in update_settings()

2. OrdersController.php
   - Removed register_as_member parameter
   - Now reads from CustomerSettingsProvider
   - Site-level setting applies to all orders
   - Consistent behavior across all order creation

Benefits:
 Site-level control (not per-order)
 Consistent customer experience
 Easier to manage (one setting)
 No UI clutter in order form
 Setting persists across all orders

Migration:
- Old orders with checkbox: No impact
- New orders: Use site setting
- Default: Disabled (safe default)

Result:
Admins can now control customer registration site-wide from Customer Settings instead of per-order checkbox
2025-11-20 20:40:43 +07:00
dwindown
afb54b962e fix: Critical fixes for shipping and meta field registration
Issue 1: Shipping recalculation on order edit (FIXED)
- Problem: OrderForm recalculated shipping on every edit
- Expected: Shipping should be fixed unless address changes
- Solution: Use existing order.totals.shipping in edit mode
- Create mode: Still calculates from shipping method

Issue 2: Meta fields not appearing without data (DOCUMENTED)
- Problem: Private meta fields dont appear if no data exists yet
- Example: Admin cannot input tracking number on first time
- Root cause: Fields only exposed if data exists in database
- Solution: Plugins MUST register fields via MetaFieldsRegistry
- Registration makes field available even when empty

Updated METABOX_COMPAT.md:
- Changed optional to REQUIRED for field registration
- Added critical warning section
- Explained private vs public meta behavior
- Private meta: MUST register to appear
- Public meta: Auto-exposed, no registration needed

The Flow (Corrected):
1. Plugin registers field -> Field appears in UI (even empty)
2. Admin inputs data -> Saved to database
3. Data visible in both admins

Without Registration:
- Private meta (_field): Not exposed, not editable
- Public meta (field): Auto-exposed, auto-editable

Why Private Meta Requires Registration:
- Security: Hidden by default
- Privacy: Prevents exposing sensitive data
- Control: Plugins explicitly declare visibility

Files Changed:
- OrderForm.tsx: Use existing shipping total in edit mode
- METABOX_COMPAT.md: Critical documentation updates

Result:
- Shipping no longer recalculates on edit
- Clear documentation on field registration requirement
- Developers know they MUST register private meta fields
2025-11-20 12:53:55 +07:00
dwindown
be69b40237 fix: OrderForm variable product issues - empty colors, desktop dialog, duplicate handling
**Issues Fixed:**

1. **Empty Color Values**
   - Problem: Variation attributes showed 'Color:' with no value
   - Cause: Backend returned empty strings for some attributes
   - Fix: Filter empty values with .filter(([_, value]) => value)
   - Result: Only non-empty attributes displayed

2. **Desktop Should Use Dialog**
   - Problem: Both desktop and mobile used Drawer (bottom sheet)
   - Expected: Desktop = Dialog (modal), Mobile = Drawer
   - Fix: Added useMediaQuery hook, conditional rendering
   - Pattern: Same as Settings pages (Payments, Shipping, etc.)

3. **Duplicate Product+Variation Handling**
   - Problem: Same product+variation created new row each time
   - Expected: Should increment quantity of existing row
   - Fix: Check for existing item before adding
   - Logic: findIndex by product_id + variation_id, then increment qty

**Changes to OrderForm.tsx:**
- Added Dialog and useMediaQuery imports
- Added isDesktop detection
- Split variation selector into Desktop (Dialog) and Mobile (Drawer)
- Fixed variationLabel to filter empty values
- Added duplicate check logic before adding to cart
- If exists: increment qty, else: add new item

**Changes to PROJECT_SOP.md:**
- Added Responsive Modal Pattern section
- Documented Dialog/Drawer pattern with code example
- Added rule 3: Same product+variation = increment qty
- Added rule 6: Filter empty attribute values
- Added rule 7: Responsive modals (Dialog/Drawer)

**Result:**
 Color values display correctly (empty values filtered)
 Desktop uses Dialog (centered modal)
 Mobile uses Drawer (bottom sheet)
 Duplicate product+variation increments quantity
 UX matches Tokopedia/Shopee pattern
 Follows Settings page modal pattern
2025-11-20 10:44:48 +07:00
dwindown
9a6a434c48 feat: Implement variable product handling in OrderForm (Tokopedia pattern)
Following PROJECT_SOP.md section 5.7 - Variable Product Handling:

**Backend (OrdersController.php):**
- Updated /products/search endpoint to return:
  - Product type (simple/variable)
  - Variations array with attributes, prices, stock
  - Formatted attribute names (Color, Size, etc.)

**Frontend (OrderForm.tsx):**
- Updated ProductSearchItem type to include variations
- Updated LineItem type to support variation_id and variation_name
- Added variation selector drawer (mobile + desktop)
- Each variation = separate cart item row
- Display variation name below product name
- Fixed remove button to work with variations (by index)

**UX Pattern:**
1. Search product → If variable, show variation drawer
2. Select variation → Add as separate line item
3. Can add same product with different variations
4. Each variation shown clearly: 'Product Name' + 'Color: Red'

**Result:**
 Tokopedia/Shopee pattern implemented
 No auto-selection of first variation
 Each variation is independent cart item
 Works on mobile and desktop

**Next:** Fix PageHeader max-w-5xl to only apply on settings pages
2025-11-20 09:47:14 +07:00
dwindown
a499b6ad0b fix(orders): Add debouncing and disable aggressive caching for shipping rates
## Three Issues Fixed 

### 1. Backend hit on every keypress 
**Problem:**
- Type "Bandung" → 7 API calls (B, Ba, Ban, Band, Bandu, Bandun, Bandung)
- Expensive for live rate APIs (Rajaongkir, UPS)
- Poor UX with constant loading

**Solution - Debouncing:**
```ts
const [debouncedCity, setDebouncedCity] = useState(city);

useEffect(() => {
  const timer = setTimeout(() => {
    setDebouncedCity(city);
  }, 500); // Wait 500ms after user stops typing

  return () => clearTimeout(timer);
}, [city]);

// Use debouncedCity in query key
queryKey: [..., debouncedCity]
```

**Result:**
- Type "Bandung" → Wait 500ms → 1 API call 
- Much better UX and performance

---

### 2. Same rates for different provinces 
**Problem:**
- Select "Jawa Barat" → JNE REG Rp31,000
- Select "Bali" → JNE REG Rp31,000 (wrong!)
- Should be different rates

**Root Cause:**
```ts
staleTime: 5 * 60 * 1000 // Cache for 5 minutes
```

React Query was caching too aggressively. Even though query key changed (different state), it was returning cached data.

**Solution:**
```ts
gcTime: 0,      // Don't cache in memory
staleTime: 0,   // Always refetch when key changes
```

**Result:**
- Select "Jawa Barat" → Fetch → JNE REG Rp31,000
- Select "Bali" → Fetch → JNE REG Rp45,000 
- Correct rates for each province

---

### 3. No Rajaongkir API hits 
**Problem:**
- Check Rajaongkir dashboard → No new API calls
- Rates never actually calculated
- Using stale cached data

**Root Cause:**
Same as #2 - aggressive caching prevented real API calls

**Solution:**
Disabled caching completely for shipping calculations:
```ts
gcTime: 0,      // No garbage collection time
staleTime: 0,   // No stale time
```

**Result:**
- Change province → Real Rajaongkir API call 
- Fresh rates every time 
- Dashboard shows API usage 

---

## How It Works Now:

### User Types City:
```
1. Type "B" → Timer starts (500ms)
2. Type "a" → Timer resets (500ms)
3. Type "n" → Timer resets (500ms)
4. Type "dung" → Timer resets (500ms)
5. Stop typing → Wait 500ms
6.  API call with "Bandung"
```

### User Changes Province:
```
1. Select "Jawa Barat"
2. Query key changes
3.  Fetch fresh rates (no cache)
4.  Rajaongkir API called
5. Returns: JNE REG Rp31,000

6. Select "Bali"
7. Query key changes
8.  Fetch fresh rates (no cache)
9.  Rajaongkir API called again
10. Returns: JNE REG Rp45,000 (different!)
```

## Benefits:
-  No more keypress spam
-  Correct rates per province
-  Real API calls to Rajaongkir
-  Fresh data always
-  Better UX with 500ms debounce
2025-11-10 18:34:49 +07:00
dwindown
97f25aa6af fix(orders): Use billing address for shipping when not shipping to different address
## Critical Bug Fixed 

### Problem:
- User fills billing address (Country, State, City)
- Shipping says "No shipping methods available"
- Backend returns empty methods array
- No rates calculated

### Root Cause:
Frontend was only checking `shippingData` for completeness:
```ts
if (!shippingData.country) return false;
if (!shippingData.city) return false;
```

But when user doesn't check "Ship to different address":
- `shippingData` is empty {}
- Billing address has all the data
- Query never enabled!

### Solution:
Use effective shipping address based on `shipDiff` toggle:

```ts
const effectiveShippingAddress = useMemo(() => {
  if (shipDiff) {
    return shippingData; // Use separate shipping address
  }
  // Use billing address
  return {
    country: bCountry,
    state: bState,
    city: bCity,
    postcode: bPost,
    address_1: bAddr1,
  };
}, [shipDiff, shippingData, bCountry, bState, bCity, bPost, bAddr1]);
```

Then check completeness on effective address:
```ts
const isComplete = useMemo(() => {
  const addr = effectiveShippingAddress;
  if (!addr.country) return false;
  if (!addr.city) return false;
  if (hasStates && !addr.state) return false;
  return true;
}, [effectiveShippingAddress]);
```

### Backend Enhancement:
Also set billing address for tax calculation context:
```php
// Set both shipping and billing for proper tax calculation
WC()->customer->set_shipping_country( $country );
WC()->customer->set_billing_country( $country );
```

## Result:

### Before:
1. Fill billing: Indonesia, Jawa Barat, Bandung
2. Shipping: "No shipping methods available" 
3. No API call made

### After:
1. Fill billing: Indonesia, Jawa Barat, Bandung
2.  API called with billing address
3.  Returns: JNE REG, JNE YES, TIKI REG
4.  First rate auto-selected
5.  Total calculated with tax

## Testing:
-  Fill billing only → Shipping calculated
-  Check "Ship to different" → Use shipping address
-  Uncheck → Switch back to billing
-  Change billing city → Rates recalculate
2025-11-10 18:27:49 +07:00
dwindown
a00ffedc41 fix(orders): Prevent premature shipping rate fetching
## Issues Fixed:

### 1. Shipping rates fetched on page load 
**Problem:**
- Open New Order form → Shipping already calculated
- Using cached/legacy values
- Should wait for address to be filled

**Solution:**
Added address completeness check:
```ts
const isShippingAddressComplete = useMemo(() => {
  if (!shippingData.country) return false;
  if (!shippingData.city) return false;

  // If country has states, require state
  const countryStates = states[shippingData.country];
  if (countryStates && Object.keys(countryStates).length > 0) {
    if (!shippingData.state) return false;
  }

  return true;
}, [shippingData.country, shippingData.state, shippingData.city]);
```

Query only enabled when address is complete:
```ts
enabled: isShippingAddressComplete && items.length > 0
```

### 2. Unnecessary refetches 
**Problem:**
- Every keystroke triggered refetch
- staleTime: 0 meant always refetch

**Solution:**
```ts
staleTime: 5 * 60 * 1000 // Cache for 5 minutes
```

Query key still includes all address fields, so:
- Change country → Refetch (key changed)
- Change state → Refetch (key changed)
- Change city → Refetch (key changed)
- Change postcode → Refetch (key changed)
- Same values → Use cache (key unchanged)

### 3. Order preview fetching too early 
**Problem:**
- Preview calculated before shipping method selected
- Incomplete data

**Solution:**
```ts
enabled: items.length > 0 && !!bCountry && !!shippingMethod
```

## New Behavior:

### On Page Load:
-  No shipping fetch
-  No preview fetch
-  Clean state

### User Fills Address:
1. Enter country → Not enough
2. Enter state → Not enough
3. Enter city →  **Fetch shipping rates**
4. Rates appear → First auto-selected
5.  **Fetch order preview** (has method now)

### User Changes Address:
1. Change Jakarta → Bandung
2. Query key changes (city changed)
3.  **Refetch shipping rates**
4. New rates appear → First auto-selected
5.  **Refetch order preview**

### User Types in Same Field:
1. Type "Jak..." → "Jakarta"
2. Query key same (city still "Jakarta")
3.  No refetch (use cache)
4. Efficient!

## Benefits:
-  No premature fetching
-  No unnecessary API calls
-  Smart caching (5 min)
-  Only refetch when address actually changes
-  Better UX and performance
2025-11-10 18:19:25 +07:00
dwindown
71aa8d3940 fix(orders): Fix shipping rate recalculation and auto-selection
## Issues Fixed:

### 1. Shipping rates not recalculating when address changes 

**Problem:**
- Change province → Rates stay the same
- Query was cached incorrectly

**Root Cause:**
Query key only tracked country, state, postcode:
```ts
queryKey: [..., shippingData.country, shippingData.state, shippingData.postcode]
```

But Rajaongkir and other plugins also need:
- City (different rates per city)
- Address (for some plugins)

**Solution:**
```ts
queryKey: [
  ...,
  shippingData.country,
  shippingData.state,
  shippingData.city,      // Added
  shippingData.postcode,
  shippingData.address_1  // Added
],
staleTime: 0, // Always refetch when key changes
```

### 2. First rate auto-selected but dropdown shows placeholder 

**Problem:**
- Rates calculated → First rate used in total
- But dropdown shows "Select shipping"
- Confusing UX

**Solution:**
Added useEffect to auto-select first rate:
```ts
useEffect(() => {
  if (shippingRates?.methods?.length > 0) {
    const firstRateId = shippingRates.methods[0].id;
    const currentExists = shippingRates.methods.some(m => m.id === shippingMethod);

    // Auto-select if no selection or current not in new rates
    if (!shippingMethod || !currentExists) {
      setShippingMethod(firstRateId);
    }
  }
}, [shippingRates?.methods]);
```

## Benefits:
-  Change province → Rates recalculate immediately
-  First rate auto-selected in dropdown
-  Selection cleared if no rates available
-  Selection preserved if still valid after recalculation

## Testing:
1. Select Jakarta → Shows JNE rates
2. Change to Bali → Rates recalculate, first auto-selected
3. Change to remote area → Different rates, first auto-selected
4. Dropdown always shows current selection
2025-11-10 17:52:20 +07:00
dwindown
3f6052f1de feat(orders): Integrate WooCommerce calculation in OrderForm
## Frontend Implementation Complete 

### Changes in OrderForm.tsx:

1. **Added Shipping Rate Calculation Query**
   - Fetches live rates when address changes
   - Passes items + shipping address to `/shipping/calculate`
   - Returns service-level options (UPS Ground, Express, etc.)
   - Shows loading state while calculating

2. **Added Order Preview Query**
   - Calculates totals with taxes using `/orders/preview`
   - Passes items, billing, shipping, method, coupons
   - Returns: subtotal, shipping, tax, discounts, total
   - Updates when any dependency changes

3. **Updated Shipping Method Dropdown**
   - Shows dynamic rates with services and costs
   - Format: "UPS Ground - RM15,000"
   - Loading state: "Calculating rates..."
   - Fallback to static methods if no address

4. **Updated Order Summary**
   - Shows tax breakdown when available
   - Format:
     - Items: 1
     - Subtotal: RM97,000
     - Shipping: RM15,000
     - Tax: RM12,320 (11%)
     - Total: RM124,320
   - Loading state: "Calculating..."
   - Fallback to manual calculation

### Features:
-  Live shipping rates (UPS, FedEx)
-  Service-level options appear
-  Tax calculated correctly (11% PPN)
-  Coupons applied properly
-  Loading states
-  Graceful fallbacks
-  Uses WooCommerce core calculation

### Testing:
1. Add physical product → Shipping dropdown shows services
2. Select UPS Ground → Total updates with shipping cost
3. Change address → Rates recalculate
4. Tax shows 11% of subtotal + shipping
5. Digital products → No shipping, no shipping tax

### Expected Result:
**Before:** Total: RM97,000 (no tax, no service options)
**After:** Total: RM124,320 (with 11% tax, service options visible)
2025-11-10 16:01:24 +07:00
dwindown
a487baa61d fix: Resolve Tax and OrderForm errors
## Error 1: Tax Settings - Empty SelectItem value 
**Issue:** Radix UI Select does not allow empty string as SelectItem value
**Error:** "A <Select.Item /> must have a value prop that is not an empty string"

**Solution:**
- Use 'standard' instead of empty string for UI
- Convert 'standard' → '' when submitting to API
- Initialize selectedTaxClass to 'standard'
- Update all dialog handlers to use 'standard'

## Error 2: OrderForm - Undefined shipping variables 
**Issue:** Removed individual shipping state variables (sFirst, sLast, sCountry, etc.) but forgot to update all references
**Error:** "Cannot find name 'sCountry'"

**Solution:**
Fixed all remaining references:
1. **useEffect for country sync:** `setSCountry(bCountry)` → `setShippingData({...shippingData, country: bCountry})`
2. **useEffect for state validation:** `sState && !states[sCountry]` → `shippingData.state && !states[shippingData.country]`
3. **Customer autofill:** Individual setters → `setShippingData({ first_name, last_name, ... })`
4. **Removed sStateOptions:** No longer needed with dynamic fields

## Testing:
-  Tax settings page loads without errors
-  Add/Edit tax rate dialog works
-  OrderForm loads without errors
-  Shipping fields render dynamically
-  Customer autofill works with new state structure
2025-11-10 15:42:16 +07:00
dwindown
e05635f358 feat(orders): Dynamic shipping fields from checkout API
## Complete Rewrite of Shipping Implementation

### Backend (Already Done):
-  `/checkout/fields` API endpoint
-  Respects addon hide/show logic
-  Handles digital-only products
-  Returns field metadata (type, required, hidden, options, etc.)

### Frontend (New Implementation):
**Replaced hardcoded shipping fields with dynamic API-driven rendering**

#### Changes in OrderForm.tsx:

1. **Query checkout fields API:**
   - Fetches fields based on cart items
   - Enabled only when items exist
   - Passes product IDs and quantities

2. **Dynamic state management:**
   - Removed individual useState for each field (sFirst, sLast, sAddr1, etc.)
   - Replaced with single `shippingData` object: `Record<string, any>`
   - Cleaner, more flexible state management

3. **Dynamic field rendering:**
   - Filters fields by fieldset === 'shipping' and !hidden
   - Sorts by priority
   - Renders based on field.type:
     - `select` → Select with options
     - `country` → SearchableSelect
     - `textarea` → Textarea
     - default → Input (text/email/tel)
   - Respects required flag with visual indicator
   - Auto-detects wide fields (address_1, address_2)

4. **Form submission:**
   - Uses `shippingData` directly instead of individual fields
   - Cleaner payload construction

### Benefits:
-  Addons can add custom fields (e.g., subdistrict)
-  Fields show/hide based on addon logic
-  Required flags respected
-  Digital products hide shipping correctly
-  No hardcoding - fully extensible
-  Maintains existing UX

### Testing:
- Test with physical products → shipping fields appear
- Test with digital products → shipping hidden
- Test with addons that add fields → custom fields render
- Test form submission → data sent correctly
2025-11-10 14:34:15 +07:00
dwindown
58d508eb4e feat: Move action buttons to contextual headers for CRUD pages
Implemented proper contextual header pattern for all Order CRUD pages.

Problem:
- New/Edit pages had action buttons at bottom of form
- Detail page showed duplicate headers (contextual + inline)
- Not following mobile-first best practices

Solution: [Back] Page Title [Action]

1. Edit Order Page
   Header: [Back] Edit Order #337 [Save]

   Implementation:
   - Added formRef to trigger form submit from header
   - Save button in contextual header
   - Removed submit button from form bottom
   - Button shows loading state during save

   Changes:
   - Edit.tsx: Added formRef, updated header with Save button
   - OrderForm.tsx: Added formRef and hideSubmitButton props
   - Form submit triggered via formRef.current.requestSubmit()

2. New Order Page
   Header: [Back] New Order [Create]

   Implementation:
   - Added formRef to trigger form submit from header
   - Create button in contextual header
   - Removed submit button from form bottom
   - Button shows loading state during creation

   Changes:
   - New.tsx: Added formRef, updated header with Create button
   - Same OrderForm props as Edit page

3. Order Detail Page
   Header: (hidden)

   Implementation:
   - Cleared contextual header completely
   - Detail page has its own inline header with actions
   - Inline header: [Back] Order #337 [Print] [Invoice] [Label] [Edit]

   Changes:
   - Detail.tsx: clearPageHeader() in useEffect
   - No duplicate headers

OrderForm Component Updates:
- Added formRef prop (React.RefObject<HTMLFormElement>)
- Added hideSubmitButton prop (boolean)
- Form element accepts ref: <form ref={formRef}>
- Submit button conditionally rendered: {!hideSubmitButton && <Button...>}
- Backward compatible (both props optional)

Benefits:
 Consistent header pattern across all CRUD pages
 Action buttons always visible (sticky header)
 Better mobile UX (no scrolling to find buttons)
 Loading states in header buttons
 Clean, modern interface
 Follows industry standards (Gmail, Notion, Linear)

Files Modified:
- routes/Orders/New.tsx
- routes/Orders/Edit.tsx
- routes/Orders/Detail.tsx
- routes/Orders/partials/OrderForm.tsx

Result:
 New/Edit: Action buttons in contextual header
 Detail: No contextual header (has inline header)
 Professional, mobile-first UX! 🎯
2025-11-08 15:38:38 +07:00
dwindown
e49a0d1e3d feat: Implement Phase 1 Shopify-inspired settings (Store, Payments, Shipping)
 Features:
- Store Details page with live currency preview
- Payments page with visual provider cards and test mode
- Shipping & Delivery page with zone cards and local pickup
- Shared components: SettingsLayout, SettingsCard, SettingsSection, ToggleField

🎨 UI/UX:
- Card-based layouts (not boring forms)
- Generous whitespace and visual hierarchy
- Toast notifications using sonner (reused from Orders)
- Sticky save button at top
- Mobile-responsive design

🔧 Technical:
- Installed ESLint with TypeScript support
- Fixed all lint errors (0 errors)
- Phase 1 files have zero warnings
- Used existing toast from sonner (not reinvented)
- Updated routes in App.tsx

📝 Files Created:
- Store.tsx (currency preview, address, timezone)
- Payments.tsx (provider cards, manual methods)
- Shipping.tsx (zone cards, rates, local pickup)
- SettingsLayout.tsx, SettingsCard.tsx, SettingsSection.tsx, ToggleField.tsx

Phase 1 complete: 18-24 hours estimated work
2025-11-05 18:54:41 +07:00
dwindown
232059e928 feat: Complete Dashboard API Integration with Analytics Controller
 Features:
- Implemented API integration for all 7 dashboard pages
- Added Analytics REST API controller with 7 endpoints
- Full loading and error states with retry functionality
- Seamless dummy data toggle for development

📊 Dashboard Pages:
- Customers Analytics (complete)
- Revenue Analytics (complete)
- Orders Analytics (complete)
- Products Analytics (complete)
- Coupons Analytics (complete)
- Taxes Analytics (complete)
- Dashboard Overview (complete)

🔌 Backend:
- Created AnalyticsController.php with REST endpoints
- All endpoints return 501 (Not Implemented) for now
- Ready for HPOS-based implementation
- Proper permission checks

🎨 Frontend:
- useAnalytics hook for data fetching
- React Query caching
- ErrorCard with retry functionality
- TypeScript type safety
- Zero build errors

📝 Documentation:
- DASHBOARD_API_IMPLEMENTATION.md guide
- Backend implementation roadmap
- Testing strategy

🔧 Build:
- All pages compile successfully
- Production-ready with dummy data fallback
- Zero TypeScript errors
2025-11-04 11:19:00 +07:00