From f518d7e589c0520a6474e41be08aea854ebad9ee Mon Sep 17 00:00:00 2001 From: Dwindi Ramadhana Date: Thu, 8 Jan 2026 14:47:54 +0700 Subject: [PATCH] feat(checkout): fix searchable select API search and add billing destination Fixes: 1. SearchableSelect now supports onSearch prop for API-based search - Added onSearch and isSearching props - shouldFilter disabled when onSearch provided 2. DynamicCheckoutField connects handleApiSearch to SearchableSelect 3. RAJAONGKIR_INTEGRATION.md adds both billing and shipping destination_id This enables the destination search field to actually call the API when user types, instead of just filtering local (empty) options. --- RAJAONGKIR_INTEGRATION.md | 15 ++++++---- .../src/components/DynamicCheckoutField.tsx | 4 ++- .../src/components/ui/searchable-select.tsx | 30 +++++++++++++++++-- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/RAJAONGKIR_INTEGRATION.md b/RAJAONGKIR_INTEGRATION.md index 312d199..887c059 100644 --- a/RAJAONGKIR_INTEGRATION.md +++ b/RAJAONGKIR_INTEGRATION.md @@ -88,7 +88,7 @@ function woonoow_rajaongkir_search_destinations($request) { } // ============================================================ -// 2. Add destination field to checkout fields +// 2. Add destination field to checkout fields (both billing and shipping) // Always add for Indonesia zone (no premature country check) // ============================================================ add_filter('woocommerce_checkout_fields', function($fields) { @@ -103,12 +103,11 @@ add_filter('woocommerce_checkout_fields', function($fields) { return $fields; } - // Add searchable destination field - // The frontend will show/hide based on selected country - $fields['shipping']['shipping_destination_id'] = [ + // Destination field definition (reused for billing and shipping) + $destination_field = [ 'type' => 'searchable_select', 'label' => __('Destination (Province, City, Subdistrict)', 'woonoow'), - 'required' => false, // Not required initially, SPA will manage this + 'required' => false, // Frontend will manage this based on country 'priority' => 85, 'class' => ['form-row-wide'], 'placeholder' => __('Search destination...', 'woonoow'), @@ -122,6 +121,12 @@ add_filter('woocommerce_checkout_fields', function($fields) { ], ]; + // Add to billing (used when "Ship to different address" is NOT checked) + $fields['billing']['billing_destination_id'] = $destination_field; + + // Add to shipping (used when "Ship to different address" IS checked) + $fields['shipping']['shipping_destination_id'] = $destination_field; + return $fields; }, 20); // Priority 20 to run after Rajaongkir's own filter diff --git a/customer-spa/src/components/DynamicCheckoutField.tsx b/customer-spa/src/components/DynamicCheckoutField.tsx index 80106ee..d45d239 100644 --- a/customer-spa/src/components/DynamicCheckoutField.tsx +++ b/customer-spa/src/components/DynamicCheckoutField.tsx @@ -162,7 +162,9 @@ export function DynamicCheckoutField({ document.dispatchEvent(event); } }} - placeholder={isSearching ? 'Searching...' : (field.placeholder || `Search ${field.label}...`)} + onSearch={handleApiSearch} + isSearching={isSearching} + placeholder={field.placeholder || `Search ${field.label}...`} emptyLabel={ isSearching ? 'Searching...' diff --git a/customer-spa/src/components/ui/searchable-select.tsx b/customer-spa/src/components/ui/searchable-select.tsx index a184609..59dcf80 100644 --- a/customer-spa/src/components/ui/searchable-select.tsx +++ b/customer-spa/src/components/ui/searchable-select.tsx @@ -28,6 +28,9 @@ interface Props { emptyLabel?: string; className?: string; disabled?: boolean; + // For API-based search + onSearch?: (searchTerm: string) => void; + isSearching?: boolean; } export function SearchableSelect({ @@ -38,12 +41,26 @@ export function SearchableSelect({ emptyLabel = "No results found.", className, disabled = false, + onSearch, + isSearching = false, }: Props) { const [open, setOpen] = React.useState(false); + const [searchValue, setSearchValue] = React.useState(""); const selected = options.find((o) => o.value === value); React.useEffect(() => { if (disabled && open) setOpen(false); }, [disabled, open]); + // Handle search input changes + const handleSearchChange = (value: string) => { + setSearchValue(value); + if (onSearch) { + onSearch(value); + } + }; + + // Determine if we should use local filtering or API-based search + const shouldFilter = !onSearch; + return ( !disabled && setOpen(o)}> @@ -69,10 +86,16 @@ export function SearchableSelect({ align="start" sideOffset={4} > - - + + - {emptyLabel} + + {isSearching ? "Searching..." : emptyLabel} + {options.map((opt) => ( { onChange?.(opt.value); setOpen(false); + setSearchValue(""); }} >