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/areaSearch/components/AreaSearchApplicationListPage.js b/src/areaSearch/components/AreaSearchApplicationListPage.js index b332abf6e..c964e71b7 100644 --- a/src/areaSearch/components/AreaSearchApplicationListPage.js +++ b/src/areaSearch/components/AreaSearchApplicationListPage.js @@ -69,6 +69,8 @@ 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'; +import { getUserActiveServiceUnit } from "../../usersPermissions/selectors"; +import type { UserServiceUnit } from "../../usersPermissions/types"; const VisualizationTypes = { MAP: 'map', @@ -106,6 +108,7 @@ type Props = { lastEditError: any, change: Function, selectedSearches: Object, + userActiveServiceUnit: UserServiceUnit } type State = { @@ -125,6 +128,7 @@ type State = { class AreaSearchApplicationListPage extends PureComponent { _isMounted: boolean + _hasFetchedAreaSearches: boolean; state: State = { properties: [], @@ -139,6 +143,7 @@ class AreaSearchApplicationListPage extends PureComponent { isEditModalOpen: false, isExportModalOpen: false, editModalTargetAreaSearch: null, + userActiveServiceUnit: undefined, } static contextTypes = { @@ -356,7 +361,7 @@ class AreaSearchApplicationListPage extends PureComponent { }; search = () => { - const {fetchAreaSearchList, location: {search}} = this.props; + const {fetchAreaSearchList, location: {search}, userActiveServiceUnit} = this.props; const searchQuery = getUrlParams(search); const page = searchQuery.page ? Number(searchQuery.page) : 1; @@ -365,6 +370,11 @@ class AreaSearchApplicationListPage extends PureComponent { } searchQuery.limit = LIST_TABLE_PAGE_SIZE; + + if (searchQuery.service_unit === undefined && userActiveServiceUnit) { + searchQuery.service_unit = userActiveServiceUnit.id; + } + delete searchQuery.page; delete searchQuery.in_bbox; delete searchQuery.visualization; @@ -374,7 +384,7 @@ class AreaSearchApplicationListPage extends PureComponent { } searchByBBox = () => { - const {fetchAreaSearchListByBBox, location: {search}} = this.props; + const {fetchAreaSearchListByBBox, location: {search}, userActiveServiceUnit} = this.props; const searchQuery = getUrlParams(search); const leaseStates = this.getSearchStates(searchQuery); @@ -388,6 +398,10 @@ class AreaSearchApplicationListPage extends PureComponent { searchQuery.lease_state = leaseStates; } + if (searchQuery.service_unit === undefined && userActiveServiceUnit) { + searchQuery.service_unit = userActiveServiceUnit.id; + } + searchQuery.limit = 10000; delete searchQuery.page; delete searchQuery.visualization; @@ -480,11 +494,29 @@ class AreaSearchApplicationListPage extends PureComponent { } componentDidUpdate(prevProps) { - const {location: {search: currentSearch}, isEditingAreaSearch, lastEditError} = this.props; - const {location: {search: prevSearch}} = prevProps; + const {location: {search: currentSearch}, isEditingAreaSearch, lastEditError, userActiveServiceUnit} = this.props; + const {location: {search: prevSearch}, userActiveServiceUnit: prevUserActiveServiceUnit} = prevProps; const {visualizationType} = this.state; const searchQuery = getUrlParams(currentSearch); + const handleSearch = () => { + this.setSearchFormValues(); + this.search(); + }; + + if(userActiveServiceUnit) { + + if(!this._hasFetchedAreaSearches) { // No search has been done yet + handleSearch(); + this._hasFetchedAreaSearches = true; + + } else if(userActiveServiceUnit !== prevUserActiveServiceUnit + && !currentSearch.includes('service_unit')) { + // Search again after changing user active service unit only if not explicitly setting the service unit filter + handleSearch(); + } + } + if ((currentSearch !== prevSearch) || (!isEditingAreaSearch && !lastEditError && prevProps.isEditingAreaSearch)) { this.closeAreaSearchEditModal(); @@ -514,6 +546,7 @@ class AreaSearchApplicationListPage extends PureComponent { componentWillUnmount() { window.removeEventListener('popstate', this.handlePopState); this._isMounted = false; + this._hasFetchedAreaSearches = false; } handlePopState = () => { @@ -546,7 +579,7 @@ class AreaSearchApplicationListPage extends PureComponent { } setSearchFormValues = () => { - const {location: {search}, initializeForm} = this.props; + const {location: {search}, initializeForm, userActiveServiceUnit} = this.props; const searchQuery = getUrlParams(search); const page = searchQuery.page ? Number(searchQuery.page) : 1; const states = this.getSearchStates(searchQuery); @@ -565,6 +598,10 @@ class AreaSearchApplicationListPage extends PureComponent { delete initialValues.visualization; delete initialValues.zoom; + if(initialValues.service_unit === undefined && userActiveServiceUnit) { + initialValues.service_unit = userActiveServiceUnit.id; + } + await initializeForm(FormNames.AREA_SEARCH_SEARCH, initialValues); }; @@ -799,6 +836,7 @@ export default (flowRight( isEditingAreaSearch: getIsEditingAreaSearch(state), lastEditError: getLastAreaSearchEditError(state), selectedSearches: selector(state, 'selectedSearches'), + userActiveServiceUnit: getUserActiveServiceUnit(state), }; }, { diff --git a/src/areaSearch/components/search/Search.js b/src/areaSearch/components/search/Search.js index 375903dc7..b75149a57 100644 --- a/src/areaSearch/components/search/Search.js +++ b/src/areaSearch/components/search/Search.js @@ -51,6 +51,7 @@ type State = { areaSearches: ApiResponse, areaSearchAttributes: Attributes, attributes: Attributes, + serviceUnitOptions: Array, } class Search extends Component { @@ -64,6 +65,7 @@ class Search extends Component { areaSearches: null, areaSearchAttributes: {}, attributes: {}, + serviceUnitOptions: [] }; componentDidMount() { @@ -148,6 +150,7 @@ class Search extends Component { if (props.areaSearchAttributes !== state.areaSearchAttributes) { newState.intendedUseOptions = getFieldOptions(props.areaSearchAttributes, AreaSearchFieldPaths.INTENDED_USE); newState.lessorOptions = getFieldOptions(props.areaSearchAttributes, AreaSearchFieldPaths.LESSOR); + newState.serviceUnitOptions = getFieldOptions(props.areaSearchAttributes, 'service_unit', true); } return !isEmpty(newState) ? newState : null; @@ -163,6 +166,7 @@ class Search extends Component { isBasicSearch, intendedUseOptions, lessorOptions, + serviceUnitOptions, } = this.state; return ( @@ -459,6 +463,28 @@ class Search extends Component { /> + + + Palvelukokonaisuus + + + + + + diff --git a/src/components/attributes/LeaseInvoiceTabAttributes.js b/src/components/attributes/LeaseInvoiceTabAttributes.js index 6d7044fa0..0384b0389 100644 --- a/src/components/attributes/LeaseInvoiceTabAttributes.js +++ b/src/components/attributes/LeaseInvoiceTabAttributes.js @@ -136,7 +136,7 @@ function LeaseInvoiceTabAttributes(WrappedComponent: any) { fetchLeaseCreateChargeAttributes(); } - if(!isFetchingReceivableTypes && !receivableTypes) { + if(!isFetchingReceivableTypes) { fetchReceivableTypes(); } } diff --git a/src/components/form/FieldTypeContactSelect.js b/src/components/form/FieldTypeContactSelect.js index 9b776ba55..d25cd18fa 100644 --- a/src/components/form/FieldTypeContactSelect.js +++ b/src/components/form/FieldTypeContactSelect.js @@ -7,6 +7,8 @@ import {getContentContact} from '$src/contacts/helpers'; import {addEmptyOption, sortStringByKeyAsc} from '$util/helpers'; import {fetchContacts} from '$src/contacts/requestsAsync'; +import type {UserServiceUnit} from '$src/usersPermissions/types'; + type Props = { disabled?: boolean, displayError: boolean, @@ -14,6 +16,7 @@ type Props = { isDirty: boolean, onChange: Function, placeholder?: string, + serviceUnit: UserServiceUnit, } const FieldTypeContactSelect = ({ @@ -23,11 +26,13 @@ const FieldTypeContactSelect = ({ isDirty, onChange, placeholder, + serviceUnit, }: Props): React$Node => { const getContacts = debounce(async(inputValue: string, callback: Function) => { const contacts = await fetchContacts({ search: inputValue, limit: 20, + service_unit: serviceUnit?.id || "", }); callback(addEmptyOption(contacts.map((lessor) => getContentContact(lessor)).sort((a, b) => sortStringByKeyAsc(a, b, 'label')))); diff --git a/src/components/form/FieldTypeLeaseSelect.js b/src/components/form/FieldTypeLeaseSelect.js index d5f0b27a0..cd6e0b39c 100644 --- a/src/components/form/FieldTypeLeaseSelect.js +++ b/src/components/form/FieldTypeLeaseSelect.js @@ -7,6 +7,8 @@ import {getContentLeaseOption} from '$src/leases/helpers'; import {addEmptyOption, sortStringByKeyAsc} from '$util/helpers'; import {fetchLeases} from '$src/leases/requestsAsync'; +import type {UserServiceUnit} from '$src/usersPermissions/types'; + type Props = { disabled?: boolean, displayError: boolean, @@ -14,6 +16,7 @@ type Props = { isDirty: boolean, onChange: Function, placeholder?: string, + serviceUnit: UserServiceUnit, } const FieldTypeLeaseSelect = ({ @@ -23,12 +26,14 @@ const FieldTypeLeaseSelect = ({ isDirty, onChange, placeholder, + serviceUnit, }: Props): React$Node => { const getLeases = debounce(async(inputValue: string, callback: Function) => { const leases = await fetchLeases({ succinct: true, identifier: inputValue, limit: 15, + service_unit: serviceUnit?.id || "", }); callback(addEmptyOption(leases.map((lease) => getContentLeaseOption(lease)).sort((a, b) => sortStringByKeyAsc(a, b, 'label')))); diff --git a/src/components/form/FieldTypeLessorSelect.js b/src/components/form/FieldTypeLessorSelect.js index b795e477a..4b9f14b99 100644 --- a/src/components/form/FieldTypeLessorSelect.js +++ b/src/components/form/FieldTypeLessorSelect.js @@ -7,6 +7,8 @@ import {getContentLessor} from '$src/lessor/helpers'; import {addEmptyOption, sortStringByKeyAsc} from '$util/helpers'; import {fetchContacts} from '$src/contacts/requestsAsync'; +import type {UserServiceUnit} from '$src/usersPermissions/types'; + type Props = { disabled?: boolean, displayError: boolean, @@ -14,6 +16,7 @@ type Props = { isDirty: boolean, onChange: Function, placeholder?: string, + serviceUnit: UserServiceUnit, } const FieldTypeLessorSelect = ({ @@ -23,11 +26,13 @@ const FieldTypeLessorSelect = ({ isDirty, onChange, placeholder, + serviceUnit, }: Props): React$Node => { const getLessors = debounce(async(inputValue: string, callback: Function) => { const lessors = await fetchContacts({ is_lessor: true, search: inputValue, + service_unit: serviceUnit?.id || "", }); callback(addEmptyOption(lessors.map((lessor) => getContentLessor(lessor)).sort((a, b) => sortStringByKeyAsc(a, b, 'label')))); diff --git a/src/components/form/FieldTypeUserSelect.js b/src/components/form/FieldTypeUserSelect.js index 9203f6fa6..695450b17 100644 --- a/src/components/form/FieldTypeUserSelect.js +++ b/src/components/form/FieldTypeUserSelect.js @@ -7,6 +7,8 @@ import {getContentUser} from '$src/users/helpers'; import {addEmptyOption, sortStringByKeyAsc} from '$util/helpers'; import {fetchSingleUser, fetchUsers} from '$src/users/requestsAsync'; +import type {UserServiceUnit} from '$src/usersPermissions/types'; + type Props = { disabled?: boolean, displayError: boolean, @@ -16,6 +18,7 @@ type Props = { placeholder?: string, multiSelect?: boolean, valueSelectedCallback: Function, + serviceUnit: UserServiceUnit, } const FieldTypeUserSelect = ({ @@ -26,6 +29,7 @@ const FieldTypeUserSelect = ({ onChange, placeholder, multiSelect, + serviceUnit, }: Props): React$Node => { // If a plain ID value has already been set when the component mounts, // retrieve the corresponding single user object and set the state up accordingly @@ -65,6 +69,7 @@ const FieldTypeUserSelect = ({ const getUsers = debounce(async(inputValue: string, callback: Function) => { const contacts = await fetchUsers({ search: inputValue, + service_unit: serviceUnit?.id || "", }); callback(addEmptyOption(contacts.map((lessor) => getContentUser(lessor)).sort((a, b) => sortStringByKeyAsc(a, b, 'label')))); diff --git a/src/components/form/FormField.js b/src/components/form/FormField.js index 6817b9cc2..d3920f684 100644 --- a/src/components/form/FormField.js +++ b/src/components/form/FormField.js @@ -44,6 +44,8 @@ import {getRouteById, Routes} from '$src/root/routes'; import {genericValidator} from '$components/form/validations'; import {getHoursAndMinutes} from '$util/date'; +import type {UserServiceUnit} from '$src/usersPermissions/types'; + const FieldTypes = { [FieldTypeOptions.ADDRESS]: FieldTypeAddress, [FieldTypeOptions.BOOLEAN]: FieldTypeBoolean, @@ -116,6 +118,7 @@ type InputProps = { relativeTo?: any, required: boolean, rows?: number, + serviceUnit: UserServiceUnit, setRefForField?: Function, tooltipStyle?: Object, uiDataKey: ?string, @@ -152,6 +155,7 @@ const FormFieldInput = ({ relativeTo, required, rows, + serviceUnit, setRefForField, tooltipStyle, valueSelectedCallback, @@ -241,7 +245,7 @@ const FormFieldInput = ({ }
- {createElement(fieldComponent, {autoBlur, autoComplete, displayError, disabled, filterOption, input, isDirty, isLoading, label, language, minDate, maxDate, multiSelect, optionLabel, placeholder, options, rows, setRefForField, type, valueSelectedCallback})} + {createElement(fieldComponent, {autoBlur, autoComplete, displayError, disabled, filterOption, input, isDirty, isLoading, label, language, minDate, maxDate, multiSelect, optionLabel, placeholder, options, rows, serviceUnit,setRefForField, type, valueSelectedCallback})} {unit && {unit}}
{displayError && } @@ -303,6 +307,7 @@ type Props = { readOnlyValueRenderer?: Function, relativeTo?: any, rows?: number, + serviceUnit: UserServiceUnit, setRefForField?: Function, tooltipStyle?: Object, validate?: Function, @@ -421,6 +426,7 @@ class FormField extends PureComponent { readOnlyValueRenderer, relativeTo, rows, + serviceUnit, setRefForField, tooltipStyle, valueSelectedCallback, @@ -469,6 +475,7 @@ class FormField extends PureComponent { relativeTo={relativeTo} required={required} rows={rows} + serviceUnit={serviceUnit} setRefForField={setRefForField} tooltipStyle={tooltipStyle} validate={allowEdit diff --git a/src/components/form/validations.js b/src/components/form/validations.js index 1e081225e..fa51ebb80 100644 --- a/src/components/form/validations.js +++ b/src/components/form/validations.js @@ -77,6 +77,13 @@ export const dateGreaterOrEqual = (date: ?string, otherDate: ?string, error?: st : undefined; }; +export const internalOrder = (value: any, error?: string): ?string => { + if (isEmptyValue(value)) { + return undefined; + } + return value.length <= 12 ? undefined : (error ? error : 'Sisäisen tilauksen numero on korkeintaan 12-merkkinen numerosarja.'); +}; + export const referenceNumber = (value: any, error?: string): ?string => { if (isEmptyValue(value)) { return undefined; diff --git a/src/components/inputs/LeaseSelectInput.js b/src/components/inputs/LeaseSelectInput.js index 2887f73d3..a2572feda 100644 --- a/src/components/inputs/LeaseSelectInput.js +++ b/src/components/inputs/LeaseSelectInput.js @@ -15,6 +15,8 @@ import {getAttributes as getLeaseAttributes} from '$src/leases/selectors'; import {store} from '$src/root/startApp'; import { LeaseFieldPaths, LeaseHistoryItemTypes, LeaseHistoryContentTypes } from "$src/leases/enums"; +import type {UserServiceUnit} from '$src/usersPermissions/types'; + type Props = { disabled?: boolean, name: string, @@ -22,6 +24,7 @@ type Props = { onChange: Function, placeholder?: string, leaseHistoryItems: Array, + serviceUnit: UserServiceUnit, value?: Object, } @@ -32,6 +35,7 @@ const LeaseSelectInput = ({ onChange, placeholder, leaseHistoryItems, + serviceUnit, value, }: Props) => { const leaseAttributes = getLeaseAttributes(store.getState()); diff --git a/src/components/inputs/UserServiceUnitSelectInput.js b/src/components/inputs/UserServiceUnitSelectInput.js new file mode 100644 index 000000000..006bd08b4 --- /dev/null +++ b/src/components/inputs/UserServiceUnitSelectInput.js @@ -0,0 +1,101 @@ +// @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; + + const selected = userServiceUnits.find((u) => u.id === val?.value); + + 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 + +
+
+