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.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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...'
|
||||
|
||||
@@ -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 (
|
||||
<Popover open={disabled ? false : open} onOpenChange={(o) => !disabled && setOpen(o)}>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -69,10 +86,16 @@ export function SearchableSelect({
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
>
|
||||
<Command shouldFilter>
|
||||
<CommandInput placeholder="Search..." />
|
||||
<Command shouldFilter={shouldFilter}>
|
||||
<CommandInput
|
||||
placeholder="Search..."
|
||||
value={searchValue}
|
||||
onValueChange={handleSearchChange}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>{emptyLabel}</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
{isSearching ? "Searching..." : emptyLabel}
|
||||
</CommandEmpty>
|
||||
{options.map((opt) => (
|
||||
<CommandItem
|
||||
key={opt.value}
|
||||
@@ -80,6 +103,7 @@ export function SearchableSelect({
|
||||
onSelect={() => {
|
||||
onChange?.(opt.value);
|
||||
setOpen(false);
|
||||
setSearchValue("");
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
|
||||
Reference in New Issue
Block a user