From 5cb9042304229523c3d3959ff113eb2fdd82a452 Mon Sep 17 00:00:00 2001 From: Jukka Ahonen Date: Wed, 31 Jul 2024 13:42:39 +0300 Subject: [PATCH] refactor areaSearch into hooks --- .../AreaSearchApplicantInfoCheck.tsx | 33 +- .../AreaSearchApplicantInfoCheckEdit.tsx | 65 ++- .../components/AreaSearchApplication.tsx | 36 +- .../AreaSearchApplicationAuditLog.tsx | 166 +++--- .../AreaSearchApplicationCreateForm.tsx | 100 ++-- .../AreaSearchApplicationCreatePage.tsx | 424 ++++++++------- .../AreaSearchApplicationCreateSpecs.tsx | 377 +++++++------ .../components/AreaSearchApplicationEdit.tsx | 5 +- .../AreaSearchApplicationListPage.tsx | 512 +++++++----------- .../components/AreaSearchApplicationPage.tsx | 313 ++++------- ...eaSearchApplicationPropertyIdentifiers.tsx | 30 +- .../components/AreaSearchExportModal.tsx | 251 +++++---- .../AreaSearchStatusNoteHistory.tsx | 48 +- .../components/EditAreaSearchPreparerForm.tsx | 183 ++++--- .../EditAreaSearchPreparerModal.tsx | 75 ++- 15 files changed, 1289 insertions(+), 1329 deletions(-) diff --git a/src/areaSearch/components/AreaSearchApplicantInfoCheck.tsx b/src/areaSearch/components/AreaSearchApplicantInfoCheck.tsx index ad74eb890..75fceceb6 100644 --- a/src/areaSearch/components/AreaSearchApplicantInfoCheck.tsx +++ b/src/areaSearch/components/AreaSearchApplicantInfoCheck.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from "react"; +import React from "react"; import { connect } from "react-redux"; import { getFieldOptions } from "util/helpers"; import type { Attributes } from "types"; @@ -12,19 +12,20 @@ type Props = OwnProps & { infoCheckAttributes: Attributes; }; -class AreaSearchApplicantInfoCheck extends PureComponent { - render(): React.ReactNode { - const { - infoCheckAttributes, - infoCheckData - } = this.props; - const infoCheckStateOptions = getFieldOptions(infoCheckAttributes, 'state'); - const infoChecks = getApplicantInfoCheckItems(infoCheckData); - return ; - } - -} +const AreaSearchApplicantInfoCheck = ({ + infoCheckAttributes, + infoCheckData, +}: Props) => { + const infoCheckStateOptions = getFieldOptions(infoCheckAttributes, "state"); + const infoChecks = getApplicantInfoCheckItems(infoCheckData); + return ( + + ); +}; -export default (connect(state => ({ - infoCheckAttributes: getApplicantInfoCheckAttributes(state) -}))(AreaSearchApplicantInfoCheck) as React.ComponentType); \ No newline at end of file +export default connect((state) => ({ + infoCheckAttributes: getApplicantInfoCheckAttributes(state), +}))(AreaSearchApplicantInfoCheck) as React.ComponentType; diff --git a/src/areaSearch/components/AreaSearchApplicantInfoCheckEdit.tsx b/src/areaSearch/components/AreaSearchApplicantInfoCheckEdit.tsx index 4936d9f41..7ea633f4b 100644 --- a/src/areaSearch/components/AreaSearchApplicantInfoCheckEdit.tsx +++ b/src/areaSearch/components/AreaSearchApplicantInfoCheckEdit.tsx @@ -1,8 +1,10 @@ -import React, { PureComponent } from "react"; +import React from "react"; import { connect } from "react-redux"; import { change } from "redux-form"; -import PlotApplicationInfoCheckCollapse from "plotApplications/components/infoCheck/PlotApplicationInfoCheckCollapse"; -import { getApplicantInfoCheckSubmissionErrors, getApplicationApplicantInfoCheckData } from "areaSearch/selectors"; +import { + getApplicantInfoCheckSubmissionErrors, + getApplicationApplicantInfoCheckData, +} from "areaSearch/selectors"; import ApplicantInfoCheckEdit from "application/components/infoCheck/ApplicantInfoCheckEdit"; import { getApplicantInfoCheckFormName } from "application/helpers"; type OwnProps = { @@ -16,30 +18,43 @@ type Props = OwnProps & { submissionErrors: Array<{ id: number; kind: Record | null | undefined; - error: (Record | null | undefined) | (Array> | null | undefined); + error: + | (Record | null | undefined) + | (Array> | null | undefined); }>; }; -class PlotApplicationApplicantInfoCheck extends PureComponent { - render(): React.ReactNode { - const { +const PlotApplicationApplicantInfoCheck = ({ + infoCheckIds, + answer, + submissionErrors, +}: Props) => { + return ( + + ); +}; + +export default connect( + (state, props) => { + const formName = getApplicantInfoCheckFormName(props.identifier); + const infoCheckIds = getApplicationApplicantInfoCheckData(state) + .filter((item) => item.entry === props.identifier) + .map((item) => item.id); + return { infoCheckIds, - answer, - submissionErrors - } = this.props; - return ; + formName, + submissionErrors: getApplicantInfoCheckSubmissionErrors( + state, + infoCheckIds + ), + }; + }, + { + change, } - -} - -export default (connect((state, props) => { - const formName = getApplicantInfoCheckFormName(props.identifier); - const infoCheckIds = getApplicationApplicantInfoCheckData(state).filter(item => item.entry === props.identifier).map(item => item.id); - return { - infoCheckIds, - formName, - submissionErrors: getApplicantInfoCheckSubmissionErrors(state, infoCheckIds) - }; -}, { - change -})(PlotApplicationApplicantInfoCheck) as React.ComponentType); \ No newline at end of file +)(PlotApplicationApplicantInfoCheck) as React.ComponentType; diff --git a/src/areaSearch/components/AreaSearchApplication.tsx b/src/areaSearch/components/AreaSearchApplication.tsx index a950796a2..d2df96070 100644 --- a/src/areaSearch/components/AreaSearchApplication.tsx +++ b/src/areaSearch/components/AreaSearchApplication.tsx @@ -1,5 +1,4 @@ -import { $Shape } from "utility-types"; -import React, { Component, Fragment } from "react"; +import React, { Fragment, useState } from "react"; import { connect } from "react-redux"; import flowRight from "lodash/flowRight"; import orderBy from "lodash/orderBy"; @@ -37,30 +36,21 @@ type Props = OwnProps & { formAttributes: Attributes; areaSearchAttributes: Attributes; }; -type State = { + +const AreaSearchApplication = ({ + areaSearch, + isFetchingFormAttributes, + formAttributes, + areaSearchAttributes +}: Props) => { // The Leaflet element doesn't initialize correctly if it's invisible in a collapsed section element, // only rendering some map tiles and calculating the viewport from given bounds incorrectly, until an // action that forces it to update its dimensions (like resizing the window) happens. We can // circumvent this by forcing it to rerender with a key whenever that section is opened; during // the opening transition, the initialization works properly. - selectedAreaSectionRefreshKey: number; -}; + const [selectedAreaSectionRefreshKey, setSelectedAreaSectionRefreshKey] = + useState(0); -class AreaSearchApplication extends Component { - state: any = { - selectedAreaSectionRefreshKey: 0 - }; - - render(): React.ReactNode { - const { - areaSearch, - isFetchingFormAttributes, - formAttributes, - areaSearchAttributes - } = this.props; - const { - selectedAreaSectionRefreshKey - } = this.state; const fieldTypes = getFieldAttributes(formAttributes, 'sections.child.children.fields.child.children.type.choices'); let form: Form | null | undefined; let answer: Record | null | undefined; @@ -121,9 +111,7 @@ class AreaSearchApplication extends Component { { if (isOpen) { - this.setState(state => ({ - selectedAreaSectionRefreshKey: state.selectedAreaSectionRefreshKey + 1 - })); + setSelectedAreaSectionRefreshKey(selectedAreaSectionRefreshKey + 1); } }} defaultOpen> @@ -234,8 +222,6 @@ class AreaSearchApplication extends Component { } ; - } - } export default (flowRight(connect(state => ({ diff --git a/src/areaSearch/components/AreaSearchApplicationAuditLog.tsx b/src/areaSearch/components/AreaSearchApplicationAuditLog.tsx index 6ac91f625..69744093b 100644 --- a/src/areaSearch/components/AreaSearchApplicationAuditLog.tsx +++ b/src/areaSearch/components/AreaSearchApplicationAuditLog.tsx @@ -1,6 +1,5 @@ -import React, { Fragment, PureComponent } from "react"; +import React, { Fragment, PureComponent, useEffect, useState } from "react"; import { connect } from "react-redux"; -import isEmpty from "lodash/isEmpty"; import AuditLogTable from "components/auditLog/AuditLogTable"; import Divider from "components/content/Divider"; import Loader from "components/loader/Loader"; @@ -11,11 +10,19 @@ import Title from "components/content/Title"; import { fetchAuditLogByAreaSearch } from "auditLog/actions"; import { LIST_TABLE_PAGE_SIZE } from "util/constants"; import { AreaSearchFieldPaths, AreaSearchFieldTitles } from "areaSearch/enums"; -import { getApiResponseCount, getApiResponseMaxPage, getApiResponseResults } from "util/helpers"; +import { + getApiResponseCount, + getApiResponseMaxPage, + getApiResponseResults, +} from "util/helpers"; import { getUiDataLeaseKey } from "uiData/helpers"; -import { getAuditLogByAreaSearch, getIsFetchingByAreaSearch } from "auditLog/selectors"; +import { + getAuditLogByAreaSearch, + getIsFetchingByAreaSearch, +} from "auditLog/selectors"; import { getIsEditMode } from "leases/selectors"; import type { AuditLogList } from "auditLog/types"; + type Props = { auditLogList: AuditLogList; fetchAuditLogByAreaSearch: (...args: Array) => any; @@ -23,101 +30,84 @@ type Props = { isFetching: boolean; areaSearchId: string; }; -type State = { - activePage: number; - auditLogItems: Array>; - auditLogList: AuditLogList; - count: number; - maxPage: number; -}; -class AreaSearchApplicationAuditLog extends PureComponent { - state = { - activePage: 1, - auditLogItems: [], - auditLogList: {}, - count: 0, - maxPage: 0 +const AreaSearchApplicationAuditLog = ({ + isEditMode, + isFetching, + fetchAuditLogByAreaSearch, + areaSearchId, + auditLogList +}: Props) => { + const [activePage, setActivePage] = useState(1); + const [auditLogItems, setAuditLogItems] = useState([]); + const [maxPage, setMaxPage] = useState(0); + + const handlePageClick = (page: number) => { + setActivePage(page); }; - componentDidMount() { - const { - fetchAuditLogByAreaSearch, - areaSearchId - } = this.props; - fetchAuditLogByAreaSearch({ + useEffect(() => { + const payload: any = { id: areaSearchId, - limit: LIST_TABLE_PAGE_SIZE - }); - } + limit: LIST_TABLE_PAGE_SIZE, + }; - static getDerivedStateFromProps(props: Props, state: State) { - const newState: any = {}; - - if (props.auditLogList !== state.auditLogList) { - newState.auditLogList = props.auditLogList; - newState.auditLogItems = getApiResponseResults(props.auditLogList); - newState.count = getApiResponseCount(props.auditLogList); - newState.maxPage = getApiResponseMaxPage(props.auditLogList, LIST_TABLE_PAGE_SIZE); + // activePage used to be page (parameter of the handlePageClick function) + // TODO: Check if it's correct to use activePage here, instead of page + if (activePage > 1) { + payload.offset = (activePage - 1) * LIST_TABLE_PAGE_SIZE; } - return !isEmpty(newState) ? newState : null; - } - - handlePageClick = (page: number) => { - const { - fetchAuditLogByAreaSearch, - areaSearchId - } = this.props; - this.setState({ - activePage: page - }, () => { - const payload: any = { - id: areaSearchId, - limit: LIST_TABLE_PAGE_SIZE - }; + fetchAuditLogByAreaSearch(payload); + }, [activePage]); - if (page > 1) { - payload.offset = (page - 1) * LIST_TABLE_PAGE_SIZE; - } + useEffect(() => { + setAuditLogItems(getApiResponseResults(auditLogList)) + setMaxPage(getApiResponseMaxPage(auditLogList, LIST_TABLE_PAGE_SIZE)) - fetchAuditLogByAreaSearch(payload); + fetchAuditLogByAreaSearch({ + id: areaSearchId, + limit: LIST_TABLE_PAGE_SIZE, }); - }; + }, []); - render() { - const { - isEditMode, - isFetching - } = this.props; - const { - activePage, - auditLogItems, - maxPage - } = this.state; - return - - {AreaSearchFieldTitles.AUDIT_LOG} - - + return ( + + + {AreaSearchFieldTitles.AUDIT_LOG} + + - - {isFetching && } + + {isFetching && ( + + + + )} - - - - ; - } - -} + + + + + ); +}; -export default connect((state, props: Props) => { - return { - auditLogList: getAuditLogByAreaSearch(state, props.areaSearchId), - isEditMode: getIsEditMode(state), - isFetching: getIsFetchingByAreaSearch(state, props.areaSearchId) - }; -}, { - fetchAuditLogByAreaSearch -})(AreaSearchApplicationAuditLog); \ No newline at end of file +export default connect( + (state, props: Props) => { + return { + auditLogList: getAuditLogByAreaSearch(state, props.areaSearchId), + isEditMode: getIsEditMode(state), + isFetching: getIsFetchingByAreaSearch(state, props.areaSearchId), + }; + }, + { + fetchAuditLogByAreaSearch, + } +)(AreaSearchApplicationAuditLog); diff --git a/src/areaSearch/components/AreaSearchApplicationCreateForm.tsx b/src/areaSearch/components/AreaSearchApplicationCreateForm.tsx index 9b7dc505b..b178da1a6 100644 --- a/src/areaSearch/components/AreaSearchApplicationCreateForm.tsx +++ b/src/areaSearch/components/AreaSearchApplicationCreateForm.tsx @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { Component, useEffect } from "react"; import { connect } from "react-redux"; import { flowRight } from "lodash/util"; import { getFormValues, reduxForm } from "redux-form"; @@ -19,63 +19,67 @@ type Props = OwnProps & { valid: boolean; }; -class AreaSearchApplicationCreateForm extends Component { - componentDidMount() { - const { - initialize, - formData, - formValues, - fieldTypeMapping, - receiveFormValidFlags, - valid - } = this.props; +const AreaSearchApplicationCreateForm = ({ + initialize, + formData, + formValues, + fieldTypeMapping, + receiveFormValidFlags, + valid, +}: Props) => { + useEffect(() => { receiveFormValidFlags({ - [FormNames.AREA_SEARCH_CREATE_FORM]: valid + [FormNames.AREA_SEARCH_CREATE_FORM]: valid, }); if (formValues.form === null) { initialize({ - form: getInitialApplicationForm(fieldTypeMapping, formData) + form: getInitialApplicationForm(fieldTypeMapping, formData), }); } - } + }, []); - componentDidUpdate(prevProps) { - const { - receiveFormValidFlags - } = this.props; + useEffect(() => { + receiveFormValidFlags({ + [FormNames.AREA_SEARCH_CREATE_FORM]: valid, + }); + }, [valid]); - if (prevProps.valid !== this.props.valid) { - receiveFormValidFlags({ - [FormNames.AREA_SEARCH_CREATE_FORM]: this.props.valid - }); - } + if (!formValues?.form) { + return null; } - render(): React.ReactNode { - const { - formData, - formValues - } = this.props; + return ( +
+ {formData.sections.map((section) => ( + + ))} +
+ ); +}; - if (!formValues?.form) { - return null; +export default flowRight( + connect( + (state) => ({ + fieldTypeMapping: getFieldTypeMapping(state), + formValues: getFormValues(FormNames.AREA_SEARCH_CREATE_FORM)(state), + }), + { + receiveFormValidFlags, } - - return
- {formData.sections.map(section => )} -
; - } - -} - -export default (flowRight(connect(state => ({ - fieldTypeMapping: getFieldTypeMapping(state), - formValues: getFormValues(FormNames.AREA_SEARCH_CREATE_FORM)(state) -}), { - receiveFormValidFlags -}), reduxForm({ - form: FormNames.AREA_SEARCH_CREATE_FORM, - destroyOnUnmount: false, - validate: validateApplicationForm('form') -}))(AreaSearchApplicationCreateForm) as React.ComponentType); \ No newline at end of file + ), + reduxForm({ + form: FormNames.AREA_SEARCH_CREATE_FORM, + destroyOnUnmount: false, + validate: validateApplicationForm("form"), + }) +)(AreaSearchApplicationCreateForm) as React.ComponentType; diff --git a/src/areaSearch/components/AreaSearchApplicationCreatePage.tsx b/src/areaSearch/components/AreaSearchApplicationCreatePage.tsx index 647fedfe4..deb11d607 100644 --- a/src/areaSearch/components/AreaSearchApplicationCreatePage.tsx +++ b/src/areaSearch/components/AreaSearchApplicationCreatePage.tsx @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { Component, useEffect, useState } from "react"; import { connect } from "react-redux"; import { flowRight } from "lodash/util"; import { destroy, getFormValues, initialize, isDirty } from "redux-form"; @@ -13,18 +13,45 @@ import TabPane from "components/tabs/TabPane"; import PageContainer from "components/content/PageContainer"; import ContentContainer from "components/content/ContentContainer"; import AreaSearchApplicationCreateSpecs from "areaSearch/components/AreaSearchApplicationCreateSpecs"; -import { createAreaSearchApplication, createAreaSearchSpecs, deleteUploadedAttachment, fetchAttributes, hideEditMode, receiveIsSaveClicked, receiveSingleAreaSearch, showEditMode, uploadAttachment } from "areaSearch/actions"; -import { getAttributes, getCurrentAreaSearch, getIsFetchingAttributes, getIsFormValidById, getIsSaveClicked, getIsPerformingFileOperation, getIsSubmittingAreaSearchSpecs, getIsSubmittingAreaSearchApplication } from "areaSearch/selectors"; +import { + createAreaSearchApplication, + createAreaSearchSpecs, + deleteUploadedAttachment, + fetchAttributes, + hideEditMode, + receiveIsSaveClicked, + receiveSingleAreaSearch, + showEditMode, + uploadAttachment, +} from "areaSearch/actions"; +import { + getAttributes, + getCurrentAreaSearch, + getIsFetchingAttributes, + getIsFormValidById, + getIsSaveClicked, + getIsPerformingFileOperation, + getIsSubmittingAreaSearchSpecs, + getIsSubmittingAreaSearchApplication, +} from "areaSearch/selectors"; import { setPageTitle } from "util/helpers"; import Loader from "components/loader/Loader"; import AreaSearchApplicationCreateForm from "areaSearch/components/AreaSearchApplicationCreateForm"; import { FormNames } from "enums"; -import { getInitialAreaSearchCreateForm, prepareAreaSearchDataForSubmission } from "areaSearch/helpers"; +import { + getInitialAreaSearchCreateForm, + prepareAreaSearchDataForSubmission, +} from "areaSearch/helpers"; import type { Attributes } from "types"; -import { getFormAttributes, getIsFetchingFormAttributes } from "application/selectors"; +import { + getFormAttributes, + getIsFetchingFormAttributes, +} from "application/selectors"; import { fetchFormAttributes } from "application/actions"; import type { UploadedFileMeta } from "application/types"; + type OwnProps = {}; + type Props = OwnProps & { history: Record; isFetchingAttributes: boolean; @@ -52,89 +79,77 @@ type Props = OwnProps & { initialize: (...args: Array) => any; destroy: (...args: Array) => any; }; -type State = { - activeTab: number; - attachments: Array; - initialized: boolean; -}; -class AreaSearchApplicationCreatePage extends Component { - state: State = { - activeTab: 0, - attachments: [], - initialized: false - }; +const AreaSearchApplicationCreatePage = ({ + fetchAttributes, + fetchFormAttributes, + receiveSingleAreaSearch, + showEditMode, + hideEditMode, + initialize, + destroy, + specsFormValues, + currentAreaSearch, + history, + createAreaSearchApplication, + receiveIsSaveClicked, + createAreaSearchSpecs, + isSpecsFormValid, + isApplicationFormValid, + uploadAttachment, + deleteUploadedAttachment, + isFetchingAttributes, + isFetchingFormAttributes, + isSaveClicked, + isPerformingFileOperation, + isSubmitting, + isSpecsFormDirty, + isApplicationFormDirty, +}: Props) => { + const [activeTab, setActiveTab] = useState(0); + const [attachments, setAttachments] = useState>([]); + const [initialized, setInitialized] = useState(false); - componentDidMount() { - const { - fetchAttributes, - fetchFormAttributes, - receiveSingleAreaSearch, - showEditMode - } = this.props; - setPageTitle('Uusi aluehakemus'); + useEffect(() => { + setPageTitle("Uusi aluehakemus"); receiveSingleAreaSearch(null); showEditMode(); fetchAttributes(); fetchFormAttributes(); - this.initializeForms(); - } - - componentWillUnmount() { - this.props.hideEditMode(); - this.destroyAllForms(); - } + initializeForms(); + return () => { + hideEditMode(); + destroyAllForms(); + }; + }, []); - initializeForms: () => void = () => { - const { - initialize - } = this.props; - initialize(FormNames.AREA_SEARCH_CREATE_SPECS, getInitialAreaSearchCreateForm()); + const initializeForms: () => void = () => { + initialize( + FormNames.AREA_SEARCH_CREATE_SPECS, + getInitialAreaSearchCreateForm() + ); initialize(FormNames.AREA_SEARCH_CREATE_FORM, { - form: null + form: null, }); - this.setState(() => ({ - initialized: true - })); + setInitialized(true); }; - destroyAllForms: () => void = () => { - const { - destroy - } = this.props; + + const destroyAllForms: () => void = () => { destroy(FormNames.AREA_SEARCH_CREATE_SPECS); destroy(FormNames.AREA_SEARCH_CREATE_FORM); }; - componentDidUpdate(prevProps: Props) { - const { - initialize, - specsFormValues - } = this.props; - - if (this.props.currentAreaSearch && !prevProps.currentAreaSearch) { - initialize(FormNames.AREA_SEARCH_CREATE_SPECS, specsFormValues); - this.setState(() => ({ - activeTab: 1 - })); - receiveIsSaveClicked(false); - } - } - handleBack: () => void = () => { - const { - history - } = this.props; + const handleBack: () => void = () => { history.push(getRouteById(Routes.AREA_SEARCH)); }; - saveChanges: () => void = () => { - const { - createAreaSearchApplication, - receiveIsSaveClicked - } = this.props; + + const saveChanges: () => void = () => { receiveIsSaveClicked(true); - const areFormsValid = this.areFormsValid(); + const formsValid = areFormsValid(); - if (areFormsValid) { + if (formsValid) { + // is this properly connected to the redux store? const data = prepareAreaSearchDataForSubmission(); if (!data) { @@ -146,158 +161,175 @@ class AreaSearchApplicationCreatePage extends Component { createAreaSearchApplication(data); } }; - submitSearchPart = () => { - const { - specsFormValues, - createAreaSearchSpecs, - receiveIsSaveClicked - } = this.props; - const { - attachments - } = this.state; + const submitSearchPart = () => { receiveIsSaveClicked(true); - const areFormsValid = this.areFormsValid(); + const formsValid = areFormsValid(); - if (areFormsValid) { + if (formsValid) { createAreaSearchSpecs({ - area_search_attachments: attachments.map(attachment => attachment.id), + area_search_attachments: attachments.map((attachment) => attachment.id), ...specsFormValues, - end_date: specsFormValues.end_date || null + end_date: specsFormValues.end_date || null, }); } }; - areFormsValid: () => boolean = () => { - const { - isSpecsFormValid, - isApplicationFormValid, - currentAreaSearch - } = this.props; + + const areFormsValid: () => boolean = () => { return isSpecsFormValid && (!currentAreaSearch || isApplicationFormValid); }; - handleFileAdded = (file: File) => { - const { - uploadAttachment, - currentAreaSearch - } = this.props; + + const handleFileAdded = (file: File) => { uploadAttachment({ fileData: file, areaSearch: currentAreaSearch?.id, - callback: result => this.setState(state => ({ - attachments: [...state.attachments, result] - })) + callback: (result) => setAttachments([...attachments, result]), }); }; - handleFileRemoved = (id: number) => { - const { - deleteUploadedAttachment, - currentAreaSearch - } = this.props; + const handleFileRemoved = (id: number) => { if (currentAreaSearch) { deleteUploadedAttachment({ id, - callback: () => this.setState(state => ({ - attachments: [...state.attachments.filter(file => file.id !== id)] - })) + callback: () => + setAttachments([...attachments.filter((file) => file.id !== id)]), }); } else { - this.setState(state => ({ - attachments: [...state.attachments.filter(file => file.id !== id)] - })); + setAttachments([...attachments.filter((file) => file.id !== id)]); } }; - render(): React.ReactNode { - const { - isFetchingAttributes, - currentAreaSearch, - isFetchingFormAttributes, - isSaveClicked, - isPerformingFileOperation, - isSubmitting, - isSpecsFormValid, - isApplicationFormValid, - isSpecsFormDirty, - isApplicationFormDirty - } = this.props; - const { - activeTab, - attachments, - initialized - } = this.state; - const areFormsValid = this.areFormsValid(); + const formsValid = areFormsValid(); - if (isFetchingAttributes || isFetchingFormAttributes) { - return + if (isFetchingAttributes || isFetchingFormAttributes) { + return ( + - ; - } - - return - - } infoComponent={

- Uusi aluehakemus -

} onBack={this.handleBack} /> - this.setState(() => ({ - activeTab: i - }))} /> -
- - - - - {initialized && } - - - - - {currentAreaSearch && } - - - - -
; +
+ ); } -} + return ( + + + + } + infoComponent={

Uusi aluehakemus

} + onBack={handleBack} + /> + setActiveTab(i)} + /> +
+ + + + + {initialized && activeTab === 0 && ( + + )} + + + + + {currentAreaSearch && activeTab === 1 && ( + + )} + + + + +
+ ); +}; -export default (flowRight(connect(state => { - return { - isFetchingAttributes: getIsFetchingAttributes(state), - currentAreaSearch: getCurrentAreaSearch(state), - attributes: getAttributes(state), - formAttributes: getFormAttributes(state), - isFetchingFormAttributes: getIsFetchingFormAttributes(state), - specsFormValues: getFormValues(FormNames.AREA_SEARCH_CREATE_SPECS)(state), - isSpecsFormDirty: isDirty(FormNames.AREA_SEARCH_CREATE_SPECS)(state), - isSpecsFormValid: getIsFormValidById(state, FormNames.AREA_SEARCH_CREATE_SPECS), - isApplicationFormDirty: isDirty(FormNames.AREA_SEARCH_CREATE_FORM)(state), - isApplicationFormValid: getIsFormValidById(state, FormNames.AREA_SEARCH_CREATE_FORM), - isSaveClicked: getIsSaveClicked(state), - isPerformingFileOperation: getIsPerformingFileOperation(state), - isSubmitting: getIsSubmittingAreaSearchSpecs(state) || getIsSubmittingAreaSearchApplication(state) - }; -}, { - fetchAttributes, - fetchFormAttributes, - receiveSingleAreaSearch, - showEditMode, - hideEditMode, - createAreaSearchSpecs, - createAreaSearchApplication, - receiveIsSaveClicked, - uploadAttachment, - deleteUploadedAttachment, - initialize, - destroy -}))(AreaSearchApplicationCreatePage) as React.ComponentType); \ No newline at end of file +export default flowRight( + connect( + (state) => { + return { + isFetchingAttributes: getIsFetchingAttributes(state), + currentAreaSearch: getCurrentAreaSearch(state), + attributes: getAttributes(state), + formAttributes: getFormAttributes(state), + isFetchingFormAttributes: getIsFetchingFormAttributes(state), + specsFormValues: getFormValues(FormNames.AREA_SEARCH_CREATE_SPECS)( + state + ), + isSpecsFormDirty: isDirty(FormNames.AREA_SEARCH_CREATE_SPECS)(state), + isSpecsFormValid: getIsFormValidById( + state, + FormNames.AREA_SEARCH_CREATE_SPECS + ), + isApplicationFormDirty: isDirty(FormNames.AREA_SEARCH_CREATE_FORM)( + state + ), + isApplicationFormValid: getIsFormValidById( + state, + FormNames.AREA_SEARCH_CREATE_FORM + ), + isSaveClicked: getIsSaveClicked(state), + isPerformingFileOperation: getIsPerformingFileOperation(state), + isSubmitting: + getIsSubmittingAreaSearchSpecs(state) || + getIsSubmittingAreaSearchApplication(state), + }; + }, + { + fetchAttributes, + fetchFormAttributes, + receiveSingleAreaSearch, + showEditMode, + hideEditMode, + createAreaSearchSpecs, + createAreaSearchApplication, + receiveIsSaveClicked, + uploadAttachment, + deleteUploadedAttachment, + initialize, + destroy, + } + ) +)(AreaSearchApplicationCreatePage) as React.ComponentType; diff --git a/src/areaSearch/components/AreaSearchApplicationCreateSpecs.tsx b/src/areaSearch/components/AreaSearchApplicationCreateSpecs.tsx index 2a6d6bb97..be2328db1 100644 --- a/src/areaSearch/components/AreaSearchApplicationCreateSpecs.tsx +++ b/src/areaSearch/components/AreaSearchApplicationCreateSpecs.tsx @@ -1,18 +1,32 @@ -import React, { Component } from "react"; -import { change, getFormMeta, getFormSyncErrors, getFormValues, reduxForm } from "redux-form"; +import React, { useEffect } from "react"; +import { + change, + getFormMeta, + getFormSyncErrors, + getFormValues, + reduxForm, +} from "redux-form"; import { connect } from "react-redux"; import { Column, Row } from "react-foundation"; import get from "lodash/get"; import { flowRight } from "lodash/util"; import Title from "components/content/Title"; import { FieldTypes, FormNames } from "enums"; -import { getAttributes, getIsPerformingFileOperation, getIsSaveClicked, getIsSubmittingAreaSearchSpecs } from "areaSearch/selectors"; +import { + getAttributes, + getIsPerformingFileOperation, + getIsSaveClicked, + getIsSubmittingAreaSearchSpecs, +} from "areaSearch/selectors"; import type { Attributes } from "types"; import Authorization from "components/authorization/Authorization"; import FormField from "components/form/FormField"; import AreaSearchMap from "areaSearch/components/map/AreaSearchMap"; import FormFieldLabel from "components/form/FormFieldLabel"; -import { createAreaSearchSpecs, receiveFormValidFlags } from "areaSearch/actions"; +import { + createAreaSearchSpecs, + receiveFormValidFlags, +} from "areaSearch/actions"; import AddFileButton from "components/form/AddFileButton"; import RemoveButton from "components/form/RemoveButton"; import type { UploadedFileMeta } from "application/types"; @@ -38,178 +52,223 @@ type Props = OwnProps & { geometryError: any; }; -class AreaSearchApplicationCreateSpecs extends Component { - componentDidMount() { - const { - receiveFormValidFlags, - valid - } = this.props; +const AreaSearchApplicationCreateSpecs = ({ + onFileAdded, + onFileRemoved, + attachments, + change, + touch, + attributes, + // createAreaSearchSpecs, + isPerformingFileOperation, + isSaveClicked, + isSubmittingAreaSearchSpecs, + // formValues, + receiveFormValidFlags, + valid, + formMeta, + geometryError, +}: Props) => { + useEffect(() => { receiveFormValidFlags({ - [FormNames.AREA_SEARCH_CREATE_SPECS]: valid + [FormNames.AREA_SEARCH_CREATE_SPECS]: valid, }); - } + }, []); - componentDidUpdate(prevProps) { - const { - receiveFormValidFlags, - valid - } = this.props; - - if (prevProps.valid !== valid) { - receiveFormValidFlags({ - [FormNames.AREA_SEARCH_CREATE_SPECS]: valid - }); - } - } + useEffect(() => { + receiveFormValidFlags({ + [FormNames.AREA_SEARCH_CREATE_SPECS]: valid, + }); + }, [valid]); - handleFileAdded = (e: any) => { - const { - onFileAdded - } = this.props; + const handleFileAdded = (e: any) => { onFileAdded(e.target.files[0]); }; - handleFileRemoved = (index: number) => { - const { - onFileRemoved - } = this.props; + + const handleFileRemoved = (index: number) => { onFileRemoved(index); }; - render(): React.ReactNode { - const { - attributes, - change, - attachments, - isPerformingFileOperation, - isSaveClicked, - isSubmittingAreaSearchSpecs, - formMeta, - touch, - geometryError - } = this.props; - - if (!attributes) { - return null; - } - - const geometryHasError = geometryError && (isSaveClicked || formMeta.geometry?.touched); - const today = startOfToday(); - return
- - Aluehaku - + if (!attributes) { + return null; + } - - - - - - - - - - - - - - - - - - - - - - - - - + const geometryHasError = + geometryError && (isSaveClicked || formMeta.geometry?.touched); + const today = startOfToday(); + return ( +
+ Aluehaku + + + + + + + + + + + + + + + + + + + - - Haettava alue - - { - change('geometry', features); - touch('geometry'); - }} hasError={geometryHasError} /> - + + + + + + + Haettava alue + { + change("geometry", features); + touch("geometry"); + }} + hasError={geometryHasError} + /> + + + + + + + - - - - - - - - - - - - Liitteet - {attachments.length === 0 &&
Ei lisättyjä liitteitä.
} - {attachments.map(attachment => +
+
+ + + + + Liitteet + + {attachments.length === 0 &&
Ei lisättyjä liitteitä.
} + {attachments.map((attachment) => ( + {attachment.name} - this.handleFileRemoved(attachment.id)} /> + handleFileRemoved(attachment.id)} + /> - )} - -
-
-
-
; - } - -} - -export default (flowRight(connect(state => { - return { - attributes: getAttributes(state), - formValues: getFormValues(FormNames.AREA_SEARCH_CREATE_SPECS)(state), - isPerformingFileOperation: getIsPerformingFileOperation(state), - isSaveClicked: getIsSaveClicked(state), - isSubmittingAreaSearchSpecs: getIsSubmittingAreaSearchSpecs(state), - formMeta: getFormMeta(FormNames.AREA_SEARCH_CREATE_SPECS)(state), - geometryError: getFormSyncErrors(FormNames.AREA_SEARCH_CREATE_SPECS)(state)?.geometry - }; -}, { - createAreaSearchSpecs, - receiveFormValidFlags -}), reduxForm({ - form: FormNames.AREA_SEARCH_CREATE_SPECS, - destroyOnUnmount: false, - change, - validate: values => { - const errors: any = {}; +
+ ))} + + + + +
+ ); +}; - if (values.start_date && values.end_date && values.start_date > values.end_date) { - errors.start_date = 'Alkupäivämäärän on oltava ennen loppupäivämäärää'; - errors.end_date = 'Loppupäivämäärän on oltava ennen alkupäivämäärää'; +export default flowRight( + connect( + (state) => { + return { + attributes: getAttributes(state), + formValues: getFormValues(FormNames.AREA_SEARCH_CREATE_SPECS)(state), + isPerformingFileOperation: getIsPerformingFileOperation(state), + isSaveClicked: getIsSaveClicked(state), + isSubmittingAreaSearchSpecs: getIsSubmittingAreaSearchSpecs(state), + formMeta: getFormMeta(FormNames.AREA_SEARCH_CREATE_SPECS)(state), + geometryError: getFormSyncErrors(FormNames.AREA_SEARCH_CREATE_SPECS)( + state + )?.geometry, + }; + }, + { + createAreaSearchSpecs, + receiveFormValidFlags, } + ), + reduxForm({ + form: FormNames.AREA_SEARCH_CREATE_SPECS, + destroyOnUnmount: false, + change, + validate: (values) => { + const errors: any = {}; - return errors; - } -}))(AreaSearchApplicationCreateSpecs) as React.ComponentType); \ No newline at end of file + if ( + values.start_date && + values.end_date && + values.start_date > values.end_date + ) { + errors.start_date = "Alkupäivämäärän on oltava ennen loppupäivämäärää"; + errors.end_date = "Loppupäivämäärän on oltava ennen alkupäivämäärää"; + } + + return errors; + }, + }) +)(AreaSearchApplicationCreateSpecs) as React.ComponentType; diff --git a/src/areaSearch/components/AreaSearchApplicationEdit.tsx b/src/areaSearch/components/AreaSearchApplicationEdit.tsx index efdff1a7f..7d34e343a 100644 --- a/src/areaSearch/components/AreaSearchApplicationEdit.tsx +++ b/src/areaSearch/components/AreaSearchApplicationEdit.tsx @@ -1,6 +1,4 @@ -import { $Shape } from "utility-types"; import React, { - Component, Fragment, useCallback, useEffect, @@ -36,7 +34,6 @@ import Loader from "components/loader/Loader"; import FormTextTitle from "components/form/FormTextTitle"; import FormText from "components/form/FormText"; import { AreaSearchFieldTitles } from "areaSearch/enums"; -import { getUserFullName } from "users/helpers"; import SubTitle from "components/content/SubTitle"; import FileDownloadLink from "components/file/FileDownloadLink"; import { getAreaFromGeoJSON } from "util/map"; @@ -64,7 +61,7 @@ import type { } from "areaSearch/types"; import AddFileButton from "components/form/AddFileButton"; import { uploadAttachment, setAreaSearchAttachments } from "areaSearch/actions"; -import RemoveButton from "components/form/RemoveButton"; + type Props = { areaSearch: AreaSearch | null; isFetchingFormAttributes: boolean; diff --git a/src/areaSearch/components/AreaSearchApplicationListPage.tsx b/src/areaSearch/components/AreaSearchApplicationListPage.tsx index 62bbe0e59..921ff7793 100644 --- a/src/areaSearch/components/AreaSearchApplicationListPage.tsx +++ b/src/areaSearch/components/AreaSearchApplicationListPage.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, PureComponent } from "react"; +import React, { Fragment, PureComponent, useEffect, useState } from "react"; import PropTypes from "prop-types"; import flowRight from "lodash/flowRight"; import isArray from "lodash/isArray"; @@ -100,35 +100,45 @@ type State = { userActiveServiceUnit?: any; }; -class AreaSearchApplicationListPage extends PureComponent { - _isMounted: boolean; - _hasFetchedAreaSearches: boolean; - state: State = { - properties: [], - sortKey: DEFAULT_SORT_KEY, - sortOrder: DEFAULT_SORT_ORDER, - activePage: 1, - count: 0, - isSearchInitialized: false, - maxPage: 0, - selectedStates: DEFAULT_AREA_SEARCH_STATES, - visualizationType: VisualizationTypes.TABLE, - isEditModalOpen: false, - isExportModalOpen: false, - editModalTargetAreaSearch: null, - userActiveServiceUnit: undefined - }; - static contextTypes = { - router: PropTypes.object - }; - - componentDidMount() { - const { - receiveTopNavigationSettings, - location: { - search - } - } = this.props; +const AreaSearchApplicationListPage = ({ + history, + location: { + search + }, + usersPermissions, + receiveTopNavigationSettings, + areaSearchListAttributes, + areaSearchListMethods, + isFetchingAreaSearchListAttributes, + isFetching, + initializeForm, + isFetchingByBBox, + fetchAreaSearchList, + fetchAreaSearchListByBBox, + areaSearches, + areaSearchesByBBox, + editAreaSearch, + isEditingAreaSearch, + lastEditError, + change, + selectedSearches, + userActiveServiceUnit +}: Props) => { + const [properties, setProperties] = useState([]) + const [sortKey, setSortKey] = useState(DEFAULT_SORT_KEY) + const [sortOrder, setSortOrder] = useState(DEFAULT_SORT_ORDER) + const [activePage, setActivePage] = useState(1) + const [count, setCount] = useState(0) + const [isSearchInitialized, setIsSearchInitialized] = useState(false) + const [maxPage, setMaxPage] = useState(0) + const [selectedStates, setSelectedStates] = useState(DEFAULT_AREA_SEARCH_STATES) + const [visualizationType, setVisualizationType] = useState(VisualizationTypes.TABLE) + const [isEditModalOpen, setIsEditModalOpen] = useState(false) + const [isExportModalOpen, setIsExportModalOpen] = useState(false) + const [editModalTargetAreaSearch, setEditModalTargetAreaSearch] = useState(null) + const [hasFetchedAreaSearches, setHasFetchedAreaSearches] = useState(false) + + useEffect(() => { const searchQuery = getUrlParams(search); setPageTitle('Aluehaun hakemukset'); receiveTopNavigationSettings({ @@ -138,64 +148,50 @@ class AreaSearchApplicationListPage extends PureComponent { }); if (searchQuery.visualization === VisualizationTypes.MAP) { - this.setState({ - visualizationType: VisualizationTypes.MAP - }); - this.searchByBBox(); + setVisualizationType(VisualizationTypes.MAP); + searchByBBox(); } else { - this.search(); + searchFunc(); } - this.setSearchFormValues(); - window.addEventListener('popstate', this.handlePopState); - this._isMounted = true; - } - - handleVisualizationTypeChange = (value: string) => { - this.setState({ - visualizationType: value - }, () => { - const { - history, - location: { - search - } - } = this.props; - const searchQuery = getUrlParams(search); - - if (value === VisualizationTypes.MAP) { - searchQuery.visualization = VisualizationTypes.MAP; - } else { - delete searchQuery.visualization; - delete searchQuery.in_bbox; - delete searchQuery.zoom; - } + setSearchFormValues(); + window.addEventListener('popstate', handlePopState); - return history.push({ - pathname: getRouteById(Routes.AREA_SEARCH), - search: getSearchQuery(searchQuery) - }); - }); + return () => { + window.removeEventListener('popstate', handlePopState); + } + }, []) + + const handleVisualizationTypeChange = (value: string) => { + setVisualizationType(value); + // TODO: Replace this with the hook equivalent + // () => { + // const searchQuery = getUrlParams(search); + + // if (value === VisualizationTypes.MAP) { + // searchQuery.visualization = VisualizationTypes.MAP; + // } else { + // delete searchQuery.visualization; + // delete searchQuery.in_bbox; + // delete searchQuery.zoom; + // } + + // return history.push({ + // pathname: getRouteById(Routes.AREA_SEARCH), + // search: getSearchQuery(searchQuery) + // }); + // }; }; - handleAreaSearchStatesChange = (values: Array) => { - const { - location: { - search - } - } = this.props; + + const handleAreaSearchStatesChange = (values: Array) => { const searchQuery = getUrlParams(search); delete searchQuery.page; searchQuery.state = values; - this.setState({ - selectedStates: values - }); - this.handleSearchChange(searchQuery, true); + setSelectedStates(values); + handleSearchChange(searchQuery, true); }; - getColumns = () => { - const { - areaSearchListAttributes, - selectedSearches - } = this.props; + + const getColumns = () => { const columns = []; const intendedUseOptions = getFieldOptions(areaSearchListAttributes, 'intended_use'); const stateOptions = getFieldOptions(areaSearchListAttributes, 'state'); @@ -215,7 +211,7 @@ class AreaSearchApplicationListPage extends PureComponent { value: true, label: '' }] - }} onBlur={(_, value) => this.updateAllSearchesSelected({ ...selectedSearches, + }} onBlur={(_, value) => updateAllSearchesSelected({ ...selectedSearches, [item.id]: value })} /> @@ -266,44 +262,38 @@ class AreaSearchApplicationListPage extends PureComponent { key: 'lessor', text: 'Vuokranantaja', renderer: (val, row) => e.stopPropagation()}> -