fix: Add region search filter + pre-select on edit + create plan doc
## ✅ Issue #1: TAX_NOTIFICATIONS_PLAN.md Created - Complete implementation plan for Tax & Notifications - 80/20 rule: Core features vs Advanced (WooCommerce) - API endpoints defined - Implementation phases prioritized ## ✅ Issue #2: Region Search Filter - Added search input above region list - Real-time filtering as you type - Shows "No regions found" when no matches - Clears search on dialog close/cancel - Makes finding countries/states MUCH faster! ## ✅ Issue #3: Pre-select Regions on Edit - Backend now returns raw `locations` array - Frontend uses `defaultChecked` with location matching - Existing regions auto-selected when editing zone - Works correctly for countries, states, and continents ## UX Improvements: - Search placeholder: "Search regions..." - Filter is case-insensitive - Empty state when no results - Clean state management (clear on close) Now zone editing is smooth and fast!
This commit is contained in:
106
TAX_NOTIFICATIONS_PLAN.md
Normal file
106
TAX_NOTIFICATIONS_PLAN.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Tax & Notifications Implementation Plan
|
||||
|
||||
## Philosophy: 80/20 Rule - Real Value, Not Menu-ing
|
||||
|
||||
WooNooW provides elegant UX for 80% of daily use cases.
|
||||
The 20% advanced/rare features stay in WooCommerce.
|
||||
|
||||
---
|
||||
|
||||
## Tax Settings - What to Build
|
||||
|
||||
### Core Features (80% use cases):
|
||||
|
||||
1. **Enable/Disable Tax Calculation**
|
||||
- Simple toggle
|
||||
- Clear explanation
|
||||
|
||||
2. **Tax Rates by Country/State**
|
||||
- Add/Edit/Delete tax rates
|
||||
- Country selector
|
||||
- State selector (for countries with states)
|
||||
- Rate percentage input
|
||||
- Tax class selector (Standard, Reduced, Zero)
|
||||
|
||||
3. **Prices Include Tax Toggle**
|
||||
- "Prices entered with tax" vs "without tax"
|
||||
- Clear explanation
|
||||
|
||||
4. **Display Settings**
|
||||
- Show prices in shop: including/excluding tax
|
||||
- Show prices in cart: including/excluding tax
|
||||
- Display suffix (e.g., "incl. VAT")
|
||||
|
||||
### Advanced Features (Link to WooCommerce):
|
||||
|
||||
- Complex tax classes (rare)
|
||||
- Compound taxes (rare)
|
||||
- Tax based on shipping vs billing (advanced)
|
||||
- Custom tax tables (very rare)
|
||||
|
||||
---
|
||||
|
||||
## Notifications Settings - What to Build
|
||||
|
||||
### Core Features (80% use cases):
|
||||
|
||||
1. **Email Enable/Disable**
|
||||
- Toggle for each email type
|
||||
- Already implemented ✅
|
||||
|
||||
2. **Edit Email Subjects**
|
||||
- Inline editing of subject lines
|
||||
- Variables support ({order_number}, {site_title})
|
||||
|
||||
3. **Sender Configuration**
|
||||
- "From" name
|
||||
- "From" email address
|
||||
|
||||
4. **Email Preview**
|
||||
- Preview button for each email
|
||||
- Opens WooCommerce preview
|
||||
|
||||
### Advanced Features (Link to WooCommerce):
|
||||
|
||||
- Full HTML template editing (advanced)
|
||||
- Custom email templates (developer)
|
||||
- Header/footer customization (advanced)
|
||||
- Additional recipients (rare)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
### Phase 1: Tax Rates (High Impact)
|
||||
1. Backend: Tax rates CRUD API
|
||||
2. Frontend: Tax rates list + Add/Edit dialog
|
||||
3. Frontend: Enable/disable toggle
|
||||
4. Frontend: Prices include tax toggle
|
||||
|
||||
### Phase 2: Email Subjects (High Impact)
|
||||
1. Backend: Get/update email settings API
|
||||
2. Frontend: Sender name/email inputs
|
||||
3. Frontend: Inline subject editing
|
||||
4. Frontend: Preview links
|
||||
|
||||
### Phase 3: Tax Display Settings (Medium Impact)
|
||||
1. Frontend: Display settings card
|
||||
2. Backend: Save display preferences
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints Needed
|
||||
|
||||
### Tax:
|
||||
- GET /settings/tax/rates
|
||||
- POST /settings/tax/rates
|
||||
- PUT /settings/tax/rates/{id}
|
||||
- DELETE /settings/tax/rates/{id}
|
||||
- GET /settings/tax/config
|
||||
- POST /settings/tax/config
|
||||
|
||||
### Notifications:
|
||||
- GET /settings/notifications/emails
|
||||
- POST /settings/notifications/emails/{id}
|
||||
- GET /settings/notifications/sender
|
||||
- POST /settings/notifications/sender
|
||||
@@ -42,6 +42,7 @@ export default function ShippingPage() {
|
||||
const [showAddZone, setShowAddZone] = useState(false);
|
||||
const [editingZone, setEditingZone] = useState<any | null>(null);
|
||||
const [deletingZone, setDeletingZone] = useState<any | null>(null);
|
||||
const [regionSearch, setRegionSearch] = useState('');
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
// Fetch shipping zones from WooCommerce
|
||||
@@ -877,6 +878,7 @@ export default function ShippingPage() {
|
||||
if (!open) {
|
||||
setShowAddZone(false);
|
||||
setEditingZone(null);
|
||||
setRegionSearch('');
|
||||
}
|
||||
}}>
|
||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
@@ -927,6 +929,16 @@ export default function ShippingPage() {
|
||||
<p className="text-xs text-muted-foreground mb-3">
|
||||
{__('Select countries, states, or continents for this zone')}
|
||||
</p>
|
||||
|
||||
{/* Search Filter */}
|
||||
<input
|
||||
type="text"
|
||||
placeholder={__('Search regions...')}
|
||||
value={regionSearch}
|
||||
onChange={(e) => setRegionSearch(e.target.value)}
|
||||
className="w-full px-3 py-2 border rounded-md mb-2"
|
||||
/>
|
||||
|
||||
<div className="border rounded-md max-h-[300px] overflow-y-auto">
|
||||
{availableLocations.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
@@ -934,20 +946,32 @@ export default function ShippingPage() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y">
|
||||
{availableLocations.map((location: any) => (
|
||||
<label
|
||||
key={location.code}
|
||||
className="flex items-center gap-3 p-3 hover:bg-accent cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="regions"
|
||||
value={location.code}
|
||||
className="rounded"
|
||||
/>
|
||||
<span className="text-sm">{location.label}</span>
|
||||
</label>
|
||||
))}
|
||||
{availableLocations
|
||||
.filter((location: any) =>
|
||||
location.label.toLowerCase().includes(regionSearch.toLowerCase())
|
||||
)
|
||||
.map((location: any) => (
|
||||
<label
|
||||
key={location.code}
|
||||
className="flex items-center gap-3 p-3 hover:bg-accent cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="regions"
|
||||
value={location.code}
|
||||
defaultChecked={editingZone?.locations?.some((l: any) => l.code === location.code)}
|
||||
className="rounded"
|
||||
/>
|
||||
<span className="text-sm">{location.label}</span>
|
||||
</label>
|
||||
))}
|
||||
{availableLocations.filter((location: any) =>
|
||||
location.label.toLowerCase().includes(regionSearch.toLowerCase())
|
||||
).length === 0 && (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
{__('No regions found')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -960,6 +984,7 @@ export default function ShippingPage() {
|
||||
onClick={() => {
|
||||
setShowAddZone(false);
|
||||
setEditingZone(null);
|
||||
setRegionSearch('');
|
||||
}}
|
||||
>
|
||||
{__('Cancel')}
|
||||
|
||||
@@ -229,8 +229,16 @@ class ShippingController extends WP_REST_Controller {
|
||||
// Get zone locations (regions)
|
||||
$locations = $zone->get_zone_locations();
|
||||
$regions = array();
|
||||
$raw_locations = array();
|
||||
|
||||
foreach ( $locations as $location ) {
|
||||
// Store raw location data for editing
|
||||
$raw_locations[] = array(
|
||||
'code' => $location->code,
|
||||
'type' => $location->type,
|
||||
);
|
||||
|
||||
// Format for display
|
||||
if ( $location->type === 'country' ) {
|
||||
$countries = WC()->countries->get_countries();
|
||||
$regions[] = $countries[ $location->code ] ?? $location->code;
|
||||
@@ -280,11 +288,12 @@ class ShippingController extends WP_REST_Controller {
|
||||
}
|
||||
|
||||
$zones_data[] = array(
|
||||
'id' => $zone_data['id'],
|
||||
'name' => $zone->get_zone_name(),
|
||||
'order' => $zone->get_zone_order(),
|
||||
'regions' => ! empty( $regions ) ? implode( ', ', $regions ) : __( 'No regions', 'woonoow' ),
|
||||
'rates' => $rates,
|
||||
'id' => $zone_data['id'],
|
||||
'name' => $zone->get_zone_name(),
|
||||
'order' => $zone->get_zone_order(),
|
||||
'regions' => ! empty( $regions ) ? implode( ', ', $regions ) : __( 'No regions', 'woonoow' ),
|
||||
'locations' => $raw_locations,
|
||||
'rates' => $rates,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user