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 1/4] 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 2/4] 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 { /> } +