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)
|
// Always add for Indonesia zone (no premature country check)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
add_filter('woocommerce_checkout_fields', function($fields) {
|
add_filter('woocommerce_checkout_fields', function($fields) {
|
||||||
@@ -103,12 +103,11 @@ add_filter('woocommerce_checkout_fields', function($fields) {
|
|||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add searchable destination field
|
// Destination field definition (reused for billing and shipping)
|
||||||
// The frontend will show/hide based on selected country
|
$destination_field = [
|
||||||
$fields['shipping']['shipping_destination_id'] = [
|
|
||||||
'type' => 'searchable_select',
|
'type' => 'searchable_select',
|
||||||
'label' => __('Destination (Province, City, Subdistrict)', 'woonoow'),
|
'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,
|
'priority' => 85,
|
||||||
'class' => ['form-row-wide'],
|
'class' => ['form-row-wide'],
|
||||||
'placeholder' => __('Search destination...', 'woonoow'),
|
'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;
|
return $fields;
|
||||||
}, 20); // Priority 20 to run after Rajaongkir's own filter
|
}, 20); // Priority 20 to run after Rajaongkir's own filter
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,9 @@ export function DynamicCheckoutField({
|
|||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder={isSearching ? 'Searching...' : (field.placeholder || `Search ${field.label}...`)}
|
onSearch={handleApiSearch}
|
||||||
|
isSearching={isSearching}
|
||||||
|
placeholder={field.placeholder || `Search ${field.label}...`}
|
||||||
emptyLabel={
|
emptyLabel={
|
||||||
isSearching
|
isSearching
|
||||||
? 'Searching...'
|
? 'Searching...'
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ interface Props {
|
|||||||
emptyLabel?: string;
|
emptyLabel?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
// For API-based search
|
||||||
|
onSearch?: (searchTerm: string) => void;
|
||||||
|
isSearching?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SearchableSelect({
|
export function SearchableSelect({
|
||||||
@@ -38,12 +41,26 @@ export function SearchableSelect({
|
|||||||
emptyLabel = "No results found.",
|
emptyLabel = "No results found.",
|
||||||
className,
|
className,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
onSearch,
|
||||||
|
isSearching = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [searchValue, setSearchValue] = React.useState("");
|
||||||
const selected = options.find((o) => o.value === value);
|
const selected = options.find((o) => o.value === value);
|
||||||
|
|
||||||
React.useEffect(() => { if (disabled && open) setOpen(false); }, [disabled, open]);
|
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 (
|
return (
|
||||||
<Popover open={disabled ? false : open} onOpenChange={(o) => !disabled && setOpen(o)}>
|
<Popover open={disabled ? false : open} onOpenChange={(o) => !disabled && setOpen(o)}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
@@ -69,10 +86,16 @@ export function SearchableSelect({
|
|||||||
align="start"
|
align="start"
|
||||||
sideOffset={4}
|
sideOffset={4}
|
||||||
>
|
>
|
||||||
<Command shouldFilter>
|
<Command shouldFilter={shouldFilter}>
|
||||||
<CommandInput placeholder="Search..." />
|
<CommandInput
|
||||||
|
placeholder="Search..."
|
||||||
|
value={searchValue}
|
||||||
|
onValueChange={handleSearchChange}
|
||||||
|
/>
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>{emptyLabel}</CommandEmpty>
|
<CommandEmpty>
|
||||||
|
{isSearching ? "Searching..." : emptyLabel}
|
||||||
|
</CommandEmpty>
|
||||||
{options.map((opt) => (
|
{options.map((opt) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={opt.value}
|
key={opt.value}
|
||||||
@@ -80,6 +103,7 @@ export function SearchableSelect({
|
|||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
onChange?.(opt.value);
|
onChange?.(opt.value);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
setSearchValue("");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Check
|
<Check
|
||||||
|
|||||||
Reference in New Issue
Block a user