From 0d7e05af7b9f17ea7ed996a9a9aa04391bd68e7c Mon Sep 17 00:00:00 2001 From: Juha Kujala Date: Thu, 23 Jun 2022 12:56:45 +0300 Subject: [PATCH 01/29] Add service units to redux store --- src/root/createRootReducer.js | 2 ++ src/root/createRootSaga.js | 2 ++ src/root/types.js | 2 ++ src/serviceUnits/actions.js | 19 +++++++++++++++++++ src/serviceUnits/reducer.js | 23 +++++++++++++++++++++++ src/serviceUnits/requests.js | 8 ++++++++ src/serviceUnits/saga.js | 35 +++++++++++++++++++++++++++++++++++ src/serviceUnits/selectors.js | 10 ++++++++++ src/serviceUnits/types.js | 17 +++++++++++++++++ 9 files changed, 118 insertions(+) create mode 100644 src/serviceUnits/actions.js create mode 100644 src/serviceUnits/reducer.js create mode 100644 src/serviceUnits/requests.js create mode 100644 src/serviceUnits/saga.js create mode 100644 src/serviceUnits/selectors.js create mode 100644 src/serviceUnits/types.js diff --git a/src/root/createRootReducer.js b/src/root/createRootReducer.js index 225445dec..36bd2a8bc 100644 --- a/src/root/createRootReducer.js +++ b/src/root/createRootReducer.js @@ -43,6 +43,7 @@ import previewInvoicesReducer from '$src/previewInvoices/reducer'; import rentBasisReducer from '$src/rentbasis/reducer'; import rentForPeriodReducer from '$src/rentForPeriod/reducer'; import sapInvoiceReducer from '$src/sapInvoice/reducer'; +import serviceUnitsReducer from '$src/serviceUnits/reducer'; import topNavigationReducer from '$components/topNavigation/reducer'; import tradeRegisterReducer from '$src/tradeRegister/reducer'; import uiDataReducer from '$src/uiData/reducer'; @@ -96,6 +97,7 @@ export default (history: Object): Reducer => rentBasis: rentBasisReducer, rentForPeriod: rentForPeriodReducer, router: connectRouter(history), + serviceUnits: serviceUnitsReducer, sapInvoice: sapInvoiceReducer, toastr: toastrReducer, topNavigation: topNavigationReducer, diff --git a/src/root/createRootSaga.js b/src/root/createRootSaga.js index 95adf103b..1c53bf4c3 100644 --- a/src/root/createRootSaga.js +++ b/src/root/createRootSaga.js @@ -39,6 +39,7 @@ import relatedLeaseSaga from '$src/relatedLease/saga'; import rentBasisSaga from '$src/rentbasis/saga'; import rentForPeriodSaga from '$src/rentForPeriod/saga'; import sapInvoicesSaga from '$src/sapInvoice/saga'; +import serviceUnitsSaga from '$src/serviceUnits/saga'; import tradeRegisterSaga from '$src/tradeRegister/saga'; import uiDataSaga from '$src/uiData/saga'; import userSaga from '$src/users/saga'; @@ -92,6 +93,7 @@ export default () => fork(rentBasisSaga), fork(rentForPeriodSaga), fork(sapInvoicesSaga), + fork(serviceUnitsSaga), fork(tradeRegisterSaga), fork(uiDataSaga), fork(userSaga), diff --git a/src/root/types.js b/src/root/types.js index ebe6bb074..2dd3686c2 100644 --- a/src/root/types.js +++ b/src/root/types.js @@ -33,6 +33,7 @@ import type {PlotApplicationsState} from '$src/plotApplications/types'; import type {RentBasisState} from '$src/rentbasis/types'; import type {RentForPeriodState} from '$src/rentForPeriod/types'; import type {SapInvoicesState} from '$src/sapInvoice/types'; +import type {ServiceUnitsState} from '$src/serviceUnits/types'; import type {LeaseStatisticReportState} from '$src/leaseStatisticReport/types'; import type {TradeRegisterState} from '$src/tradeRegister/types'; import type {UiDataState} from '$src/uiData/types'; @@ -82,6 +83,7 @@ export type RootState = { rentBasis: RentBasisState, rentForPeriod: RentForPeriodState, sapInvoice: SapInvoicesState, + serviceUnits: ServiceUnitsState, leaseStatisticReport: LeaseStatisticReportState, tradeRegister: TradeRegisterState, uiData: UiDataState, diff --git a/src/serviceUnits/actions.js b/src/serviceUnits/actions.js new file mode 100644 index 000000000..be3b2bec8 --- /dev/null +++ b/src/serviceUnits/actions.js @@ -0,0 +1,19 @@ +// @flow + +import {createAction} from 'redux-actions'; + +import type { + ServiceUnits, + FetchServiceUnitsAction, + ReceiveServiceUnitsAction, + ServiceUnitsNotFoundAction, +} from './types'; + +export const fetchServiceUnits = (): FetchServiceUnitsAction => + createAction('mvj/serviceUnits/FETCH_ALL')(); + +export const receiveServiceUnits = (serviceUnits: ServiceUnits): ReceiveServiceUnitsAction => + createAction('mvj/serviceUnits/RECEIVE_ALL')(serviceUnits); + +export const notFound = (): ServiceUnitsNotFoundAction => + createAction('mvj/serviceUnits/NOT_FOUND')(); diff --git a/src/serviceUnits/reducer.js b/src/serviceUnits/reducer.js new file mode 100644 index 000000000..a427a9a72 --- /dev/null +++ b/src/serviceUnits/reducer.js @@ -0,0 +1,23 @@ +// @flow +import {combineReducers} from 'redux'; +import {handleActions} from 'redux-actions'; + +import type {Reducer} from '../types'; +import type {ServiceUnits, ReceiveServiceUnitsAction} from './types'; + +const isFetchingReducer: Reducer = handleActions({ + 'mvj/serviceUnits/FETCH_ALL': () => true, + 'mvj/serviceUnits/NOT_FOUND': () => false, + 'mvj/serviceUnits/RECEIVE_ALL': () => false, +}, false); + +const serviceUnitsReducer: Reducer = handleActions({ + ['mvj/serviceUnits/RECEIVE_ALL']: (state: ServiceUnits, {payload: serviceUnits}: ReceiveServiceUnitsAction) => { + return serviceUnits; + }, +}, []); + +export default combineReducers({ + isFetching: isFetchingReducer, + serviceUnits: serviceUnitsReducer, +}); diff --git a/src/serviceUnits/requests.js b/src/serviceUnits/requests.js new file mode 100644 index 000000000..1f7004d03 --- /dev/null +++ b/src/serviceUnits/requests.js @@ -0,0 +1,8 @@ +// @flow + +import callApi from '../api/callApi'; +import createUrl from '../api/createUrl'; + +export const fetchServiceUnits = (): Generator => { + return callApi(new Request(createUrl('service_unit/'))); +}; diff --git a/src/serviceUnits/saga.js b/src/serviceUnits/saga.js new file mode 100644 index 000000000..a706937b1 --- /dev/null +++ b/src/serviceUnits/saga.js @@ -0,0 +1,35 @@ +// @flow +import {all, call, fork, put, takeLatest} from 'redux-saga/effects'; + +import {receiveServiceUnits, notFound} from './actions'; +import {fetchServiceUnits} from './requests'; + +import {receiveError} from '../api/actions'; + +function* fetchServiceUnitsSaga(): Generator { + try { + const {response: {status: statusCode}, bodyAsJson} = yield call(fetchServiceUnits); + + switch (statusCode) { + case 200: + const data = bodyAsJson.results; + yield put(receiveServiceUnits(data)); + break; + default: + yield put(notFound()); + break; + } + } catch (error) { + console.error('Failed to fetch service units with error "%s"', error); + yield put(notFound()); + yield put(receiveError(error)); + } +} + +export default function* (): Generator { + yield all([ + fork(function* (): Generator { + yield takeLatest('mvj/serviceUnits/FETCH_ALL', fetchServiceUnitsSaga); + }), + ]); +} diff --git a/src/serviceUnits/selectors.js b/src/serviceUnits/selectors.js new file mode 100644 index 000000000..5e2c56bb8 --- /dev/null +++ b/src/serviceUnits/selectors.js @@ -0,0 +1,10 @@ +// @flow +import type {Selector} from '$src/types'; +import type {RootState} from '$src/root/types'; +import type {ServiceUnits} from './types'; + +export const getIsFetching: Selector = (state: RootState): boolean => + state.serviceUnits.isFetching; + +export const getServiceUnits: Selector = (state: RootState): ServiceUnits => + state.serviceUnits.serviceUnits; diff --git a/src/serviceUnits/types.js b/src/serviceUnits/types.js new file mode 100644 index 000000000..2683cd244 --- /dev/null +++ b/src/serviceUnits/types.js @@ -0,0 +1,17 @@ +// @flow + +import type {Action} from '../types'; + +export type ServiceUnit = Object; + +export type ServiceUnits = Array; + +export type ServiceUnitState = { + isFetching: boolean, + serviceUnits: ServiceUnits, +}; + +export type FetchServiceUnitsAction = Action<'mvj/searviceUnits/FETCH_ALL', void>; +export type ReceiveServiceUnitsAction = Action<'mvj/searviceUnits/RECEIVE_ALL', ServiceUnits>; + +export type ServiceUnitsNotFoundAction = Action<'mvj/searviceUnits/NOT_FOUND', void>; From 49b32ea2b090b9c791d541b1fd33d05583e90576 Mon Sep 17 00:00:00 2001 From: Juha Kujala Date: Thu, 23 Jun 2022 12:57:40 +0300 Subject: [PATCH 02/29] Get users service units to redux store --- src/usersPermissions/actions.js | 9 +++++++++ src/usersPermissions/reducer.js | 17 +++++++++++++++++ src/usersPermissions/saga.js | 4 ++++ src/usersPermissions/selectors.js | 8 +++++++- src/usersPermissions/types.js | 7 ++++++- 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/usersPermissions/actions.js b/src/usersPermissions/actions.js index 6882621d0..417b98bef 100644 --- a/src/usersPermissions/actions.js +++ b/src/usersPermissions/actions.js @@ -5,9 +5,12 @@ import {createAction} from 'redux-actions'; import type { UserGroups, UsersPermissions, + UserServiceUnits, FetchUsersPermissionsAction, ReceiveUserGroupsAction, ReceiveUsersPermissionsAction, + ReceiveUserServiceUnitsAction, + SetUserActiveServiceUnitAction, NotFoundAction, } from './types'; @@ -20,5 +23,11 @@ export const receiveUserGroups = (groups: UserGroups): ReceiveUserGroupsAction = export const receiveUsersPermissions = (permissions: UsersPermissions): ReceiveUsersPermissionsAction => createAction('mvj/usersPermissions/RECEIVE_ALL')(permissions); +export const receiveUserServiceUnits = (serviceUnits: UserServiceUnits): ReceiveUserServiceUnitsAction => + createAction('mvj/usersPermissions/RECEIVE_SERVICE_UNITS')(serviceUnits); + +export const setUserActiveServiceUnit = (activeServiceUnit: UserServiceUnit): SetUserActiveServiceUnitAction => + createAction('mvj/usersPermissions/SET_ACTIVE_SERVICE_UNIT')(activeServiceUnit); + export const notFound = (): NotFoundAction => createAction('mvj/usersPermissions/NOT_FOUND')(); diff --git a/src/usersPermissions/reducer.js b/src/usersPermissions/reducer.js index 5373aca8f..c104f046b 100644 --- a/src/usersPermissions/reducer.js +++ b/src/usersPermissions/reducer.js @@ -6,8 +6,11 @@ import type {Reducer} from '../types'; import type { UserGroups, UsersPermissions, + UserServiceUnits, ReceiveUserGroupsAction, ReceiveUsersPermissionsAction, + ReceiveUserServiceUnitsAction, + SetUserActiveServiceUnitAction, } from './types'; const isFetchingReducer: Reducer = handleActions({ @@ -28,8 +31,22 @@ const groupsReducer: Reducer = handleActions({ }, }, []); +const serviceUnitsReducer: Reducer = handleActions({ + ['mvj/usersPermissions/RECEIVE_SERVICE_UNITS']: (state: UserServiceUnits, {payload: serviceUnits}: ReceiveUserServiceUnitsAction) => { + return serviceUnits; + }, +}, []); + +const activeServiceUnitReducer: Reducer = handleActions({ + ['mvj/usersPermissions/SET_ACTIVE_SERVICE_UNIT']: (state: UserServiceUnit, {payload: serviceUnit}: SetUserActiveServiceUnitAction) => { + return serviceUnit; + }, +}, null); + export default combineReducers({ + activeServiceUnit: activeServiceUnitReducer, isFetching: isFetchingReducer, groups: groupsReducer, permissions: usersPermissionsReducer, + serviceUnits: serviceUnitsReducer, }); diff --git a/src/usersPermissions/saga.js b/src/usersPermissions/saga.js index e37619bcc..b418b623a 100644 --- a/src/usersPermissions/saga.js +++ b/src/usersPermissions/saga.js @@ -4,6 +4,8 @@ import {all, call, fork, put, takeLatest} from 'redux-saga/effects'; import { receiveUserGroups, receiveUsersPermissions, + receiveUserServiceUnits, + setUserActiveServiceUnit, notFound, } from './actions'; @@ -20,7 +22,9 @@ function* fetchUsersPermissionsSaga(): Generator { switch (statusCode) { case 200: yield put(receiveUserGroups(bodyAsJson.groups)); + yield put(receiveUserServiceUnits(bodyAsJson.service_units)); yield put(receiveUsersPermissions(bodyAsJson.permissions)); + yield put(setUserActiveServiceUnit(bodyAsJson.service_units[0])); break; default: yield put(receiveError(bodyAsJson)); diff --git a/src/usersPermissions/selectors.js b/src/usersPermissions/selectors.js index 2d1e63963..b8b0d202f 100644 --- a/src/usersPermissions/selectors.js +++ b/src/usersPermissions/selectors.js @@ -1,7 +1,7 @@ // @flow import type {Selector} from '$src/types'; import type {RootState} from '$src/root/types'; -import type {UserGroups, UsersPermissions} from './types'; +import type {UserGroups, UsersPermissions, UserServiceUnit, UserServiceUnits} from './types'; export const getIsFetching: Selector = (state: RootState): boolean => state.usersPermissions.isFetching; @@ -11,3 +11,9 @@ export const getUserGroups: Selector = (state: RootState): Use export const getUsersPermissions: Selector = (state: RootState): UsersPermissions => state.usersPermissions.permissions; + +export const getUserServiceUnits: Selector = (state: RootState): UserServiceUnits => + state.usersPermissions.serviceUnits; + +export const getUserActiveServiceUnit: Selector = (state: RootState): UserServiceUnit => + state.usersPermissions.activeServiceUnit; diff --git a/src/usersPermissions/types.js b/src/usersPermissions/types.js index 101234bae..3ee0416f7 100644 --- a/src/usersPermissions/types.js +++ b/src/usersPermissions/types.js @@ -3,16 +3,21 @@ import type {Action} from '../types'; export type UserGroups = Array; - export type UsersPermissions = Array; +export type UserServiceUnit = Object; +export type UserServiceUnits = Array; export type UsersPermissionsState = { + activeServiceUnit: UserServiceUnit, groups: UserGroups, isFetching: boolean, permissions: UsersPermissions, + serviceUnits: UserServiceUnits, }; export type FetchUsersPermissionsAction = Action<'mvj/usersPermissions/FETCH_ALL', void>; export type ReceiveUserGroupsAction = Action<'mvj/usersPermissions/RECEIVE_GROUPS', UserGroups>; export type ReceiveUsersPermissionsAction = Action<'mvj/usersPermissions/RECEIVE_ALL', UsersPermissions>; +export type ReceiveUserServiceUnitsAction = Action<'mvj/usersPermissions/RECEIVE_SERVICE_UNITS', UserServiceUnits>; +export type SetUserActiveServiceUnitAction = Action<'mvj/usersPermissions/SET_ACTIVE_SERVICE_UNIT', UserServiceUnit>; export type NotFoundAction = Action<'mvj/usersPermissions/NOT_FOUND', void>; From 97a563319d166b46218120a679c9b34c899d0642 Mon Sep 17 00:00:00 2001 From: Juha Kujala Date: Thu, 23 Jun 2022 13:19:08 +0300 Subject: [PATCH 03/29] Add user active service unit selection to top navigation --- src/app/App.js | 19 +++- .../inputs/UserServiceUnitSelectInput.js | 105 ++++++++++++++++++ src/components/topNavigation/TopNavigation.js | 24 +++- .../topNavigation/_top-navigation.scss | 25 +++++ 4 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 src/components/inputs/UserServiceUnitSelectInput.js diff --git a/src/app/App.js b/src/app/App.js index 2e92d32ee..53be89a14 100644 --- a/src/app/App.js +++ b/src/app/App.js @@ -24,13 +24,13 @@ import {getEpochTime} from '$util/helpers'; import {getError} from '$src/api/selectors'; import {getApiToken, getApiTokenExpires, getIsFetching, getLoggedInUser} from '$src/auth/selectors'; import {getLinkUrl, getPageTitle, getShowSearch} from '$components/topNavigation/selectors'; -import {getUserGroups} from '$src/usersPermissions/selectors'; +import {getUserGroups, getUserActiveServiceUnit, getUserServiceUnits} from '$src/usersPermissions/selectors'; import {setRedirectUrlToSessionStorage} from '$util/storage'; -import type {ApiError} from '$src/api/types'; -import type {ApiToken} from '$src/auth/types'; -import type {UserGroups} from '$src/usersPermissions/types'; -import type {RootState} from '$src/root/types'; +import type {ApiError} from '../api/types'; +import type {ApiToken} from '../auth/types'; +import type {UserGroups, UserServiceUnit, UserServiceUnits} from '$src/usersPermissions/types'; +import type {RootState} from '../root/types'; const url = window.location.toString(); const IS_DEVELOPMENT_URL = url.includes('ninja') || url.includes('localhost'); @@ -53,6 +53,8 @@ type Props = { linkUrl: string, location: Object, pageTitle: string, + userActiveServiceUnit: UserServiceUnit, + userServiceUnits: UserServiceUnits, showSearch: boolean, user: Object, userGroups: UserGroups, @@ -178,6 +180,8 @@ class App extends Component { showSearch, user, userGroups, + userActiveServiceUnit, + userServiceUnits, } = this.props; const {displaySideMenu} = this.state; const appStyle = (IS_DEVELOPMENT_URL) ? 'app-dev' : 'app'; @@ -260,6 +264,7 @@ class App extends Component { transitionOut="fadeOut" closeOnToastrClick={true} /> + { toggleSideMenu={this.toggleSideMenu} userGroups={userGroups} username={get(user, 'profile.name')} + userServiceUnits={userServiceUnits} + userActiveServiceUnit={userActiveServiceUnit} />
@@ -311,6 +318,8 @@ const mapStateToProps = (state: RootState) => { showSearch: getShowSearch(state), user, userGroups: getUserGroups(state), + userServiceUnits: getUserServiceUnits(state), + userActiveServiceUnit: getUserActiveServiceUnit(state), }; }; diff --git a/src/components/inputs/UserServiceUnitSelectInput.js b/src/components/inputs/UserServiceUnitSelectInput.js new file mode 100644 index 000000000..aa003abcf --- /dev/null +++ b/src/components/inputs/UserServiceUnitSelectInput.js @@ -0,0 +1,105 @@ +// @flow +import React, {Component} from 'react'; +import flowRight from 'lodash/flowRight'; +import {connect} from 'react-redux'; +import {reduxForm} from 'redux-form'; +import Select from 'react-select'; + +import FormFieldLabel from '$components/form/FormFieldLabel'; +import DropdownIndicator from '$components/inputs/DropdownIndicator'; +import LoadingIndicator from '$components/inputs/SelectLoadingIndicator'; +import {FormNames} from '$src/enums'; +import {setUserActiveServiceUnit} from '$src/usersPermissions/actions'; + +import type {UserServiceUnit, UserServiceUnits} from '$src/usersPermissions/types'; + +type Props = { + userServiceUnits: UserServiceUnits, + userActiveServiceUnit: UserServiceUnit, + setUserActiveServiceUnit: Function, +} + +class UserServiceUnitSelectInput extends Component { + handleChange = (val: any) => { + const { + setUserActiveServiceUnit, + userServiceUnits, + } = this.props; + + if (val) { + const {value} = val; + + const selected = userServiceUnits.filter((u) => u.id === value)[0]; + + if (selected) { + setUserActiveServiceUnit(selected); + } + } + } + + getOptions = (): Array => this.props.userServiceUnits.map((userServiceUnit) => { + return { + id: userServiceUnit.id, + value: userServiceUnit.id, + label: userServiceUnit.name, + }; + }); + + render() { + const { + userActiveServiceUnit, + userServiceUnits, + } = this.props; + + if (!userServiceUnits.length || !userActiveServiceUnit) { + return null; + } + + return ( +
+
+ + Palvelukokonaisuus + +
+
+