feat: Add zone management backend + drawer z-index fix + SettingsCard action prop

## 1. Fixed Drawer Z-Index 
- Increased drawer z-index from 50 to 60
- Now appears above bottom navigation (z-50)
- Fixes mobile drawer visibility issue

## 2. Zone Management Backend 
Added full CRUD for shipping zones:
- POST /settings/shipping/zones - Create zone
- PUT /settings/shipping/zones/{id} - Update zone
- DELETE /settings/shipping/zones/{id} - Delete zone
- GET /settings/shipping/locations - Get countries/states/continents

Features:
- Create zones with name and regions
- Update zone name and regions
- Delete zones
- Region selector with continents, countries, and states
- Proper cache invalidation

## 3. Zone Management Frontend (In Progress) 
- Added state for zone CRUD (showAddZone, editingZone, deletingZone)
- Added mutations (createZone, updateZone, deleteZone)
- Added "Add Zone" button to SettingsCard
- Updated empty state with "Create First Zone" button

## 4. Enhanced SettingsCard Component 
- Added optional `action` prop for header buttons
- Flexbox layout for title/description + action
- Used in Shipping zones for "Add Zone" button

## Next Steps:
- Add delete button to each zone
- Create Add/Edit Zone dialog with region selector
- Add delete confirmation dialog
- Then move to Tax rates and Email subjects
This commit is contained in:
dwindown
2025-11-10 08:24:25 +07:00
parent 06213d2ed4
commit d2350852ef
4 changed files with 328 additions and 11 deletions

View File

@@ -26,7 +26,7 @@ const DrawerOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
className={cn("fixed inset-0 z-[60] bg-black/80", className)}
{...props}
/>
))
@@ -41,7 +41,7 @@ const DrawerContent = React.forwardRef<
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
"fixed inset-x-0 bottom-0 z-[60] mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className
)}
{...props}

View File

@@ -38,6 +38,9 @@ export default function ShippingPage() {
const [expandedMethod, setExpandedMethod] = useState<string>('');
const [methodSettings, setMethodSettings] = useState<Record<string, any>>({});
const [deletingMethod, setDeletingMethod] = useState<{ zoneId: number; instanceId: number; name: string } | null>(null);
const [showAddZone, setShowAddZone] = useState(false);
const [editingZone, setEditingZone] = useState<any | null>(null);
const [deletingZone, setDeletingZone] = useState<any | null>(null);
const isDesktop = useMediaQuery("(min-width: 768px)");
// Fetch shipping zones from WooCommerce
@@ -170,6 +173,52 @@ export default function ShippingPage() {
}
};
// Zone mutations
const createZoneMutation = useMutation({
mutationFn: async (data: { name: string; regions: any[] }) => {
return api.post('/settings/shipping/zones', data);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['shipping-zones'] });
toast.success(__('Zone created successfully'));
setShowAddZone(false);
},
onError: (error: any) => {
toast.error(error?.message || __('Failed to create zone'));
},
});
const updateZoneMutation = useMutation({
mutationFn: async ({ zoneId, ...data }: { zoneId: number; name?: string; regions?: any[] }) => {
return api.wpFetch(`/woonoow/v1/settings/shipping/zones/${zoneId}`, {
method: 'PUT',
body: JSON.stringify(data),
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['shipping-zones'] });
toast.success(__('Zone updated successfully'));
setEditingZone(null);
},
onError: (error: any) => {
toast.error(error?.message || __('Failed to update zone'));
},
});
const deleteZoneMutation = useMutation({
mutationFn: async (zoneId: number) => {
return api.del(`/settings/shipping/zones/${zoneId}`);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['shipping-zones'] });
toast.success(__('Zone deleted successfully'));
setDeletingZone(null);
},
onError: (error: any) => {
toast.error(error?.message || __('Failed to delete zone'));
},
});
if (isLoading) {
return (
<SettingsLayout
@@ -203,19 +252,27 @@ export default function ShippingPage() {
<SettingsCard
title={__('Shipping Zones')}
description={__('Create zones to group regions with similar shipping rates')}
action={
<Button
variant="outline"
size="sm"
onClick={() => setShowAddZone(true)}
>
<Plus className="h-4 w-4 mr-2" />
{__('Add Zone')}
</Button>
}
>
{zones.length === 0 ? (
<div className="text-center py-8">
<p className="text-sm text-muted-foreground mb-4">
{__('No shipping zones configured yet.')}
{__('No shipping zones configured yet. Create your first zone to start offering shipping.')}
</p>
<Button
variant="outline"
asChild
onClick={() => setShowAddZone(true)}
>
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping`} target="_blank" rel="noopener noreferrer">
{__('Configure in WooCommerce')}
</a>
<Plus className="h-4 w-4 mr-2" />
{__('Create First Zone')}
</Button>
</div>
) : (

View File

@@ -6,14 +6,20 @@ interface SettingsCardProps {
description?: string;
children: React.ReactNode;
className?: string;
action?: React.ReactNode;
}
export function SettingsCard({ title, description, children, className = '' }: SettingsCardProps) {
export function SettingsCard({ title, description, children, className = '', action }: SettingsCardProps) {
return (
<Card className={className}>
<CardHeader>
<CardTitle>{title}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<CardTitle>{title}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</div>
{action && <div className="flex-shrink-0">{action}</div>}
</div>
</CardHeader>
<CardContent className="space-y-4">
{children}