From ab361596dc05f882927b603a001c2e8bf8af3914 Mon Sep 17 00:00:00 2001 From: Henri Nieminen <118905702+henrinie-nc@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:28:41 +0200 Subject: [PATCH] Add change history to area search (#467) --- .../AreaSearchApplicationAuditLog.js | 130 ++++++++++++++++++ .../components/AreaSearchApplicationPage.js | 3 +- src/areaSearch/enums.js | 2 + src/areaSearch/types.js | 1 + src/auditLog/actions.js | 13 ++ src/auditLog/reducer.js | 27 ++++ src/auditLog/saga.js | 26 ++++ src/auditLog/selectors.js | 6 + src/auditLog/spec.js | 2 + src/auditLog/types.js | 6 + 10 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 src/areaSearch/components/AreaSearchApplicationAuditLog.js diff --git a/src/areaSearch/components/AreaSearchApplicationAuditLog.js b/src/areaSearch/components/AreaSearchApplicationAuditLog.js new file mode 100644 index 000000000..8d49cddf7 --- /dev/null +++ b/src/areaSearch/components/AreaSearchApplicationAuditLog.js @@ -0,0 +1,130 @@ +//@flow +import React, {Fragment, PureComponent} 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'; +import LoaderWrapper from '$components/loader/LoaderWrapper'; +import Pagination from '$components/table/Pagination'; +import TableWrapper from '$components/table/TableWrapper'; +import Title from '$components/content/Title'; +import {fetchAuditLogByAreaSearch} from '$src/auditLog/actions'; +import {LIST_TABLE_PAGE_SIZE} from '$src/constants'; +import { + AreaSearchFieldPaths, + AreaSearchFieldTitles, +} from '$src/areaSearch/enums'; +import {getApiResponseCount, getApiResponseMaxPage, getApiResponseResults} from '$util/helpers'; +import {getUiDataLeaseKey} from '$src/uiData/helpers'; +import {getAuditLogByAreaSearch, getIsFetchingByAreaSearch} from '$src/auditLog/selectors'; +import {getIsEditMode} from '$src/leases/selectors'; + +import type {AuditLogList} from '$src/auditLog/types'; + +type Props = { + auditLogList: AuditLogList, + fetchAuditLogByAreaSearch: Function, + isEditMode: boolean, + 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, + } + componentDidMount() { + const {fetchAuditLogByAreaSearch, areaSearchId} = this.props; + + fetchAuditLogByAreaSearch({ + id: areaSearchId, + limit: LIST_TABLE_PAGE_SIZE, + }); + } + + static getDerivedStateFromProps(props: Props, state: State) { + const newState = {}; + + 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); + } + + 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, + }; + + if(page > 1) { + payload.offset = (page - 1) * LIST_TABLE_PAGE_SIZE; + } + + fetchAuditLogByAreaSearch(payload); + }); + } + + render() { + const {isEditMode, isFetching} = this.props; + const {activePage, auditLogItems, maxPage} = this.state; + + return( + + + {AreaSearchFieldTitles.AUDIT_LOG} + + + + + {isFetching && + + } + + + + + + ); + } +} + +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/AreaSearchApplicationPage.js b/src/areaSearch/components/AreaSearchApplicationPage.js index 0c377df61..7aebdce05 100644 --- a/src/areaSearch/components/AreaSearchApplicationPage.js +++ b/src/areaSearch/components/AreaSearchApplicationPage.js @@ -56,6 +56,7 @@ import ConfirmationModal from '$components/modal/ConfirmationModal'; import AreaSearchApplication from '$src/areaSearch/components/AreaSearchApplication'; import {withAreaSearchAttributes} from '$components/attributes/AreaSearchAttributes'; import AreaSearchApplicationEdit from '$src/areaSearch/components/AreaSearchApplicationEdit'; +import AreaSearchApplicationAuditLog from '$src/areaSearch/components/AreaSearchApplicationAuditLog'; import {fetchApplicantInfoCheckAttributes, fetchFormAttributes} from '$src/application/actions'; import { getFormAttributes, @@ -503,7 +504,7 @@ class AreaSearchApplicationPage extends Component { - {'Muutoshistoria'} + diff --git a/src/areaSearch/enums.js b/src/areaSearch/enums.js index 9564a33b5..32e38af79 100644 --- a/src/areaSearch/enums.js +++ b/src/areaSearch/enums.js @@ -3,6 +3,7 @@ export const AreaSearchFieldPaths = { INTENDED_USE: 'intended_use', LESSOR: 'lessor', + AUDIT_LOG: 'audit_log', }; export const AreaSearchFieldTitles = { @@ -19,4 +20,5 @@ export const AreaSearchFieldTitles = { DECLINE_REASON: 'Hylkäämisen syy', PREPARER_NOTE: 'Käsittelytietojen huomautus', STATUS_NOTES: 'Käsittelijän muistiinpanot', + AUDIT_LOG: 'Muutoshistoria', }; diff --git a/src/areaSearch/types.js b/src/areaSearch/types.js index cb030b8b0..6f044a4ab 100644 --- a/src/areaSearch/types.js +++ b/src/areaSearch/types.js @@ -28,6 +28,7 @@ export type AreaSearchState = { }; export type AreaSearch = Object; +export type AreaSearchId = number; export type FetchListAttributesAction = Action<'mvj/areaSearch/FETCH_LIST_ATTRIBUTES', void>; export type ReceiveListAttributesAction = Action<'mvj/areaSearch/RECEIVE_LIST_ATTRIBUTES', Attributes>; diff --git a/src/auditLog/actions.js b/src/auditLog/actions.js index 4bbe4d178..8e1632be6 100644 --- a/src/auditLog/actions.js +++ b/src/auditLog/actions.js @@ -3,6 +3,7 @@ import {createAction} from 'redux-actions'; import type {ContactId} from '$src/contacts/types'; import type {LeaseId} from '$src/leases/types'; +import type {AreaSearchId} from '$src/areaSearch/types'; import type { AuditLogListMap, FetchAuditLogByContactAction, @@ -11,6 +12,9 @@ import type { FetchAuditLogByLeaseAction, ReceiveAuditLogByLeaseAction, NotFoundByLeaseAction, + FetchAuditLogByAreaSearchAction, + ReceiveAuditLogByAreaSearchAction, + NotFoundByAreaSearchAction } from '$src/auditLog/types'; export const fetchAuditLogByContact = (contactId: ContactId): FetchAuditLogByContactAction => @@ -30,3 +34,12 @@ export const receiveAuditLogByLease = (payload: AuditLogListMap): ReceiveAuditLo export const notFoundByLease = (leaseId: LeaseId): NotFoundByLeaseAction => createAction('mvj/auditLog/NOT_FOUND_BY_LEASE')(leaseId); + +export const fetchAuditLogByAreaSearch = (query: Object): FetchAuditLogByAreaSearchAction => + createAction('mvj/auditLog/FETCH_BY_AREASEARCH')(query); + +export const receiveAuditLogByAreaSearch = (payload: AuditLogListMap): ReceiveAuditLogByAreaSearchAction => + createAction('mvj/auditLog/RECEIVE_BY_AREASEARCH')(payload); + +export const notFoundByAreaSearch = (areaSearchId: AreaSearchId): NotFoundByAreaSearchAction => + createAction('mvj/auditLog/NOT_FOUND_BY_AREASEARCH')(areaSearchId); diff --git a/src/auditLog/reducer.js b/src/auditLog/reducer.js index b6ee7f0f3..0b08aad06 100644 --- a/src/auditLog/reducer.js +++ b/src/auditLog/reducer.js @@ -12,6 +12,9 @@ import type { FetchAuditLogByLeaseAction, ReceiveAuditLogByLeaseAction, NotFoundByLeaseAction, + FetchAuditLogByAreaSearchAction, + NotFoundByAreaSearchAction, + ReceiveAuditLogByAreaSearchAction, } from '$src/auditLog/types'; const isFetchingByContactReducer: Reducer = handleActions({ @@ -58,9 +61,33 @@ const auditLogByLeaseReducer: Reducer = handleActions({ }), }, {}); +const isFetchingByAreaSearchReducer: Reducer = handleActions({ + ['mvj/auditLog/FETCH_BY_AREASEARCH']: (state: AuditLogIsFetchingMap, {payload}: FetchAuditLogByAreaSearchAction) => ({ + ...state, + [payload.id]: true, + }), + ['mvj/auditLog/RECEIVE_BY_AREASEARCH']: (state: AuditLogIsFetchingMap, {payload}: ReceiveAuditLogByAreaSearchAction) => ({ + ...state, + ...Object.keys(payload).reduce((obj, key) => ({...obj, [key]: false}), {}), + }), + ['mvj/auditLog/NOT_FOUND_BY_AREASEARCH']: (state: AuditLogIsFetchingMap, {payload: areaSearchId}: NotFoundByAreaSearchAction) => ({ + ...state, + [areaSearchId]: false, + }), +}, {}); + +const auditLogByAreaSearchReducer: Reducer = handleActions({ + ['mvj/auditLog/RECEIVE_BY_AREASEARCH']: (state: AuditLogListMap, {payload}: ReceiveAuditLogByAreaSearchAction) => ({ + ...state, + ...payload, + }), +}, {}); + export default combineReducers({ byContact: auditLogByContactReducer, byLease: auditLogByLeaseReducer, + byAreaSearch: auditLogByAreaSearchReducer, isFetchingByContact: isFetchingByContactReducer, isFetchingByLease: isFetchingByLeaseReducer, + isFetchingByAreaSearch: isFetchingByAreaSearchReducer, }); diff --git a/src/auditLog/saga.js b/src/auditLog/saga.js index ca4964130..ae24d2e29 100644 --- a/src/auditLog/saga.js +++ b/src/auditLog/saga.js @@ -7,6 +7,8 @@ import { notFoundByContact, receiveAuditLogByLease, notFoundByLease, + receiveAuditLogByAreaSearch, + notFoundByAreaSearch, } from './actions'; import {fetchAuditLog} from './requests'; @@ -56,11 +58,35 @@ function* fetchAuditLogByLeaseSaga({payload}): Generator { } } +function* fetchAuditLogByAreaSearchSaga({payload}): Generator { + try { + const {response: {status: statusCode}, bodyAsJson} = yield call(fetchAuditLog, { + ...payload, + type: 'areasearch', + }); + + switch (statusCode) { + case 200: + yield put(receiveAuditLogByAreaSearch({[payload.id.toString()]: bodyAsJson})); + break; + default: + console.error('Failed to fetch areasearch audit log'); + yield put(notFoundByAreaSearch(payload.id)); + break; + } + } catch (error) { + console.error('Failed to fetch areasearch audit log with error "%s"', error); + yield put(notFoundByAreaSearch(payload.id)); + yield put(receiveError(error)); + } +} + export default function*(): Generator { yield all([ fork(function*(): Generator { yield takeLatest('mvj/auditLog/FETCH_BY_CONTACT', fetchAuditLogByContactSaga); yield takeLatest('mvj/auditLog/FETCH_BY_LEASE', fetchAuditLogByLeaseSaga); + yield takeLatest('mvj/auditLog/FETCH_BY_AREASEARCH', fetchAuditLogByAreaSearchSaga); }), ]); } diff --git a/src/auditLog/selectors.js b/src/auditLog/selectors.js index 10094a4b0..b0c9a79e8 100644 --- a/src/auditLog/selectors.js +++ b/src/auditLog/selectors.js @@ -13,3 +13,9 @@ export const getIsFetchingByLease: Selector = (state: RootState export const getAuditLogByLease: Selector = (state: RootState, leaseId: string): boolean => state.auditLog.byLease[leaseId]; + +export const getIsFetchingByAreaSearch: Selector = (state: RootState, areaSearchId: string): boolean => + state.auditLog.isFetchingByAreaSearch[areaSearchId]; + +export const getAuditLogByAreaSearch: Selector = (state: RootState, areaSearchId: string): boolean => + state.auditLog.byAreaSearch[areaSearchId]; \ No newline at end of file diff --git a/src/auditLog/spec.js b/src/auditLog/spec.js index fbe161231..50c931d0d 100644 --- a/src/auditLog/spec.js +++ b/src/auditLog/spec.js @@ -16,8 +16,10 @@ import type {AuditLogState} from './types'; const defaultState: AuditLogState = { byContact: {}, byLease: {}, + byAreaSearch: {}, isFetchingByContact: {}, isFetchingByLease: {}, + isFetchingByAreaSearch: {}, }; // $FlowFixMe diff --git a/src/auditLog/types.js b/src/auditLog/types.js index a6a4bf72e..2b9071399 100644 --- a/src/auditLog/types.js +++ b/src/auditLog/types.js @@ -2,12 +2,15 @@ import type {Action} from '../types'; import type {ContactId} from '$src/contacts/types'; import type {LeaseId} from '$src/leases/types'; +import type {AreaSearchId} from '$src/areaSearch/types'; export type AuditLogState = { byContact: AuditLogListMap, byLease: AuditLogListMap, + byAreaSearch: AuditLogListMap, isFetchingByContact: AuditLogIsFetchingMap, isFetchingByLease: AuditLogIsFetchingMap, + isFetchingByAreaSearch: AuditLogIsFetchingMap, } export type AuditLogList = Object; @@ -26,3 +29,6 @@ export type NotFoundByContactAction = Action<'mvj/auditLog/NOT_FOUND_BY_CONTACT' export type FetchAuditLogByLeaseAction = Action<'mvj/auditLog/FETCH_BY_LEASE', Object>; export type ReceiveAuditLogByLeaseAction = Action<'mvj/auditLog/RECEIVE_BY_LEASE', AuditLogListMap>; export type NotFoundByLeaseAction = Action<'mvj/auditLog/NOT_FOUND_BY_LEASE', LeaseId>; +export type FetchAuditLogByAreaSearchAction = Action<'mvj/auditLog/FETCH_BY_AREASEARCH', Object>; +export type ReceiveAuditLogByAreaSearchAction = Action<'mvj/auditLog/RECEIVE_BY_AREASEARCH', AuditLogListMap>; +export type NotFoundByAreaSearchAction = Action<'mvj/auditLog/NOT_FOUND_BY_AREASEARCH', AreaSearchId>;