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
This commit is contained in:
dwindown
2025-11-10 17:52:20 +07:00
parent 857d6315e6
commit 71aa8d3940

View File

@@ -187,7 +187,7 @@ export default function OrderForm({
// Calculate shipping rates dynamically
const { data: shippingRates, isLoading: shippingLoading } = useQuery({
queryKey: ['shipping-rates', items.map(i => ({ product_id: i.product_id, qty: i.qty })), shippingData.country, shippingData.postcode, shippingData.state],
queryKey: ['shipping-rates', items.map(i => ({ product_id: i.product_id, qty: i.qty })), shippingData.country, shippingData.state, shippingData.city, shippingData.postcode, shippingData.address_1],
queryFn: async () => {
if (!shippingData.country) return null;
return api.post('/shipping/calculate', {
@@ -196,11 +196,12 @@ export default function OrderForm({
});
},
enabled: !!shippingData.country && items.length > 0,
staleTime: 0, // Always refetch when query key changes
});
// Calculate order preview with taxes
const { data: orderPreview, isLoading: previewLoading } = useQuery({
queryKey: ['order-preview', items.map(i => ({ product_id: i.product_id, qty: i.qty })), bCountry, bState, bPost, bCity, shippingData, shippingMethod, validatedCoupons.map(c => c.code)],
queryKey: ['order-preview', items.map(i => ({ product_id: i.product_id, qty: i.qty })), bCountry, bState, bPost, bCity, shippingData.country, shippingData.state, shippingData.city, shippingData.postcode, shippingMethod, validatedCoupons.map(c => c.code)],
queryFn: async () => {
if (items.length === 0) return null;
return api.post('/orders/preview', {
@@ -212,6 +213,7 @@ export default function OrderForm({
});
},
enabled: items.length > 0 && !!bCountry,
staleTime: 0, // Always refetch when query key changes
});
// --- Product search for Add Item ---
@@ -301,6 +303,21 @@ export default function OrderForm({
setValidatedCoupons(validatedCoupons.filter(c => c.code !== code));
};
// Auto-select first shipping rate when rates change
React.useEffect(() => {
if (shippingRates?.methods && shippingRates.methods.length > 0) {
const firstRateId = shippingRates.methods[0].id;
// Only auto-select if no method selected or current method not in new rates
const currentMethodExists = shippingRates.methods.some((m: any) => m.id === shippingMethod);
if (!shippingMethod || !currentMethodExists) {
setShippingMethod(firstRateId);
}
} else if (shippingRates?.methods && shippingRates.methods.length === 0) {
// Clear selection if no rates available
setShippingMethod('');
}
}, [shippingRates?.methods]);
// Check if cart has physical products
const hasPhysicalProduct = React.useMemo(
() => items.some(item => {