feat: create CityAutocomplete airport search component
This commit is contained in:
@@ -1,184 +1,82 @@
|
|||||||
$space-m: 1rem;
|
|
||||||
$gray: #666;
|
|
||||||
$border-input: #ddd;
|
|
||||||
$red: #d32f2f;
|
|
||||||
$white: #fff;
|
|
||||||
$buttons-width: 45px;
|
|
||||||
$border-radius: 4px;
|
|
||||||
$standard-button-height: 42px;
|
|
||||||
|
|
||||||
.city-autocomplete {
|
.city-autocomplete {
|
||||||
display: flex;
|
width: 100%;
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
&__labels-container {
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: $space-m;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: $gray;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__error {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: $red;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
position: relative;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid $border-input;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
background-color: $white;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__field {
|
|
||||||
flex: 1;
|
|
||||||
border: none;
|
|
||||||
padding: 0.75rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
outline: none;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__clear-button {
|
|
||||||
width: $buttons-width !important;
|
|
||||||
min-width: $buttons-width;
|
|
||||||
border: none !important;
|
|
||||||
background-color: transparent !important;
|
|
||||||
height: $standard-button-height - 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
padding: 0;
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__search-button {
|
|
||||||
width: $buttons-width !important;
|
|
||||||
min-width: $buttons-width;
|
|
||||||
border-radius: 0 $border-radius $border-radius 0 !important;
|
|
||||||
border: none !important;
|
|
||||||
border-left: 1px solid $white !important;
|
|
||||||
background-color: transparent !important;
|
|
||||||
height: $standard-button-height - 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $white !important;
|
|
||||||
border-left-color: $border-input !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--opened {
|
|
||||||
background-color: #f5f5f5 !important;
|
|
||||||
border-left-color: $border-input !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input--has-value {
|
|
||||||
.city-autocomplete__clear-button {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.city-autocomplete__search-button {
|
|
||||||
border-left: 1px solid $border-input !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input--has-error {
|
|
||||||
border-color: $red;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__suggestions {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: $white;
|
|
||||||
border: 1px solid $border-input;
|
|
||||||
border-top: none;
|
|
||||||
border-radius: 0 0 $border-radius $border-radius;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__suggestion-item {
|
|
||||||
padding: 0.75rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__suggestion-name {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__suggestion-code {
|
|
||||||
font-weight: bold;
|
|
||||||
color: $gray;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.city-autocomplete-popup-wrapper {
|
.city-autocomplete__input-group {
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
z-index: 2000;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.p-autocomplete {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-autocomplete-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #1976d2;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-autocomplete-panel {
|
||||||
|
max-height: 300px;
|
||||||
|
|
||||||
|
.p-autocomplete-list {
|
||||||
|
max-height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.city-autocomplete-popup {
|
.city-autocomplete__item {
|
||||||
background-color: $white;
|
display: flex;
|
||||||
border-radius: $border-radius;
|
align-items: center;
|
||||||
padding: 2rem;
|
gap: 12px;
|
||||||
max-width: 600px;
|
padding: 8px;
|
||||||
width: 90%;
|
|
||||||
max-height: 80vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-clear {
|
.city-autocomplete__code {
|
||||||
display: none !important;
|
font-weight: 600;
|
||||||
|
color: #1976d2;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city-autocomplete__details {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city-autocomplete__name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city-autocomplete__country {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city-autocomplete__clear {
|
||||||
|
:global {
|
||||||
|
&.p-button-rounded {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.city-autocomplete__search {
|
||||||
|
:global {
|
||||||
|
&.p-button-rounded {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,212 +1,132 @@
|
|||||||
import React, { useState, useRef, useEffect, forwardRef, useCallback } from 'react'
|
import React, { useState, useCallback, useRef, useEffect } from 'react'
|
||||||
|
import { AutoComplete } from 'primereact/autocomplete'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import axios from 'axios'
|
||||||
import './city-autocomplete.scss'
|
import './city-autocomplete.scss'
|
||||||
|
|
||||||
export interface City {
|
export interface City {
|
||||||
code: string
|
code: string
|
||||||
name: string
|
name: string
|
||||||
|
country?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CityAutocompleteProps
|
export interface CityAutocompleteProps {
|
||||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'data-testid' | 'onChange' | 'onSelect'> {
|
value?: City | null
|
||||||
label?: string
|
onChange?: (city: City | null) => void
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
error?: string
|
disabled?: boolean
|
||||||
onErrorChange?: (error?: string) => void
|
onCitySelectClick?: () => void
|
||||||
onChange?: (value?: string) => void
|
|
||||||
onSelect?: (city: City) => void
|
|
||||||
'data-testid'?: string
|
'data-testid'?: string
|
||||||
|
label?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CityAutocomplete = forwardRef<HTMLInputElement, CityAutocompleteProps>(
|
export const CityAutocomplete: React.FC<CityAutocompleteProps> = ({
|
||||||
({
|
value,
|
||||||
label,
|
onChange,
|
||||||
placeholder = 'Enter city or airport',
|
placeholder = 'Select city',
|
||||||
error,
|
disabled = false,
|
||||||
onErrorChange,
|
onCitySelectClick,
|
||||||
onChange,
|
'data-testid': dataTestId,
|
||||||
onSelect,
|
}) => {
|
||||||
className = '',
|
const [suggestions, setSuggestions] = useState<City[]>([])
|
||||||
'data-testid': dataTestId = 'city-autocomplete-input',
|
const [loading, setLoading] = useState(false)
|
||||||
...props
|
const debounceTimer = useRef<NodeJS.Timeout | null>(null)
|
||||||
}, ref) => {
|
|
||||||
const [city, setCity] = useState<City | null>(null)
|
|
||||||
const [inputValue, setInputValue] = useState('')
|
|
||||||
const [suggestions, setSuggestions] = useState<City[]>([])
|
|
||||||
const [showSuggestions, setShowSuggestions] = useState(false)
|
|
||||||
const [openedPopup, setOpenedPopup] = useState(false)
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
|
||||||
const debounceTimer = useRef<NodeJS.Timeout | null>(null)
|
|
||||||
|
|
||||||
// Simulate fetching cities (in real app, would call an API)
|
const search = useCallback(async (query: string) => {
|
||||||
const filterCities = useCallback((query: string) => {
|
if (!query || query.length < 2) {
|
||||||
if (!query.trim()) {
|
|
||||||
setSuggestions([])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock cities data
|
|
||||||
const mockCities: City[] = [
|
|
||||||
{ code: 'MOW', name: 'Moscow' },
|
|
||||||
{ code: 'LED', name: 'Saint Petersburg' },
|
|
||||||
{ code: 'SVO', name: 'Sheremetyevo' },
|
|
||||||
{ code: 'DME', name: 'Domodedovo' },
|
|
||||||
{ code: 'NYC', name: 'New York' },
|
|
||||||
{ code: 'JFK', name: 'John F. Kennedy' },
|
|
||||||
{ code: 'PAR', name: 'Paris' },
|
|
||||||
{ code: 'CDG', name: 'Charles de Gaulle' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const filtered = mockCities.filter(
|
|
||||||
c =>
|
|
||||||
c.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
||||||
c.code.toUpperCase().includes(query.toUpperCase())
|
|
||||||
)
|
|
||||||
|
|
||||||
setSuggestions(filtered)
|
|
||||||
setShowSuggestions(filtered.length > 0)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Debounced input handler
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = e.target.value
|
|
||||||
setInputValue(value)
|
|
||||||
|
|
||||||
if (debounceTimer.current) {
|
|
||||||
clearTimeout(debounceTimer.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
debounceTimer.current = setTimeout(() => {
|
|
||||||
filterCities(value)
|
|
||||||
}, 300)
|
|
||||||
|
|
||||||
resetError()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelectCity = (selectedCity: City) => {
|
|
||||||
setCity(selectedCity)
|
|
||||||
setInputValue(selectedCity.name)
|
|
||||||
setSuggestions([])
|
setSuggestions([])
|
||||||
setShowSuggestions(false)
|
return
|
||||||
onChange?.(selectedCity.code)
|
|
||||||
onSelect?.(selectedCity)
|
|
||||||
resetError()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClearInput = () => {
|
try {
|
||||||
setCity(null)
|
setLoading(true)
|
||||||
setInputValue('')
|
const response = await axios.get(`/api/cities/search`, {
|
||||||
|
params: { q: query },
|
||||||
|
})
|
||||||
|
setSuggestions(response.data || [])
|
||||||
|
} catch (error) {
|
||||||
|
console.error('City search failed:', error)
|
||||||
setSuggestions([])
|
setSuggestions([])
|
||||||
setShowSuggestions(false)
|
} finally {
|
||||||
onChange?.(undefined)
|
setLoading(false)
|
||||||
resetError()
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSearch = (e: { query: string }) => {
|
||||||
|
// Clear existing debounce timer
|
||||||
|
if (debounceTimer.current) {
|
||||||
|
clearTimeout(debounceTimer.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePopupToggle = () => {
|
// Debounce the API call
|
||||||
setOpenedPopup(!openedPopup)
|
debounceTimer.current = setTimeout(() => {
|
||||||
resetError()
|
search(e.query)
|
||||||
}
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
const resetError = () => {
|
const handleSelect = (city: City) => {
|
||||||
onErrorChange?.(undefined)
|
onChange?.(city)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close popup when clicking outside
|
const handleClear = () => {
|
||||||
useEffect(() => {
|
onChange?.(null)
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
setSuggestions([])
|
||||||
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
}
|
||||||
setShowSuggestions(false)
|
|
||||||
setOpenedPopup(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('click', handleClickOutside)
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('click', handleClickOutside)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
const itemTemplate = (city: City) => {
|
||||||
return (
|
return (
|
||||||
<div className="city-autocomplete" ref={containerRef}>
|
<div className="city-autocomplete__item">
|
||||||
<div className="city-autocomplete__labels-container">
|
<div className="city-autocomplete__code">{city.code}</div>
|
||||||
{label && <label className="city-autocomplete__label">{label}</label>}
|
<div className="city-autocomplete__details">
|
||||||
{city && <label className="city-autocomplete__label" data-testid="city-code">{city.code}</label>}
|
<div className="city-autocomplete__name">{city.name}</div>
|
||||||
|
{city.country && <div className="city-autocomplete__country">{city.country}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
|
||||||
<div className="city-autocomplete__error">
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`city-autocomplete__input ${city ? 'city-autocomplete__input--has-value' : ''} ${
|
|
||||||
error ? 'city-autocomplete__input--has-error' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
ref={ref}
|
|
||||||
type="text"
|
|
||||||
className={`city-autocomplete__field ${className}`.trim()}
|
|
||||||
placeholder={placeholder}
|
|
||||||
value={inputValue}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
data-testid={dataTestId}
|
|
||||||
autoComplete="off"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{city && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="city-autocomplete__clear-button button-clear"
|
|
||||||
onClick={handleClearInput}
|
|
||||||
data-testid="autocomplete-clear-input"
|
|
||||||
aria-label="Clear input"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`city-autocomplete__search-button ${
|
|
||||||
openedPopup ? 'city-autocomplete__search-button--opened' : ''
|
|
||||||
}`}
|
|
||||||
onClick={handlePopupToggle}
|
|
||||||
data-testid="autocomplete-popup-button"
|
|
||||||
aria-label="Toggle city selector"
|
|
||||||
>
|
|
||||||
▼
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{showSuggestions && suggestions.length > 0 && (
|
|
||||||
<div className="city-autocomplete__suggestions">
|
|
||||||
{suggestions.map(suggestion => (
|
|
||||||
<div
|
|
||||||
key={suggestion.code}
|
|
||||||
className="city-autocomplete__suggestion-item"
|
|
||||||
onClick={() => handleSelectCity(suggestion)}
|
|
||||||
data-testid={`suggestion-${suggestion.code}`}
|
|
||||||
>
|
|
||||||
<span className="city-autocomplete__suggestion-name">{suggestion.name}</span>
|
|
||||||
<span className="city-autocomplete__suggestion-code">{suggestion.code}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{openedPopup && (
|
|
||||||
<div className="city-autocomplete-popup-wrapper" data-testid="city-select-popup">
|
|
||||||
{/* City selector popup would go here */}
|
|
||||||
<div className="city-autocomplete-popup">
|
|
||||||
<p>City selector popup</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
CityAutocomplete.displayName = 'CityAutocomplete'
|
// Cleanup debounce timer on unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (debounceTimer.current) {
|
||||||
|
clearTimeout(debounceTimer.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="city-autocomplete" data-testid={dataTestId || 'city-autocomplete'}>
|
||||||
|
<div className="city-autocomplete__input-group">
|
||||||
|
<AutoComplete<City>
|
||||||
|
value={value || undefined}
|
||||||
|
suggestions={suggestions}
|
||||||
|
completeMethod={handleSearch}
|
||||||
|
field="name"
|
||||||
|
onSelect={(e) => handleSelect(e.value)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
disabled={disabled || loading}
|
||||||
|
itemTemplate={itemTemplate}
|
||||||
|
inputClassName="city-autocomplete__field"
|
||||||
|
dropdown
|
||||||
|
forceSelection
|
||||||
|
/>
|
||||||
|
{value && (
|
||||||
|
<Button
|
||||||
|
icon="pi pi-times"
|
||||||
|
className="p-button-rounded p-button-text city-autocomplete__clear"
|
||||||
|
onClick={handleClear}
|
||||||
|
data-testid={`${dataTestId || 'city-autocomplete'}-clear`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{onCitySelectClick && (
|
||||||
|
<Button
|
||||||
|
icon="pi pi-search"
|
||||||
|
className="p-button-rounded p-button-text city-autocomplete__search"
|
||||||
|
onClick={onCitySelectClick}
|
||||||
|
data-testid={`${dataTestId || 'city-autocomplete'}-search`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { CityAutocomplete } from '../../components/city-autocomplete'
|
import { CityAutocomplete, City } from '../../components/city-autocomplete'
|
||||||
import { Button } from '../../components/button'
|
import { Button } from '../../components/button'
|
||||||
import { DatePicker } from '../../components/datepicker'
|
import { DatePicker } from '../../components/datepicker'
|
||||||
import { Flight } from '../OnlineBoard'
|
import { Flight } from '../OnlineBoard'
|
||||||
@@ -63,8 +63,8 @@ const OnlineBoardSearch: React.FC<OnlineBoardSearchProps> = ({
|
|||||||
setSearchError(undefined)
|
setSearchError(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDestinationChange = (cityCode?: string) => {
|
const handleDestinationChange = (city: City | null) => {
|
||||||
setDestination(cityCode)
|
setDestination(city?.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -72,6 +72,7 @@ const OnlineBoardSearch: React.FC<OnlineBoardSearchProps> = ({
|
|||||||
<div className="online-board-search__container">
|
<div className="online-board-search__container">
|
||||||
<div className="online-board-search__field">
|
<div className="online-board-search__field">
|
||||||
<CityAutocomplete
|
<CityAutocomplete
|
||||||
|
value={destination ? { code: destination, name: '' } : null}
|
||||||
label={mode === 'arrivals' ? 'From' : 'To'}
|
label={mode === 'arrivals' ? 'From' : 'To'}
|
||||||
placeholder="Select city or airport"
|
placeholder="Select city or airport"
|
||||||
onChange={handleDestinationChange}
|
onChange={handleDestinationChange}
|
||||||
|
|||||||
Reference in New Issue
Block a user