import { useCallback, useMemo, useState } from '@wordpress/element'; import { Button, CardBody, Flex, Modal, Notice } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { CourierSelectWithList } from '../../shared/components'; import type { Order } from '../../types/order'; import type { PluginSettings } from '../../types/settings'; import { useQuotesFetcher } from '../../shared/hooks'; import apiFetch from '@wordpress/api-fetch'; import type { QuotePayload, Service } from 'types/quote'; import { Spinner } from '@woocommerce/components'; interface SelectServiceModalProps { onClose: () => void; onRefreshOrders?: () => void; order: Order; settings: PluginSettings; } export default function SelectServiceModal({ onClose, onRefreshOrders, order, settings, }: SelectServiceModalProps) { const methodId = order.shipping_method?.id ?? null; const [selectedServices, setSelectedServices] = useState( methodId ? settings.defaultServiceCouriers[methodId] || [] : [] ); const [isSavingSelection, setIsSavingSelection] = useState(false); const [hasChanges, setHasChanges] = useState(false); const [saveFeedback, setSaveFeedback] = useState<{ status: 'success' | 'error'; message: string; } | null>(null); const defaultDimension = useMemo(() => { const defaultPackage = settings.packages.find( (p) => p.id === settings.defaultPackageId ); return { weight: defaultPackage?.weight || settings.overrides.defaultWeightKg || 1, length: defaultPackage?.length, width: defaultPackage?.width, height: defaultPackage?.height, }; }, []); const origin: QuotePayload['origin'] = { country: settings.shipping?.country, countryName: settings.shipping?.countryName, postcode: settings.shipping?.postcode, }; const destination: QuotePayload['destination'] = { country: order.shipping.country, countryName: order.shipping.country_name, postcode: order.shipping.postcode, }; const parcels = order.items.map((item, index) => { const itemDimension = item.dimensions; const ref = item.product_id ?? `parcel-${index}`; const value = parseFloat(item.total) * 100; if ( !itemDimension.width || !itemDimension.length || !itemDimension.height ) { return { ref, weight: defaultDimension.weight, height: defaultDimension.height, length: defaultDimension.length, width: defaultDimension.width, value, }; } return { ref, weight: Number(itemDimension.weight), height: Number(itemDimension.height), length: Number(itemDimension.length), width: Number(itemDimension.width), value, }; }); const quotesFetcher = useQuotesFetcher({ origin, destination, parcels }); const missingMethodMessage = __( 'This order has no shipping method/zone. Assign one in WooCommerce first, then set a default service.', 'parcel2go-shipping' ); const saveCouriers = useCallback( async (services: Service[]) => { if (!methodId) { setSaveFeedback({ status: 'error', message: missingMethodMessage, }); return; } await apiFetch({ path: '/parcel2go-shipping/v1/settings/default-service-couriers', method: 'PATCH', data: { methodId, services }, }); }, [methodId] ); const applySelectedServices = useCallback( async ( next: Service[], successMessage: string, errorMessage: string ) => { const previous = selectedServices; setIsSavingSelection(true); setSaveFeedback(null); setSelectedServices(next); try { await saveCouriers(next); if (!methodId) { setSelectedServices(previous); return; } setSaveFeedback({ status: 'success', message: successMessage }); setHasChanges(true); } catch { setSelectedServices(previous); setSaveFeedback({ status: 'error', message: errorMessage }); } finally { setIsSavingSelection(false); } }, [selectedServices, saveCouriers] ); const addCourierFromQuote = useCallback( async (service: Service) => { if (selectedServices.some((s) => s.slug === service.slug)) return; const next = [...selectedServices, service]; await applySelectedServices( next, __('Default service added successfully.', 'parcel2go-shipping'), __( 'Failed to add default service. Please try again.', 'parcel2go-shipping' ) ); quotesFetcher.setActiveCacheKey(null); }, [selectedServices, applySelectedServices, quotesFetcher] ); const removeCourier = useCallback( async (service: Service) => { const current = selectedServices ?? []; const next = current.filter((c) => c.slug !== service.slug); await applySelectedServices( next, __( 'Default service removed successfully.', 'parcel2go-shipping' ), __( 'Failed to remove default service. Please try again.', 'parcel2go-shipping' ) ); }, [selectedServices, applySelectedServices] ); const clearAll = useCallback(async () => { await applySelectedServices( [], __('Default services cleared successfully.', 'parcel2go-shipping'), __( 'Failed to clear default services. Please try again.', 'parcel2go-shipping' ) ); quotesFetcher.setActiveCacheKey(null); }, [applySelectedServices, quotesFetcher]); const handleDropdownOpen = useCallback(() => { if (!methodId) { setSaveFeedback({ status: 'error', message: missingMethodMessage }); return; } const key = `${methodId}-${order.shipping.country}`; quotesFetcher.setActiveCacheKey(key); quotesFetcher.fetchQuotes(key, destination); }, [order, destination, quotesFetcher]); const handleClose = useCallback(() => { if (hasChanges) { onRefreshOrders?.(); } onClose(); }, [hasChanges, onRefreshOrders, onClose, methodId]); const modalTitle = sprintf( __("Set a default service for '%s' ", 'parcel2go-shipping'), order.shipping_method?.title || __('Unknown shipping method', 'parcel2go-shipping') ); return (

You can select multiple services to be automatically applied to new orders the cheapest that can handle the order will be selected.

{sprintf( __('Quotes for order #%s', 'parcel2go-shipping'), order.id )}

{isSavingSelection && ( {__('Saving default services…', 'parcel2go-shipping')} )} {saveFeedback && ( setSaveFeedback(null)} > {saveFeedback.message} )}

Courier:

addCourierFromQuote(service)} onRemoveCourier={(service) => removeCourier(service)} canFetch={!isSavingSelection} missingMessage={null} clearAllButton={{ label: __('Clear All', 'parcel2go-shipping'), onClick: clearAll, disabled: isSavingSelection, isBusy: isSavingSelection, }} />
); }