diff --git a/src/components/address-search/AddressSearchInput.js b/src/components/address-search/AddressSearchInput.js index 08c5157dc..3b1a6c423 100644 --- a/src/components/address-search/AddressSearchInput.js +++ b/src/components/address-search/AddressSearchInput.js @@ -9,34 +9,44 @@ import Loader from '$components/loader/Loader'; import LoaderWrapper from '$components/loader/LoaderWrapper'; import {stringifyQuery} from '$src/api/createUrl'; import {KeyCodes} from '$src/enums'; -import {findFromOcdString, hasNumber} from '$util/helpers'; +import {findFromOcdString} from '$util/helpers'; const SERVICE_MAP_URL = 'https://api.hel.fi/servicemap/v2'; type Language = 'fi' | 'sv'; +const MINIMUM_SEARCH_STRING = 3 +const DEBOUNCE_TIME_MILLISECONDS = 500 -type Street = { - id: number, - language: Language, - municipality: string, +type Address = { + object_type: string, name: { - fi: string, - sv: string, + fi: string, + sv: string, + en: string }, -} - -type Address = { number: string, - number_end: ?string, - letter: ?string, - location: { - coordinates: Array + number_end: string, + letter: string, + modified_at: string, + municipality: { + id: string, + name: { + fi: string, + sv: string + } }, - street: Street, -} + street: { + name: { + fi: string, + sv: string + } + }, + location: { + type: string, + coordinates: Array + }} type Props = { addressDetailsCallBack?: Function, - autoComplete: string, id?: string, name?: string, onBlur?: Function, @@ -47,13 +57,11 @@ type Props = { type State = { addresses: Array
, filteredAddresses: Array
, - focusedType: 'street' | 'address' | null, focusedValue: ?Object, hasFocus: boolean, isLoading: boolean, menuOpen: boolean, - selectedStreet: ?Street, - streets: Array, + selectedAddress: ?Address, value: ?string, } @@ -64,13 +72,11 @@ class AddressSearchInput extends Component { state = { addresses: [], filteredAddresses: [], - focusedType: null, focusedValue: null, hasFocus: false, isLoading: false, menuOpen: false, - selectedStreet: null, - streets: [], + selectedAddress: null, value: '', } @@ -92,13 +98,6 @@ class AddressSearchInput extends Component { } componentDidUpdate(prevProps: Props, prevState: State) { - if(prevState.addresses !== this.state.addresses || - prevState.value !== this.state.value) { - this.setState({ - filteredAddresses: this.filterAddresses(), - }); - } - if(prevState.focusedValue !== this.state.focusedValue) { this.scrollToFocusedItem(); } @@ -131,12 +130,6 @@ class AddressSearchInput extends Component { } } - filterAddresses = (): Array
=> { - const {addresses, value} = this.state; - - return addresses.filter((address) => this.getAddressText(address).toLowerCase().startsWith(value ? value.toLowerCase() : '')); - } - onDocumentClick = (event: any) => { const {menuOpen} = this.state; const target = event.target, @@ -157,14 +150,13 @@ class AddressSearchInput extends Component { closeMenu = () => { this.setState({ - focusedType: null, focusedValue: null, menuOpen: false, }); } onKeyDown = (e: any) => { - const {focusedType, focusedValue, hasFocus} = this.state; + const {focusedValue, hasFocus} = this.state; if(hasFocus) { switch(e.keyCode) { @@ -180,40 +172,14 @@ class AddressSearchInput extends Component { break; case KeyCodes.ENTER: if(focusedValue) { - switch (focusedType) { - case 'address': - this.handleAddressItemClick(focusedValue); - break; - case 'street': - this.handleStreetItemClick(focusedValue); - break; - } - } else { - const {filteredAddresses, selectedStreet, value} = this.state; - - if(selectedStreet) { - const address = filteredAddresses.find((address) => this.getAddressText(address).toLowerCase() === (value ? value.toLowerCase() : '')); - - if(address) { - this.handleAddressItemClick(address); - } - } + this.handleAddressItemClick(focusedValue); } break; case KeyCodes.ESC: this.closeMenu(); break; case KeyCodes.TAB: - const {filteredAddresses, selectedStreet, value} = this.state; - - if(selectedStreet) { - const address = filteredAddresses.find((address) => this.getAddressText(address).toLowerCase() === (value ? value.toLowerCase() : '')); - - if(address) { - this.handleAddressItemClick(address); - } - } - + e.preventDefault(); this.closeMenu(); break; } @@ -240,63 +206,35 @@ class AddressSearchInput extends Component { } focusValue = (direction: 'next' | 'previous') => { - const {focusedValue, selectedStreet} = this.state; - - if(!selectedStreet) { - const {streets} = this.state; - const index = streets.findIndex((street) => focusedValue === street); - - switch(direction) { - case 'next': - if(index < (streets.length - 1)) { - this.setState({ - focusedType: 'street', - focusedValue: streets[index + 1], - menuOpen: true, - }); - } - break; - case 'previous': - if(index > (0)) { - this.setState({ - focusedType: 'street', - focusedValue: streets[index - 1], - menuOpen: true, - }); - } - break; - } - } else { - const {filteredAddresses} = this.state; - const index = filteredAddresses.findIndex((address) => focusedValue === address); - - switch(direction) { - case 'next': - if(index < (filteredAddresses.length - 1)) { - this.setState({ - focusedType: 'address', - focusedValue: filteredAddresses[index + 1], - menuOpen: true, - }); - } - break; - case 'previous': - if(index) { - this.setState({ - focusedType: 'address', - focusedValue: filteredAddresses[index - 1], - menuOpen: true, - }); - } - break; - } + const {focusedValue} = this.state; + + const {addresses} = this.state; + const index = addresses.findIndex((address) => focusedValue === address); + + switch(direction) { + case 'next': + if(index < (addresses.length - 1)) { + this.setState({ + focusedValue: addresses[index + 1], + menuOpen: true, + }); + } + break; + case 'previous': + if(index > (0)) { + this.setState({ + focusedValue: addresses[index - 1], + menuOpen: true, + }); + } + break; } } - searchByKeyword = (input: string) => { - const fetchByKeyword = (language: 'fi' | 'sv') => { + searchByKeyword = debounce((input: string) => { + const fetchByKeyword = (language: Language) => { const url = `${SERVICE_MAP_URL}/search/?${stringifyQuery({ - page_size: 4, + page_size: 25, type: 'address', input: input, language: language, @@ -308,14 +246,11 @@ class AddressSearchInput extends Component { const fetchResults = async() => { const fiResponse = await fetchByKeyword('fi'); - const svResponse = await fetchByKeyword('sv'); const fiResults = await fiResponse.json(); - const svResults = await svResponse.json(); return [ - ...fiResults.results.map((street) => ({...street, language: 'fi'})), - ...svResults.results.map((street) => ({...street, language: 'sv'})), + ...fiResults.results.map((address) => ({...address, language: 'fi'})), ]; }; @@ -324,12 +259,12 @@ class AddressSearchInput extends Component { fetchResults() .then((results) => { if(results.length) { - const street = { - ...results[0].street, - language: results[0].language, - }; + this.setState({ + addresses: results, + selectedAddress: results[0], + }); - this.searchAddresses(street); + this.setState({isLoading: false}); } else { this.setState({isLoading: false}); } @@ -338,175 +273,35 @@ class AddressSearchInput extends Component { this.setState({isLoading: false}); console.error(`Failed to fetch by keyword with error ${error}`); }); - } - - sortStreets = (a: Street, b: Street) => { - const aStreet = a.name[a.language] ? a.name[a.language].toLowerCase() : ''; - const bStreet = b.name[b.language] ? b.name[b.language].toLowerCase() : ''; - - if(aStreet < bStreet) return -1; - if(aStreet > bStreet) return 1; - return 0; - } - - searchStreets = debounce((input: string) => { - const fetchStreets = (language: 'fi' | 'sv') => { - const url = `${SERVICE_MAP_URL}/street/?${stringifyQuery({ - page_size: 4, - input: input, - language: language, - })}`; - const request = new Request(url); - - return fetch(request); - }; - - const fetchAllStreets = async() => { - const fiResponse = await fetchStreets('fi'); - const svResponse = await fetchStreets('sv'); - - const fiResults = await fiResponse.json(); - const svResults = await svResponse.json(); - - return [ - ...fiResults.results.map((street) => ({...street, language: 'fi'})), - ...svResults.results.map((street) => ({...street, language: 'sv'})), - ]; - }; - - this.setState({isLoading: true}); - - fetchAllStreets() - .then((results) => { - const streets = results.sort(this.sortStreets), - newState: any = {streets: streets}; - - if(streets.length === 1) { - const {selectedStreet} = this.state; - - if(!selectedStreet || (streets[0].id !== selectedStreet.id)) { - newState.selectedStreet = streets[0]; - this.searchAddresses(streets[0]); - } - } else { - newState.selectedStreet = null; - newState.addresses = []; - } - - newState.isLoading = false; - this.setState(newState); - }) - .catch((error) => { - this.setState({isLoading: false}); - console.error(`Failed to fetch streets with error ${error}`); - }); - }, 300); + }, DEBOUNCE_TIME_MILLISECONDS) handleOnChange = (e: any) => { const {onChange} = this.props; - const {selectedStreet} = this.state; const newValue = e.target.value; - if(!selectedStreet && hasNumber(newValue)) { + if(newValue && newValue?.length >= MINIMUM_SEARCH_STRING) { this.searchByKeyword(newValue); - } else if(!selectedStreet || (selectedStreet && !newValue.startsWith(selectedStreet.name[selectedStreet.language]))) { - this.searchStreets(newValue); } - this.setState({ - menuOpen: newValue ? true : false, - value: newValue, - }); - - if(onChange) { - onChange(newValue); - } - } - - getStreetText = (street: Street) => `${street.name[street.language]}`; - - getFullStreetText = (street: Street) => `${street.name[street.language]}, ${capitalize(street.municipality)}`; - - sortAddresses = (a: Address, b: Address) => { - if(a.number === b.number) { - const aLetter = a.letter ? a.letter : ''; - const bLetter = b.letter ? b.letter : ''; - - if(aLetter < bLetter) return -1; - if(aLetter > bLetter) return 1; - return 0; - } - - return Number(a.number) - Number(b.number); - } - - searchAddresses = (street: Street) => { - const url = `${SERVICE_MAP_URL}/address/?${stringifyQuery({ - page: 1, - page_size: 200, - street: street.id, - language: street.language, - })}`; - const request = new Request(url); - - this.setState({isLoading: true}); - - fetch(request) - .then((response) => response.json()) - .then((results) => { - this.setState({ - addresses: results.results.sort(this.sortAddresses), - selectedStreet: street, - }); - - this.setState({isLoading: false}); + if (!newValue || newValue?.length < MINIMUM_SEARCH_STRING) { + this.setState({ + addresses: [] }) - .catch((error) => { - this.setState({isLoading: false}); - console.error(`Failed to fetch addresses with error ${error}`); - }); - }; - - handleStreetItemClick = (street: Street) => { - const {onChange} = this.props, - newValue = `${this.getStreetText(street)} `; - - this.searchAddresses(street); + } this.setState({ - focusedType: null, - focusedValue: null, + menuOpen: newValue ? true : false, value: newValue, }); if(onChange) { onChange(newValue); } - - if(this.input) { - this.input.focus(); - } } - getAddressText = (address: Address) => { - const {selectedStreet} = this.state; - const numberText = `${address.number}${address.letter || ''}${address.number_end ? ` - ${address.number_end}` : ''}`; - - return selectedStreet ? `${this.getStreetText(selectedStreet)} ${numberText}` : ''; - }; - - getFullAddressText = (address: Address) => { - const {selectedStreet} = this.state; - const numberText = `${address.number}${address.letter || ''}${address.number_end ? ` - ${address.number_end}` : ''}`; - - return selectedStreet - ? `${this.getStreetText(selectedStreet)} ${numberText}, ${capitalize(address.street.municipality)}` - : ''; - }; - handleAddressItemClick = (address: Address) => { const {onChange} = this.props, - newValue = `${this.getAddressText(address)}`; + newValue = address.name.fi; this.setState({value: newValue}); this.closeMenu(); @@ -551,7 +346,7 @@ class AddressSearchInput extends Component { if(addressDetailsCallBack) { addressDetailsCallBack({ postalCode, - city: address.street.municipality ? capitalize(address.street.municipality) : '', + city: address.municipality.name.fi ? capitalize(address.municipality.name.fi) : '', country, }); } @@ -565,8 +360,9 @@ class AddressSearchInput extends Component { }; render() { - const {autoComplete, id, name, selected} = this.props; - const {addresses, filteredAddresses, focusedValue, isLoading, menuOpen, selectedStreet, streets, value} = this.state; + const {id, name, selected} = this.props; + const {addresses, focusedValue, isLoading, menuOpen, selectedAddress, value} = this.state; + return(
@@ -575,7 +371,7 @@ class AddressSearchInput extends Component { } { />
    - {!selectedStreet && !streets.length && -
  • Ei katuja
  • - } - {(!selectedStreet || !addresses.length) && !!streets.length && streets.map((street, index) => { - const handleClick = () => { - this.handleStreetItemClick(street); - }; - - const text = this.getFullStreetText(street); - - return
  • {text}
  • ; - })} - {selectedStreet && !!addresses.length && !filteredAddresses.length && -
  • Ei osoitteita
  • + {selectedAddress && !addresses.length && +
  • Ei osoitteita.
  • } - {selectedStreet && !!filteredAddresses.length && filteredAddresses.map((address, index) => { + {selectedAddress && !!addresses.length && addresses.map((address, index) => { const handleClick = () => { this.handleAddressItemClick(address); }; - const text = this.getFullAddressText(address); + const text = `${address.name.fi}, ${address.municipality.name.fi}`; return
  • {text}