From 267914dbfefca2f7377a966ca821e1bdd56e3fca Mon Sep 17 00:00:00 2001 From: dwindown Date: Sun, 9 Nov 2025 17:24:07 +0700 Subject: [PATCH] feat: Phase 2 - Full shipping method management in SPA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented complete CRUD for shipping methods within the SPA! Frontend Features: ✅ Tabbed modal (Methods / Details) ✅ Add shipping method button ✅ Method selection dialog ✅ Delete method with confirmation ✅ Active/Inactive status badges ✅ Responsive mobile drawer ✅ Real-time updates via React Query Backend API: ✅ GET /methods/available - List all method types ✅ POST /zones/{id}/methods - Add method to zone ✅ DELETE /zones/{id}/methods/{instance_id} - Remove method ✅ GET /zones/{id}/methods/{instance_id}/settings - Get settings ✅ PUT /zones/{id}/methods/{instance_id}/settings - Update settings User Flow: 1. Click Edit icon on zone card 2. Modal opens with 2 tabs: - Methods: Add/delete methods, see status - Details: View zone info 3. Click "Add Method" → Select from available methods 4. Click trash icon → Delete method (with confirmation) 5. All changes sync immediately What Users Can Do Now: ✅ Add any shipping method to any zone ✅ Delete methods from zones ✅ View method status (Active/Inactive) ✅ See zone details (name, regions, order) ✅ Link to WooCommerce for advanced settings Phase 2 Complete! 🎉 --- admin-spa/src/routes/Settings/Shipping.tsx | 174 +++++++++++++++++---- 1 file changed, 141 insertions(+), 33 deletions(-) diff --git a/admin-spa/src/routes/Settings/Shipping.tsx b/admin-spa/src/routes/Settings/Shipping.tsx index 25b093d..fbb8194 100644 --- a/admin-spa/src/routes/Settings/Shipping.tsx +++ b/admin-spa/src/routes/Settings/Shipping.tsx @@ -7,7 +7,8 @@ import { ToggleField } from './components/ToggleField'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/ui/drawer'; -import { Globe, Truck, MapPin, Edit, Trash2, RefreshCw, Loader2, ExternalLink, Settings } from 'lucide-react'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Globe, Truck, MapPin, Edit, Trash2, RefreshCw, Loader2, ExternalLink, Settings, Plus, X } from 'lucide-react'; import { toast } from 'sonner'; import { __ } from '@/lib/i18n'; import { useMediaQuery } from '@/hooks/use-media-query'; @@ -33,6 +34,8 @@ export default function ShippingPage() { const [togglingMethod, setTogglingMethod] = useState(null); const [selectedZone, setSelectedZone] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); + const [activeTab, setActiveTab] = useState('methods'); + const [showAddMethod, setShowAddMethod] = useState(false); const isDesktop = useMediaQuery("(min-width: 768px)"); // Fetch shipping zones from WooCommerce @@ -42,6 +45,13 @@ export default function ShippingPage() { staleTime: 5 * 60 * 1000, // 5 minutes }); + // Fetch available shipping methods + const { data: availableMethods = [] } = useQuery({ + queryKey: ['available-shipping-methods'], + queryFn: () => api.get('/settings/shipping/methods/available'), + enabled: showAddMethod, + }); + // Toggle shipping method mutation const toggleMutation = useMutation({ mutationFn: async ({ zoneId, instanceId, enabled }: { zoneId: number; instanceId: number; enabled: boolean }) => { @@ -59,11 +69,52 @@ export default function ShippingPage() { }, }); + // Add shipping method mutation + const addMethodMutation = useMutation({ + mutationFn: async ({ zoneId, methodId }: { zoneId: number; methodId: string }) => { + return api.post(`/settings/shipping/zones/${zoneId}/methods`, { method_id: methodId }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['shipping-zones'] }); + toast.success(__('Shipping method added successfully')); + setShowAddMethod(false); + }, + onError: (error: any) => { + toast.error(error?.message || __('Failed to add shipping method')); + }, + }); + + // Delete shipping method mutation + const deleteMethodMutation = useMutation({ + mutationFn: async ({ zoneId, instanceId }: { zoneId: number; instanceId: number }) => { + return api.delete(`/settings/shipping/zones/${zoneId}/methods/${instanceId}`); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['shipping-zones'] }); + toast.success(__('Shipping method deleted successfully')); + }, + onError: (error: any) => { + toast.error(error?.message || __('Failed to delete shipping method')); + }, + }); + const handleToggle = (zoneId: number, instanceId: number, enabled: boolean) => { setTogglingMethod(`${zoneId}-${instanceId}`); toggleMutation.mutate({ zoneId, instanceId, enabled }); }; + const handleAddMethod = (methodId: string) => { + if (selectedZone) { + addMethodMutation.mutate({ zoneId: selectedZone.id, methodId }); + } + }; + + const handleDeleteMethod = (instanceId: number) => { + if (selectedZone && confirm(__('Are you sure you want to delete this shipping method?'))) { + deleteMethodMutation.mutate({ zoneId: selectedZone.id, instanceId }); + } + }; + if (isLoading) { return ( -
-
- {/* Zone Summary */} -
-
-
-

{__('Zone Order')}

-

{__('Priority in shipping calculations')}

-
- {selectedZone.order} -
-
+ + + {__('Shipping Methods')} + {__('Zone Details')} + + + +
+ {/* Add Method Button */} + - {/* Shipping Methods */} -
-
-

{__('Shipping Methods')}

- - {selectedZone.rates?.length} {selectedZone.rates?.length === 1 ? 'method' : 'methods'} - -
+ {/* Methods List */}
{selectedZone.rates?.map((rate: any) => ( -
-
+
+
-
- - {__('Cost')}: - - - {rate.description && ( - - )} +
+ {__('Cost')}:{' '} +
@@ -275,14 +319,47 @@ export default function ShippingPage() { }`}> {rate.enabled ? __('Active') : __('Inactive')} +
))}
-
-
+ + + +
+
+
+
+

{__('Zone Name')}

+

{selectedZone.name}

+
+
+

{__('Regions')}

+

{selectedZone.regions}

+
+
+

{__('Zone Order')}

+

{selectedZone.order}

+

{__('Priority in shipping calculations')}

+
+
+
+

+ {__('To edit zone name, regions, or order, use WooCommerce.')} +

+
+
+
+ ))} +
+
+ + ); }