## 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
## 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
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! 🎯