import { useMemo } from 'react'; import type { ReactNode } from 'react'; import { Button, Tooltip } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { info } from '@wordpress/icons'; import { hasSubItems, getSubItems } from 'utils'; import { getDimensionsUnit, getStoredPackageDimensions, getWeightUnit, } from 'utils/config'; import { ShipmentItem, ReturnShipmentInfo, Package, CustomPackage, Label, DimensionUnit, WeightUnit, } from 'types'; import { useLabelPurchaseContext } from 'context/label-purchase'; interface LabelWithPackageData extends Label { packageWeight?: number; packageWeightUnit?: WeightUnit; packageLength?: number; packageWidth?: number; packageHeight?: number; packageDimensionsUnit?: DimensionUnit; packageDimensionsUnitSnapshot?: DimensionUnit; packageWeightUnitSnapshot?: WeightUnit; } interface ReturnLabelButtonProps { isDomesticShipment: boolean; shipments: Record< string, ShipmentItem[] >; currentShipmentId: string; setShipments: ( shipments: Record< string, ShipmentItem[] > ) => void; setCurrentShipmentId: ( id: string ) => void; setReturnShipments: ( callback: ( prev: Record< string, ReturnShipmentInfo > ) => Record< string, ReturnShipmentInfo > ) => void; returnShipments?: Record< string, ReturnShipmentInfo >; packages: { getSelectedPackage: () => Package | CustomPackage | null; setSelectedPackage: ( pkg: Package | CustomPackage ) => void; }; selections: Record< string, ShipmentItem[] >; setSelection: ( selections: Record< string, ShipmentItem[] > ) => void; className?: string; children?: ReactNode; } export const ReturnLabelButton = ( { isDomesticShipment, shipments, currentShipmentId, setShipments, setCurrentShipmentId, setReturnShipments, returnShipments, packages, selections, setSelection, className, children, }: ReturnLabelButtonProps ) => { const { labels } = useLabelPurchaseContext(); // Check if all items have already been returned const { availableItems, allItemsReturned, currentShipmentItems } = useMemo( () => { const shipmentItems = shipments[ currentShipmentId ] || []; const returnedItemIds = new Set< number | string >(); // Check in-session return shipments (from returnShipments state) Object.entries( returnShipments ?? {} ).forEach( ( [ shipmentId, returnInfo ] ) => { if ( returnInfo.parentShipmentId === currentShipmentId && returnInfo.isReturn ) { // Use selections to get the actual items in this return shipment const returnShipmentItems = selections[ shipmentId ] || []; returnShipmentItems.forEach( ( item ) => { if ( item?.id ) { returnedItemIds.add( item.id ); } } ); } } ); // Also check ALL purchased labels to handle page reloads // Look for return labels that have parent_shipment_id matching current shipment Object.keys( shipments ).forEach( ( shipmentId ) => { // Skip the current shipment itself if ( shipmentId === currentShipmentId ) { return; } const shipmentLabel = labels.getCurrentShipmentLabel( shipmentId ); // Check if this is a return label for the current shipment if ( shipmentLabel && shipmentLabel.isReturn && shipmentLabel.parentShipmentId === currentShipmentId ) { // This is a return label for the current shipment // Mark all items from this shipment as returned const returnShipmentItems = shipments[ shipmentId ] || []; returnShipmentItems.forEach( ( item ) => { if ( item?.id ) { returnedItemIds.add( item.id ); } } ); } } ); const itemsAvailableForReturn = shipmentItems.filter( ( orderItem ) => { if ( orderItem === null ) { return false; } // Check if this specific order item has already been returned return ! returnedItemIds.has( orderItem.id ); } ); return { availableItems: itemsAvailableForReturn, allItemsReturned: itemsAvailableForReturn.length === 0, currentShipmentItems: shipmentItems, }; }, [ shipments, returnShipments, selections, currentShipmentId, labels, ] ); const createReturnLabel = () => { try { const newShipmentId = Object.keys( shipments ).length; const newShipmentIdString = `${ newShipmentId }`; // This check should never be reached since button is disabled, but keep as safety if ( allItemsReturned ) { return; } // Capture parent shipment ID BEFORE changing currentShipmentId // If current shipment is already a return, use its parent; otherwise use current const parentShipmentId = returnShipments?.[ currentShipmentId ]?.parentShipmentId ?? currentShipmentId; const newShipment = availableItems.map( ( orderItem ) => ( { ...orderItem, subItems: orderItem.subItems || [], } ) ); const allItems = newShipment .map( ( item ) => hasSubItems( item ) ? getSubItems( item ) : item ) .flat(); setShipments( { ...shipments, [ newShipmentIdString ]: newShipment, } ); setCurrentShipmentId( newShipmentIdString ); try { // Get package details from the purchased label for the current shipment let originalPackageDetails = null; const currentLabel = labels.getCurrentShipmentLabel( currentShipmentId ); // Try to get package dimensions from stored metadata first // This is more reliable for custom packages const storedPackageDimensions = getStoredPackageDimensions(); if ( storedPackageDimensions && typeof storedPackageDimensions === 'object' ) { const shipmentKey = `shipment_${ currentShipmentId }` as keyof typeof storedPackageDimensions; const shipmentDimensions = storedPackageDimensions[ shipmentKey ]; if ( shipmentDimensions?.[ 0 ] ) { originalPackageDetails = shipmentDimensions[ 0 ]; } } // Fallback: try to get from label object (for backward compatibility) if ( ! originalPackageDetails && currentLabel ) { const labelWithPackageData = currentLabel as LabelWithPackageData; if ( labelWithPackageData.packageWeight !== undefined || labelWithPackageData.packageLength !== undefined || labelWithPackageData.packageWidth !== undefined || labelWithPackageData.packageHeight !== undefined ) { originalPackageDetails = { package_weight: labelWithPackageData.packageWeight, package_weight_unit: labelWithPackageData.packageWeightUnit ?? labelWithPackageData.packageWeightUnitSnapshot ?? getWeightUnit(), package_length: labelWithPackageData.packageLength, package_width: labelWithPackageData.packageWidth, package_height: labelWithPackageData.packageHeight, package_dimensions_unit: labelWithPackageData.packageDimensionsUnit ?? labelWithPackageData.packageDimensionsUnitSnapshot ?? getDimensionsUnit(), }; } } // Determine preservedPackage based on available data let preservedPackage; if ( currentLabel && originalPackageDetails ) { // Preserve exact package data from stored dimensions or label preservedPackage = { packageWeight: originalPackageDetails.package_weight, packageWeightUnit: originalPackageDetails.package_weight_unit ?? originalPackageDetails.package_weight_unit_snapshot ?? getWeightUnit(), packageLength: originalPackageDetails.package_length, packageWidth: originalPackageDetails.package_width, packageHeight: originalPackageDetails.package_height, packageDimensionsUnit: originalPackageDetails.package_dimensions_unit ?? originalPackageDetails.package_dimensions_unit_snapshot ?? getDimensionsUnit(), packageName: currentLabel.packageName, packageId: currentLabel.id, }; } else if ( currentLabel ) { // Label exists but no dimensions - use label data only preservedPackage = { packageName: currentLabel.packageName, packageId: currentLabel.id, }; } else { // Fallback: calculate weight from items if no parent label preservedPackage = { totalWeight: currentShipmentItems.reduce( ( total, item ) => { const weight = parseFloat( item.weight || '0' ); const quantity = item.quantity || 1; return total + weight * quantity; }, 0 ), }; } setReturnShipments( ( prev ) => ( { ...prev, [ newShipmentIdString ]: { isReturn: true, parentShipmentId, preservedPackage, originalPackageDetails: originalPackageDetails ?? undefined, }, } ) ); } catch { // Handle setReturnShipments errors gracefully } setSelection( { ...selections, [ newShipmentIdString ]: allItems, } ); try { const selectedPackage = packages.getSelectedPackage(); if ( selectedPackage ) { packages.setSelectedPackage( selectedPackage ); } } catch { // Handle package errors gracefully - continue without package selection } } catch { // Handle any other errors gracefully } }; // Determine tooltip text and disabled state let tooltipText = ''; let isDisabled = false; if ( ! isDomesticShipment ) { tooltipText = __( 'Return labels are only available for domestic shipments (US to US)', 'woocommerce-shipping' ); isDisabled = true; } else if ( allItemsReturned ) { tooltipText = __( 'All items from this shipment have already been returned', 'woocommerce-shipping' ); isDisabled = true; } if ( isDisabled ) { return ( ); } return ( ); };