From e21cab60aef31da3236e0c269e51b954a3ee2094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilia=20M=C3=A4kel=C3=A4?= Date: Thu, 24 Aug 2023 14:53:50 +0300 Subject: [PATCH 01/13] Fix incorrect state change after editing a plot search --- src/plotSearch/reducer.js | 2 +- src/plotSearch/spec.js | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plotSearch/reducer.js b/src/plotSearch/reducer.js index 1ea6a5ab0..49fab758b 100644 --- a/src/plotSearch/reducer.js +++ b/src/plotSearch/reducer.js @@ -88,7 +88,7 @@ const planUnitReducer: Reducer = handleActions({ { return merge(state, planUnit); }, - ['mvj/plotSearch/NULL_PLAN_UNITS']: () => null, + ['mvj/plotSearch/NULL_PLAN_UNITS']: () => ({}), }, {}); const customDetailedPlanAttributesReducer: Reducer = handleActions({ diff --git a/src/plotSearch/spec.js b/src/plotSearch/spec.js index 7a2492104..e08587ce2 100644 --- a/src/plotSearch/spec.js +++ b/src/plotSearch/spec.js @@ -429,9 +429,15 @@ describe('PlotSearch', () => { }); it('should null PlanUnits', () => { - const newState = {...baseState, planUnit: null}; + const newState = {...baseState, planUnit: {}}; - const state = plotSearchReducer({}, nullPlanUnits()); + const state = plotSearchReducer({ + planUnit: { + '12345': { + id: 12345, + }, + }, + }, nullPlanUnits()); expect(state).to.deep.equal(newState); }); From 1d833fbf669cccfb499f9e35fd3ca428562fd3b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilia=20M=C3=A4kel=C3=A4?= Date: Thu, 24 Aug 2023 16:21:20 +0300 Subject: [PATCH 02/13] Fix target column label in plot search application list --- src/plotApplications/components/PlotApplicationsListPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plotApplications/components/PlotApplicationsListPage.js b/src/plotApplications/components/PlotApplicationsListPage.js index c147e6224..e1e038f75 100644 --- a/src/plotApplications/components/PlotApplicationsListPage.js +++ b/src/plotApplications/components/PlotApplicationsListPage.js @@ -299,7 +299,7 @@ class PlotApplicationsListPage extends PureComponent { columns.push({ key: 'target_identifier', - text: 'Kohteen hakemustunnus', + text: 'Kohteen tunnus', sortable: false, renderer: ({identifier, application}) => identifier ? Date: Thu, 24 Aug 2023 16:30:14 +0300 Subject: [PATCH 03/13] Don't show hidden fields in application view modes --- .../components/ApplicationAnswersField.js | 13 +++++---- .../application/ApplicationPreviewSection.js | 27 +++---------------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/application/components/ApplicationAnswersField.js b/src/application/components/ApplicationAnswersField.js index ee9d16c04..cfad26150 100644 --- a/src/application/components/ApplicationAnswersField.js +++ b/src/application/components/ApplicationAnswersField.js @@ -39,6 +39,11 @@ const ApplicationAnswersField = ({ {section.fields.filter((field) => field.enabled).map((field) => { const fieldAnswer = answer.fields[field.identifier]; + const fieldType = fieldTypes?.find((fieldType) => fieldType.value === field.type)?.display_name; + + if (fieldType === 'hidden') { + return null; + } const getChoiceName = (id) => { if (!id) { @@ -57,7 +62,7 @@ const ApplicationAnswersField = ({ let displayValue = fieldAnswer?.value; if (displayValue !== undefined && displayValue !== null) { - switch (fieldTypes?.find((fieldType) => fieldType.value === field.type)?.display_name) { + switch (fieldType) { case 'radiobutton': case 'radiobuttoninline': displayValue = getChoiceName(displayValue); @@ -85,12 +90,6 @@ const ApplicationAnswersField = ({ )} : null; break; case 'hidden': - if(!field.choices) { - displayValue = field.default_value; - break; - } - - displayValue = field.choices.find(choice => choice.value === field.default_value)?.text; break; } } diff --git a/src/plotSearch/components/plotSearchSections/application/ApplicationPreviewSection.js b/src/plotSearch/components/plotSearchSections/application/ApplicationPreviewSection.js index e2e4e4990..7cbe40c4b 100644 --- a/src/plotSearch/components/plotSearchSections/application/ApplicationPreviewSection.js +++ b/src/plotSearch/components/plotSearchSections/application/ApplicationPreviewSection.js @@ -83,6 +83,10 @@ class ApplicationPreviewSection extends PureComponent { const typeMapping = get(attributes, 'sections.child.children.fields.child.children.type.choices'); const matchingType = typeMapping.find((type) => type.value === field.type)?.display_name; + if (matchingType === 'hidden') { + return null; + } + switch (matchingType) { case 'checkbox': fieldSpecificComponents = { }; break; case 'hidden': - fieldSpecificComponents = { - if(field.default_value == null) { - return '-'; - } - - if(!field.choices) { - return field.default_value; - } - - return field.choices.find(choice => choice.value === field.default_value).text; - - }} - disabled - />; break; default: console.error(`Form field type ${matchingType} (${field?.type}) is not implemented`); From 6b74394382822a6b32ff43dfdf37fdb4a379f812 Mon Sep 17 00:00:00 2001 From: Pasi Vuohijoki Date: Thu, 31 Aug 2023 12:26:20 +0300 Subject: [PATCH 04/13] Changed wrongly typed 'small' -parameter in Column element --- .../basicInfo/PlotSearchReservationRecipients.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plotSearch/components/plotSearchSections/basicInfo/PlotSearchReservationRecipients.js b/src/plotSearch/components/plotSearchSections/basicInfo/PlotSearchReservationRecipients.js index c4abb181d..2833650a7 100644 --- a/src/plotSearch/components/plotSearchSections/basicInfo/PlotSearchReservationRecipients.js +++ b/src/plotSearch/components/plotSearchSections/basicInfo/PlotSearchReservationRecipients.js @@ -22,7 +22,7 @@ const PlotSearchReservationRecipients = ({reservationRecipients}: Props): React$ {PlotSearchFieldTitles.RESERVATION_RECIPIENT_SHARE_OF_RENTAL} - {reservationRecipients.length === 0 && + {reservationRecipients.length === 0 && Ei ehdotettuja varauksensaajia. From 0861c84e72f0dfc166da1a93a037d79929438e04 Mon Sep 17 00:00:00 2001 From: Jukka Ahonen Date: Fri, 8 Sep 2023 13:49:11 +0300 Subject: [PATCH 05/13] address search input up to date --- .../address-search/AddressSearchInput.js | 340 +++++------------- 1 file changed, 89 insertions(+), 251 deletions(-) diff --git a/src/components/address-search/AddressSearchInput.js b/src/components/address-search/AddressSearchInput.js index 08c5157dc..0810d7a27 100644 --- a/src/components/address-search/AddressSearchInput.js +++ b/src/components/address-search/AddressSearchInput.js @@ -9,10 +9,11 @@ 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 = 4 type Street = { id: number, @@ -25,18 +26,36 @@ type Street = { } type Address = { + object_type: string, + name: { + fi: string, + sv: string, + en: string + }, 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,12 +66,11 @@ type Props = { type State = { addresses: Array
, filteredAddresses: Array
, - focusedType: 'street' | 'address' | null, focusedValue: ?Object, hasFocus: boolean, isLoading: boolean, menuOpen: boolean, - selectedStreet: ?Street, + selectedAddress: ?Address, streets: Array, value: ?string, } @@ -64,12 +82,11 @@ class AddressSearchInput extends Component { state = { addresses: [], filteredAddresses: [], - focusedType: null, focusedValue: null, hasFocus: false, isLoading: false, menuOpen: false, - selectedStreet: null, + selectedAddress: null, streets: [], value: '', } @@ -92,13 +109,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 +141,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 +161,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,19 +183,13 @@ class AddressSearchInput extends Component { break; case KeyCodes.ENTER: if(focusedValue) { - switch (focusedType) { - case 'address': - this.handleAddressItemClick(focusedValue); - break; - case 'street': - this.handleStreetItemClick(focusedValue); - break; - } + this.handleAddressItemClick(focusedValue); + } else { - const {filteredAddresses, selectedStreet, value} = this.state; + const {addresses, selectedAddress, value} = this.state; - if(selectedStreet) { - const address = filteredAddresses.find((address) => this.getAddressText(address).toLowerCase() === (value ? value.toLowerCase() : '')); + if(selectedAddress) { + const address = addresses.find((address) => this.getAddressText(address).toLowerCase() === (value ? value.toLowerCase() : '')); if(address) { this.handleAddressItemClick(address); @@ -204,16 +201,7 @@ class AddressSearchInput extends Component { 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,60 +228,32 @@ 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) => { + searchByKeyword = debounce((input: string) => { const fetchByKeyword = (language: 'fi' | 'sv') => { const url = `${SERVICE_MAP_URL}/search/?${stringifyQuery({ page_size: 4, @@ -308,14 +268,14 @@ class AddressSearchInput extends Component { const fetchResults = async() => { const fiResponse = await fetchByKeyword('fi'); - const svResponse = await fetchByKeyword('sv'); + // const svResponse = await fetchByKeyword('sv'); const fiResults = await fiResponse.json(); - const svResults = await svResponse.json(); + // const svResults = await svResponse.json(); return [ ...fiResults.results.map((street) => ({...street, language: 'fi'})), - ...svResults.results.map((street) => ({...street, language: 'sv'})), + // ...svResults.results.map((street) => ({...street, language: 'sv'})), ]; }; @@ -324,12 +284,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,7 +298,7 @@ class AddressSearchInput extends Component { this.setState({isLoading: false}); console.error(`Failed to fetch by keyword with error ${error}`); }); - } + }, 750) sortStreets = (a: Street, b: Street) => { const aStreet = a.name[a.language] ? a.name[a.language].toLowerCase() : ''; @@ -349,68 +309,18 @@ class AddressSearchInput extends Component { 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); - 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); + } + + if (!newValue || newValue?.length < MINIMUM_SEARCH_STRING) { + this.setState({ + addresses: [] + }) } this.setState({ @@ -440,44 +350,14 @@ class AddressSearchInput extends Component { 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}); - }) - .catch((error) => { - this.setState({isLoading: false}); - console.error(`Failed to fetch addresses with error ${error}`); - }); - }; - - handleStreetItemClick = (street: Street) => { + handleAddressItemClick = (address: Address) => { const {onChange} = this.props, - newValue = `${this.getStreetText(street)} `; + newValue = address.name.fi; - this.searchAddresses(street); + this.setState({value: newValue}); + this.closeMenu(); + this.fetchAddressDetails(address); - this.setState({ - focusedType: null, - focusedValue: null, - value: newValue, - }); if(onChange) { onChange(newValue); @@ -495,33 +375,6 @@ class AddressSearchInput extends Component { 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)}`; - - this.setState({value: newValue}); - this.closeMenu(); - this.fetchAddressDetails(address); - - - if(onChange) { - onChange(newValue); - } - - if(this.input) { - this.input.focus(); - } - } - fetchAddressDetails = (address: Address) => { const {addressDetailsCallBack} = this.props; const coordinates = address.location.coordinates; @@ -551,7 +404,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 +418,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, streets, value} = this.state; + return(
@@ -575,7 +429,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} From 5b70a0924f6f449800772d1b488fc01e5a0961e9 Mon Sep 17 00:00:00 2001 From: Jukka Ahonen Date: Fri, 8 Sep 2023 14:07:42 +0300 Subject: [PATCH 06/13] address search input: remove unnecessary stuff --- .../address-search/AddressSearchInput.js | 64 ++----------------- 1 file changed, 4 insertions(+), 60 deletions(-) diff --git a/src/components/address-search/AddressSearchInput.js b/src/components/address-search/AddressSearchInput.js index 0810d7a27..7375b7a51 100644 --- a/src/components/address-search/AddressSearchInput.js +++ b/src/components/address-search/AddressSearchInput.js @@ -15,16 +15,6 @@ const SERVICE_MAP_URL = 'https://api.hel.fi/servicemap/v2'; type Language = 'fi' | 'sv'; const MINIMUM_SEARCH_STRING = 4 -type Street = { - id: number, - language: Language, - municipality: string, - name: { - fi: string, - sv: string, - }, -} - type Address = { object_type: string, name: { @@ -71,7 +61,6 @@ type State = { isLoading: boolean, menuOpen: boolean, selectedAddress: ?Address, - streets: Array, value: ?string, } @@ -87,7 +76,6 @@ class AddressSearchInput extends Component { isLoading: false, menuOpen: false, selectedAddress: null, - streets: [], value: '', } @@ -184,17 +172,6 @@ class AddressSearchInput extends Component { case KeyCodes.ENTER: if(focusedValue) { this.handleAddressItemClick(focusedValue); - - } else { - const {addresses, selectedAddress, value} = this.state; - - if(selectedAddress) { - const address = addresses.find((address) => this.getAddressText(address).toLowerCase() === (value ? value.toLowerCase() : '')); - - if(address) { - this.handleAddressItemClick(address); - } - } } break; case KeyCodes.ESC: @@ -254,7 +231,7 @@ class AddressSearchInput extends Component { } searchByKeyword = debounce((input: string) => { - const fetchByKeyword = (language: 'fi' | 'sv') => { + const fetchByKeyword = (language: Language) => { const url = `${SERVICE_MAP_URL}/search/?${stringifyQuery({ page_size: 4, type: 'address', @@ -274,8 +251,8 @@ class AddressSearchInput extends Component { // 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'})), + // ...svResults.results.map((address) => ({...address, language: 'sv'})), ]; }; @@ -300,15 +277,6 @@ class AddressSearchInput extends Component { }); }, 750) - 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; - } - handleOnChange = (e: any) => { const {onChange} = this.props; const newValue = e.target.value; @@ -333,23 +301,6 @@ class AddressSearchInput extends Component { } } - 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); - } - handleAddressItemClick = (address: Address) => { const {onChange} = this.props, newValue = address.name.fi; @@ -368,13 +319,6 @@ class AddressSearchInput extends Component { } } - 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}` : ''; - }; - fetchAddressDetails = (address: Address) => { const {addressDetailsCallBack} = this.props; const coordinates = address.location.coordinates; @@ -419,7 +363,7 @@ class AddressSearchInput extends Component { render() { const {id, name, selected} = this.props; - const {addresses, focusedValue, isLoading, menuOpen, selectedAddress, streets, value} = this.state; + const {addresses, focusedValue, isLoading, menuOpen, selectedAddress, value} = this.state; return( From e240f3c7c2290adfcbbd132296531296b82ca94b Mon Sep 17 00:00:00 2001 From: Jukka Ahonen Date: Tue, 12 Sep 2023 10:56:09 +0300 Subject: [PATCH 07/13] address search: page size 25 --- src/components/address-search/AddressSearchInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/address-search/AddressSearchInput.js b/src/components/address-search/AddressSearchInput.js index 7375b7a51..ee3cf8990 100644 --- a/src/components/address-search/AddressSearchInput.js +++ b/src/components/address-search/AddressSearchInput.js @@ -233,7 +233,7 @@ class AddressSearchInput extends Component { 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, From 22a73b3724a8f41c47f849c9f198de837c823562 Mon Sep 17 00:00:00 2001 From: Jukka Ahonen Date: Tue, 12 Sep 2023 11:14:50 +0300 Subject: [PATCH 08/13] address search: misc fixes --- src/components/address-search/AddressSearchInput.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/address-search/AddressSearchInput.js b/src/components/address-search/AddressSearchInput.js index ee3cf8990..3b1a6c423 100644 --- a/src/components/address-search/AddressSearchInput.js +++ b/src/components/address-search/AddressSearchInput.js @@ -13,7 +13,8 @@ import {findFromOcdString} from '$util/helpers'; const SERVICE_MAP_URL = 'https://api.hel.fi/servicemap/v2'; type Language = 'fi' | 'sv'; -const MINIMUM_SEARCH_STRING = 4 +const MINIMUM_SEARCH_STRING = 3 +const DEBOUNCE_TIME_MILLISECONDS = 500 type Address = { object_type: string, @@ -245,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((address) => ({...address, language: 'fi'})), - // ...svResults.results.map((address) => ({...address, language: 'sv'})), ]; }; @@ -275,7 +273,7 @@ class AddressSearchInput extends Component { this.setState({isLoading: false}); console.error(`Failed to fetch by keyword with error ${error}`); }); - }, 750) + }, DEBOUNCE_TIME_MILLISECONDS) handleOnChange = (e: any) => { const {onChange} = this.props; From c0819f5dea666f68e5ffd8eeb0173552ff09d2eb Mon Sep 17 00:00:00 2001 From: Pasi Vuohijoki Date: Mon, 18 Sep 2023 17:11:04 +0300 Subject: [PATCH 09/13] Fixed applicant type enum to represent the enum in backend --- src/application/enums.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/application/enums.js b/src/application/enums.js index 1768e4e84..9a53198e8 100644 --- a/src/application/enums.js +++ b/src/application/enums.js @@ -32,9 +32,9 @@ export const ApplicantInfoCheckExternalTypes = { TRADE_REGISTER_INQUIRY: 'trade_register_inquiry', }; export const ApplicantTypes = { - PERSON: 'Person', - COMPANY: 'Company', - BOTH: 'Both', + PERSON: 'person', + COMPANY: 'company', + BOTH: 'both', UNKNOWN: null, // UI only states From f7ba09ea1fa66daec710bd75f6ec8487faf59d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilia=20M=C3=A4kel=C3=A4?= Date: Mon, 8 May 2023 17:02:39 +0300 Subject: [PATCH 10/13] Add selection checkboxes in area search listing --- .../AreaSearchApplicationListPage.js | 99 +++++++++++++++++-- src/areaSearch/components/search/Search.js | 1 + src/components/table/SortableTable.js | 9 ++ src/components/table/_table.scss | 4 +- src/enums.js | 1 + 5 files changed, 104 insertions(+), 10 deletions(-) diff --git a/src/areaSearch/components/AreaSearchApplicationListPage.js b/src/areaSearch/components/AreaSearchApplicationListPage.js index fa91de30f..7d3e9c2b0 100644 --- a/src/areaSearch/components/AreaSearchApplicationListPage.js +++ b/src/areaSearch/components/AreaSearchApplicationListPage.js @@ -5,12 +5,12 @@ import flowRight from 'lodash/flowRight'; import isArray from 'lodash/isArray'; import {connect} from 'react-redux'; import {Row, Column} from 'react-foundation'; -import {initialize} from 'redux-form'; +import {formValueSelector, initialize, reduxForm} from 'redux-form'; import {withRouter} from 'react-router'; import debounce from 'lodash/debounce'; import AuthorizationError from '$components/authorization/AuthorizationError'; -import {FormNames, Methods, PermissionMissingTexts} from '$src/enums'; +import {FieldTypes, FormNames, Methods, PermissionMissingTexts} from '$src/enums'; import {getUsersPermissions} from '$src/usersPermissions/selectors'; import Loader from '$components/loader/Loader'; import LoaderWrapper from '$components/loader/LoaderWrapper'; @@ -63,6 +63,7 @@ import Button from '$components/button/Button'; import EditAreaSearchPreparerModal from '$src/areaSearch/components/EditAreaSearchPreparerModal'; import Authorization from '$components/authorization/Authorization'; import AddButtonSecondary from '$components/form/AddButtonSecondary'; +import FormField from '$components/form/FormField'; import type {Attributes, Methods as MethodsType} from '$src/types'; import type {ApiResponse} from '$src/types'; @@ -93,6 +94,7 @@ type Props = { isFetchingAreaSearchListAttributes: boolean, isFetching: boolean, initialize: Function, + initializeForm: Function, isFetchingByBBox: boolean, fetchAreaSearchList: Function, fetchAreaSearchListByBBox: Function, @@ -101,6 +103,8 @@ type Props = { editAreaSearch: Function, isEditingAreaSearch: boolean, lastEditError: any, + change: Function, + selectedSearches: Object, } type State = { @@ -198,11 +202,39 @@ class AreaSearchApplicationListPage extends PureComponent { } getColumns = () => { - const {areaSearchListAttributes} = this.props; + const {areaSearchListAttributes, selectedSearches} = this.props; const columns = []; const intendedUseOptions = getFieldOptions(areaSearchListAttributes, 'intended_use'); const stateOptions = getFieldOptions(areaSearchListAttributes, 'state'); + columns.push({ + key: 'checkbox', + text: 'Tulosta', + sortable: false, + renderer: (_, item) =>
    { + e.stopPropagation(); + }}> + this.updateAllSearchesSelected({ + ...selectedSearches, + [item.id]: value, + })} + /> +
    , + }); + columns.push({ key: 'identifier', text: 'Hakemus', @@ -397,12 +429,14 @@ class AreaSearchApplicationListPage extends PureComponent { } updateTableData = () => { - const {areaSearches} = this.props; + const {areaSearches, change} = this.props; this.setState({ count: getApiResponseCount(areaSearches), maxPage: getApiResponseMaxPage(areaSearches, LIST_TABLE_PAGE_SIZE), }); + change('selectedSearches', {}); + change('allSelected', false); } handleSearchChange = (query: Object, resetActivePage?: boolean = true) => { @@ -497,7 +531,7 @@ class AreaSearchApplicationListPage extends PureComponent { } setSearchFormValues = () => { - const {location: {search}, initialize} = this.props; + const {location: {search}, initializeForm} = this.props; const searchQuery = getUrlParams(search); const page = searchQuery.page ? Number(searchQuery.page) : 1; const states = this.getSearchStates(searchQuery); @@ -516,7 +550,7 @@ class AreaSearchApplicationListPage extends PureComponent { delete initialValues.visualization; delete initialValues.zoom; - await initialize(FormNames.AREA_SEARCH_SEARCH, initialValues); + await initializeForm(FormNames.AREA_SEARCH_SEARCH, initialValues); }; this.setState({ @@ -538,7 +572,23 @@ class AreaSearchApplicationListPage extends PureComponent { history.push({ pathname: `${getRouteById(Routes.AREA_SEARCH)}/uusi`, }); - } + }; + + selectAllSearches = (event: SyntheticFocusEvent, value: boolean) => { + const {change, areaSearches} = this.props; + + change('selectedSearches', areaSearches?.results.reduce((acc, result) => { + acc[result.id] = value; + return acc; + }, {})); + }; + + updateAllSearchesSelected = (selectedSearches: Object) => { + const {change, areaSearches} = this.props; + + const isAllSelected = areaSearches?.results.every((search) => selectedSearches[search.id] === true); + change('allSelected', isAllSelected); + }; render() { const { @@ -661,6 +711,27 @@ class AreaSearchApplicationListPage extends PureComponent { sortable sortKey={sortKey} sortOrder={sortOrder} + footer={({columnCount}: {columnCount: number}) => + + + + + } /> { } } +const FORM_NAME = FormNames.AREA_SEARCH_EXPORT; +const selector = formValueSelector(FORM_NAME); + export default (flowRight( withRouter, withAreaSearchAttributes, @@ -702,14 +776,23 @@ export default (flowRight( areaSearchesByBBox: getAreaSearchListByBBox(state), isEditingAreaSearch: getIsEditingAreaSearch(state), lastEditError: getLastAreaSearchEditError(state), + selectedSearches: selector(state, 'selectedSearches'), }; }, { receiveTopNavigationSettings, - initialize, + // initialize bound to the row selection form is set by reduxForm + initializeForm: initialize, fetchAreaSearchList, fetchAreaSearchListByBBox, editAreaSearch, }, ), + reduxForm({ + form: FORM_NAME, + initialValues: { + mode: null, + selectedSearches: [], + }, + }) )(AreaSearchApplicationListPage): React$ComponentType); diff --git a/src/areaSearch/components/search/Search.js b/src/areaSearch/components/search/Search.js index b6329104d..375903dc7 100644 --- a/src/areaSearch/components/search/Search.js +++ b/src/areaSearch/components/search/Search.js @@ -156,6 +156,7 @@ class Search extends Component { render() { const { handleSubmit, + formValues, } = this.props; const { diff --git a/src/components/table/SortableTable.js b/src/components/table/SortableTable.js index b38215b69..b9d824334 100644 --- a/src/components/table/SortableTable.js +++ b/src/components/table/SortableTable.js @@ -53,6 +53,9 @@ type Props = { sortable?: boolean, style?: Object, className?: string, + footer?: ({| + columnCount: number, + |}) => React$Node, } type State = { @@ -407,6 +410,7 @@ class SortableTable extends Component { sortable, style, className, + footer, } = this.props; const { scrollHeaderColumnStyles, @@ -520,6 +524,11 @@ class SortableTable extends Component { />; })} + {footer && + {footer({ + columnCount: columns.length + (showCollapseArrowColumn ? 1 : 0), + })} + }
diff --git a/src/components/table/_table.scss b/src/components/table/_table.scss index 6787919df..d9b885d7b 100644 --- a/src/components/table/_table.scss +++ b/src/components/table/_table.scss @@ -107,7 +107,7 @@ font-size: 9.5px; } } - tbody { + tbody, tfoot { th, td { background: transparent; vertical-align: top; @@ -161,7 +161,7 @@ } } - tbody tr { + tbody tr, tfoot tr { td { line-height: rem-calc(16px); } diff --git a/src/enums.js b/src/enums.js index f8e329b15..650cca571 100644 --- a/src/enums.js +++ b/src/enums.js @@ -505,6 +505,7 @@ export const FormNames = { AREA_SEARCH_CREATE_SPECS: 'area-search-create-specs', AREA_SEARCH_CREATE_FORM: 'area-search-create-form', AREA_SEARCH_PREPARER: 'area-search-preparer', + AREA_SEARCH_EXPORT: 'area-search-export', }; /** From 661dfdf0fa84ded56a21a2a98625c666c947a50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilia=20M=C3=A4kel=C3=A4?= Date: Tue, 22 Aug 2023 12:22:09 +0300 Subject: [PATCH 11/13] Add area search export modal --- .../AreaSearchApplicationListPage.js | 24 +++- .../components/AreaSearchExportModal.js | 127 ++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 src/areaSearch/components/AreaSearchExportModal.js diff --git a/src/areaSearch/components/AreaSearchApplicationListPage.js b/src/areaSearch/components/AreaSearchApplicationListPage.js index 7d3e9c2b0..7786800da 100644 --- a/src/areaSearch/components/AreaSearchApplicationListPage.js +++ b/src/areaSearch/components/AreaSearchApplicationListPage.js @@ -68,6 +68,7 @@ import FormField from '$components/form/FormField'; import type {Attributes, Methods as MethodsType} from '$src/types'; import type {ApiResponse} from '$src/types'; import type {UsersPermissions as UsersPermissionsType} from '$src/usersPermissions/types'; +import AreaSearchExportModal from '$src/areaSearch/components/AreaSearchExportModal'; const VisualizationTypes = { MAP: 'map', @@ -118,6 +119,7 @@ type State = { selectedStates: Array, visualizationType: string, isEditModalOpen: boolean, + isExportModalOpen: boolean, editModalTargetAreaSearch: ?number, } @@ -135,6 +137,7 @@ class AreaSearchApplicationListPage extends PureComponent { selectedStates: DEFAULT_AREA_SEARCH_STATES, visualizationType: VisualizationTypes.TABLE, isEditModalOpen: false, + isExportModalOpen: false, editModalTargetAreaSearch: null, } @@ -325,6 +328,18 @@ class AreaSearchApplicationListPage extends PureComponent { })); }; + openExportModal = () => { + this.setState(() => ({ + isExportModalOpen: true, + })); + }; + + closeExportModal = () => { + this.setState(() => ({ + isExportModalOpen: false, + })); + }; + submitAreaSearchEditModal = (data: Object) => { const {editAreaSearch} = this.props; @@ -600,6 +615,7 @@ class AreaSearchApplicationListPage extends PureComponent { isFetchingByBBox, isFetchingAreaSearchListAttributes, location: {search}, + selectedSearches = {}, } = this.props; const { @@ -611,6 +627,7 @@ class AreaSearchApplicationListPage extends PureComponent { selectedStates, visualizationType, isEditModalOpen, + isExportModalOpen, editModalTargetAreaSearch, } = this.state; const searchQuery = getUrlParams(search); @@ -749,12 +766,17 @@ class AreaSearchApplicationListPage extends PureComponent { /> } +