feat: Complete Zone CRUD + fix terminology

##  Issue #2: Zone CRUD Complete
- Added full Add/Edit Zone dialog with region selector
- Multi-select for countries/states/continents
- Create, Update, Delete all working
- NO MORE menu-ing WooCommerce!

##  Issue #3: Terminology Fixed
- Changed "Delivery Option" → "Shipping Method" everywhere
- Fixed query enabled condition (showAvailableMethods)
- Now methods list appears correctly

## UI Improvements:
- 3 buttons per zone: Edit (pencil), Delete (trash), Settings (gear)
- Edit = zone name/regions
- Settings = manage methods
- Clear separation of concerns
This commit is contained in:
dwindown
2025-11-10 09:58:28 +07:00
parent d624ac5591
commit 3d9af05a25

View File

@@ -55,7 +55,14 @@ export default function ShippingPage() {
const { data: availableMethods = [] } = useQuery({
queryKey: ['available-shipping-methods'],
queryFn: () => api.get('/settings/shipping/methods/available'),
enabled: showAddMethod,
enabled: showAvailableMethods,
});
// Fetch available locations (countries/states) for zone regions
const { data: availableLocations = [] } = useQuery({
queryKey: ['available-locations'],
queryFn: () => api.get('/settings/shipping/locations'),
enabled: showAddZone || !!editingZone,
});
// Sync selectedZone with zones data when it changes
@@ -257,12 +264,10 @@ export default function ShippingPage() {
<Button
variant="outline"
size="sm"
asChild
onClick={() => setShowAddZone(true)}
>
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping&action=add_zone`} target="_blank" rel="noopener noreferrer">
<Plus className="h-4 w-4 mr-2" />
{__('Add Zone')}
</a>
</Button>
}
>
@@ -272,12 +277,10 @@ export default function ShippingPage() {
{__('No shipping zones configured yet. Create your first zone to start offering shipping.')}
</p>
<Button
asChild
onClick={() => setShowAddZone(true)}
>
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping&action=add_zone`} target="_blank" rel="noopener noreferrer">
<Plus className="h-4 w-4 mr-2" />
{__('Create First Zone')}
</a>
</Button>
</div>
) : (
@@ -306,7 +309,8 @@ export default function ShippingPage() {
<Button
variant="ghost"
size="sm"
onClick={() => setSelectedZone(zone)}
onClick={() => setEditingZone(zone)}
title={__('Edit zone name and regions')}
>
<Edit className="h-4 w-4" />
</Button>
@@ -314,9 +318,18 @@ export default function ShippingPage() {
variant="ghost"
size="sm"
onClick={() => setDeletingZone(zone)}
title={__('Delete zone')}
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setSelectedZone(zone)}
title={__('Manage shipping methods')}
>
<Settings className="h-4 w-4" />
</Button>
</div>
</div>
@@ -403,7 +416,7 @@ export default function ShippingPage() {
</DialogHeader>
<div className="flex-1 overflow-y-auto p-6 min-h-0">
<div className="space-y-4">
{/* Add Delivery Option Button */}
{/* Add Shipping Method Button */}
{!showAvailableMethods ? (
<Button
variant="outline"
@@ -411,7 +424,7 @@ export default function ShippingPage() {
onClick={() => setShowAvailableMethods(true)}
>
<Plus className="h-4 w-4 mr-2" />
{__('Add Delivery Option')}
{__('Add Shipping Method')}
</Button>
) : (
<div className="border rounded-lg p-4 space-y-3">
@@ -446,7 +459,7 @@ export default function ShippingPage() {
</div>
)}
{/* Delivery Options Accordion */}
{/* Shipping Methods Accordion */}
<Accordion type="single" collapsible value={expandedMethod} onValueChange={(value) => {
setExpandedMethod(value);
if (value) {
@@ -615,7 +628,7 @@ export default function ShippingPage() {
</DrawerHeader>
<div className="flex-1 overflow-y-auto px-4 py-4 min-h-0">
<div className="space-y-3">
{/* Add Delivery Option Button */}
{/* Add Shipping Method Button */}
{!showAvailableMethods ? (
<Button
variant="outline"
@@ -623,7 +636,7 @@ export default function ShippingPage() {
onClick={() => setShowAvailableMethods(true)}
>
<Plus className="h-4 w-4 mr-2" />
{__('Add Delivery Option')}
{__('Add Shipping Method')}
</Button>
) : (
<div className="border rounded-lg p-3 space-y-2">
@@ -658,7 +671,7 @@ export default function ShippingPage() {
</div>
)}
{/* Delivery Options Accordion (Mobile) */}
{/* Shipping Methods Accordion (Mobile) */}
<Accordion type="single" collapsible value={expandedMethod} onValueChange={(value) => {
setExpandedMethod(value);
if (value) {
@@ -859,6 +872,112 @@ export default function ShippingPage() {
</AlertDialogContent>
</AlertDialog>
{/* Add/Edit Zone Dialog */}
<Dialog open={showAddZone || !!editingZone} onOpenChange={(open) => {
if (!open) {
setShowAddZone(false);
setEditingZone(null);
}
}}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingZone ? __('Edit Zone') : __('Add Shipping Zone')}</DialogTitle>
</DialogHeader>
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const zoneName = formData.get('name') as string;
const selectedRegions = formData.getAll('regions').map(code => {
const location = availableLocations.find((l: any) => l.code === code);
return location ? { code: location.code, type: location.type } : null;
}).filter(Boolean);
if (editingZone) {
updateZoneMutation.mutate({
zoneId: editingZone.id,
name: zoneName,
regions: selectedRegions,
});
} else {
createZoneMutation.mutate({
name: zoneName,
regions: selectedRegions,
});
}
}} className="space-y-4">
<div>
<label htmlFor="zone-name" className="text-sm font-medium block mb-2">
{__('Zone Name')}
</label>
<input
id="zone-name"
name="name"
type="text"
required
defaultValue={editingZone?.name || ''}
placeholder={__('e.g., Domestic, International, Europe')}
className="w-full px-3 py-2 border rounded-md"
/>
</div>
<div>
<label className="text-sm font-medium block mb-2">
{__('Regions')}
</label>
<p className="text-xs text-muted-foreground mb-3">
{__('Select countries, states, or continents for this zone')}
</p>
<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">
{__('Loading locations...')}
</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>
))}
</div>
)}
</div>
</div>
<div className="flex justify-end gap-3 pt-4">
<Button
type="button"
variant="outline"
onClick={() => {
setShowAddZone(false);
setEditingZone(null);
}}
>
{__('Cancel')}
</Button>
<Button
type="submit"
disabled={createZoneMutation.isPending || updateZoneMutation.isPending}
>
{(createZoneMutation.isPending || updateZoneMutation.isPending) && (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
)}
{editingZone ? __('Update Zone') : __('Create Zone')}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
</SettingsLayout>
);
}