/** * Packaging section: one accordion per order item. Manual dimensions only. * Header = item name + dimensions (truncated). Expanded: manual L/W/H/weight; Apply refetches quotes. */ import { __ } from '@wordpress/i18n'; import { useState, useCallback, memo } from '@wordpress/element'; import { Button, Card, CardBody, Flex, FlexItem, Notice, TextControl, } from '@wordpress/components'; import { chevronDown, chevronUp } from '@wordpress/icons'; import { Icon } from '@wordpress/components'; import { useWindowSize } from '../../shared/hooks'; import { formatDisplayNumber, getDimensionBounds, getWeightBounds, parseWeight, getApiErrorMessage, truncateText, } from '../../shared/utils'; import type { Order, OrderItem } from '../../types'; import type { QuotePayload } from '../../types/quote'; import { buildQuotePayloadFromItems, type ItemDimensions, } from './utils/orderToQuotePayload'; import { StoreSettingsResponse } from 'types/settings'; const MAX_TITLE_LENGTH = 40; const DEFAULT_DIM = 10; const DEFAULT_WEIGHT = 1; function itemDims(dims: OrderItem['dimensions'] | undefined): ItemDimensions { const length = dims?.length != null ? Number(dims.length) : 0; const width = dims?.width != null ? Number(dims.width) : 0; const height = dims?.height != null ? Number(dims.height) : 0; const weight = dims?.weight != null ? parseWeight(dims.weight) : 0; return { length: length > 0 ? length : DEFAULT_DIM, width: width > 0 ? width : DEFAULT_DIM, height: height > 0 ? height : DEFAULT_DIM, weight: weight > 0 ? weight : DEFAULT_WEIGHT, }; } export interface PackagingSectionProps { order: Order; store: StoreSettingsResponse | null; onFetchQuotes: (payload: QuotePayload) => Promise; } interface PackageItemProps { item: OrderItem; index: number; isExpanded: boolean; dimensions: ItemDimensions; manualOverride: ItemDimensions | null; dUnit: string; wUnit: string; isApplying: boolean; isWide: boolean; onToggle: () => void; onOverrideChange: (dims: ItemDimensions | null) => void; onApply: () => void; isLastItem: boolean; } const PackageItem = memo(function PackageItem({ item, isExpanded, dimensions, manualOverride, dUnit, wUnit, isApplying, isWide, onToggle, onOverrideChange, onApply, isLastItem, }) { const title = truncateText(item.name, MAX_TITLE_LENGTH); const displayDims = manualOverride ?? dimensions; const dimensionsText = `(${formatDisplayNumber(displayDims.length)}×${formatDisplayNumber(displayDims.width)}×${formatDisplayNumber(displayDims.height)})${dUnit}`; const dimensionBounds = getDimensionBounds(dUnit || 'cm'); const weightBounds = getWeightBounds(wUnit || 'kg'); const setManualDim = useCallback( (field: keyof ItemDimensions, value: number) => { onOverrideChange({ length: field === 'length' ? value : displayDims.length, width: field === 'width' ? value : displayDims.width, height: field === 'height' ? value : displayDims.height, weight: field === 'weight' ? value : displayDims.weight, }); }, [onOverrideChange, displayDims] ); return (
{isExpanded && (

{__('Enter dimensions manually', 'parcel2go-shipping')}

setManualDim('length', parseFloat(v || '0') || 0) } /> setManualDim('width', parseFloat(v || '0') || 0) } /> setManualDim('height', parseFloat(v || '0') || 0) } /> setManualDim('weight', parseFloat(v || '0') || 0) } />
)}
); }); export default function PackagingSection({ order, store, onFetchQuotes, }: PackagingSectionProps) { const items = order.items ?? []; const dUnit = order.units?.dimension ?? 'cm'; const wUnit = order.units?.weight ?? 'kg'; const [expandedIndex, setExpandedIndex] = useState(null); const [overrides, setOverrides] = useState>({}); const [applyingIndex, setApplyingIndex] = useState(null); const [successMessage, setSuccessMessage] = useState(null); const [applyError, setApplyError] = useState(null); const { width } = useWindowSize(); const isWide = width >= 600; const getItemDimensions = useCallback( (index: number, item: OrderItem): ItemDimensions => { const itemOverride = overrides[index]; if (itemOverride) { return itemOverride; } return itemDims(item.dimensions); }, [overrides] ); const handleApply = useCallback( async (itemIndex: number) => { const payload = buildQuotePayloadFromItems(order, store, getItemDimensions); if (!payload) return; setApplyingIndex(itemIndex); setApplyError(null); setSuccessMessage(null); try { await onFetchQuotes(payload); setSuccessMessage(__('Quotes updated successfully.', 'parcel2go-shipping')); } catch (err: unknown) { setApplyError( getApiErrorMessage( err, __('Failed to update quotes. Please try again.', 'parcel2go-shipping') ) ); } finally { setApplyingIndex(null); } }, [order, store, getItemDimensions, onFetchQuotes] ); const setOverride = useCallback((index: number, dims: ItemDimensions | null) => { setOverrides((prev) => { if (dims == null) { const next = { ...prev }; delete next[index]; return next; } return { ...prev, [index]: dims }; }); }, []); if (!items.length) { return (

{__('No items in this order.', 'parcel2go-shipping')}

); } return (

{__('Packaging', 'parcel2go-shipping')}

{successMessage && ( setSuccessMessage(null)} > {successMessage} )} {applyError && ( setApplyError(null)} > {applyError} )} {items.map((item, index) => { const dimensions = getItemDimensions(index, item); return ( setExpandedIndex(expandedIndex === index ? null : index) } onOverrideChange={(dims) => setOverride(index, dims)} onApply={() => handleApply(index)} isLastItem={index === items.length - 1} /> ); })}
); }