Skip to content

Commit

Permalink
Merge pull request #474 from City-of-Helsinki/MVJ-147-add-area-search…
Browse files Browse the repository at this point in the history
…-attachments-in-edit-view

Area search application: add and remove attachments in officer UI
  • Loading branch information
NC-jsAhonen authored Apr 23, 2024
2 parents 188b224 + a334df7 commit 7d5c606
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 40 deletions.
2 changes: 2 additions & 0 deletions src/application/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ export const getInitialApplicationForm = (
};
export const getApplicationAttachmentDownloadLink = (id: number): string => createUrl(`attachment/${id}/download`);

export const getAreaSearchApplicationAttachmentDownloadLink = (id: number): string => createUrl(`area_search_attachment/${id}/download`);

export const getSectionTemplate = (formName: string, formPath: string, identifier: string): Object => {
const state = store.getState();
const templates = formValueSelector(formName)(
Expand Down
5 changes: 5 additions & 0 deletions src/areaSearch/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import type {
UploadAreaSearchAttachmentAction,
ReceiveFileOperationFinishedAction,
ReceiveFileOperationFailedAction,
SetAreaSearchAttachmentsAction,
UploadedAreaSearchAttachmentMeta,
} from '$src/areaSearch/types';
import type {Attributes, Methods} from '$src/types';

Expand Down Expand Up @@ -160,3 +162,6 @@ export const receiveFileOperationFinished = (): ReceiveFileOperationFinishedActi

export const receiveFileOperationFailed = (error: any): ReceiveFileOperationFailedAction =>
createAction('mvj/areaSearch/RECEIVE_FILE_OPERATION_FAILED')(error);

export const setAreaSearchAttachments = (attachments: Array<UploadedAreaSearchAttachmentMeta>): SetAreaSearchAttachmentsAction =>
createAction('mvj/areaSearch/SET_ATTACHMENTS')(attachments);
24 changes: 14 additions & 10 deletions src/areaSearch/components/AreaSearchApplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import {
} from '$util/helpers';
import type {Attributes} from '$src/types';
import {reshapeSavedApplicationObject} from '$src/plotApplications/helpers';
import {transformApplicantSectionTitle} from '$src/application/helpers';
import {
transformApplicantSectionTitle,
getAreaSearchApplicationAttachmentDownloadLink,
} from '$src/application/helpers';
import Title from '$components/content/Title';
import Divider from '$components/content/Divider';
import Collapse from '$components/collapse/Collapse';
Expand Down Expand Up @@ -212,15 +215,16 @@ class AreaSearchApplication extends Component<Props, State> {
]}
/>)}
<Collapse headerTitle="Liitteet" defaultOpen>
{areaSearch.area_search_attachments.map((file, index) => <Row key={file.id}>
<Column small={3}>Liite {index + 1}</Column>
<Column small={9}>
<FileDownloadLink
fileUrl={file.file}
label={file.name}
/>
</Column>
</Row>)}
{areaSearch.area_search_attachments.map((file, index) => {
return (<Row key={file.id}>
<Column small={3}>Liite {index + 1}</Column>
<Column small={9}>
<FileDownloadLink
fileUrl={getAreaSearchApplicationAttachmentDownloadLink(file.id)}
label={file.name}
/>
</Column>
</Row>)})}
{areaSearch.area_search_attachments.length === 0 && <p>
Hakemuksella ei ole liitteitä.
</p>}
Expand Down
71 changes: 58 additions & 13 deletions src/areaSearch/components/AreaSearchApplicationEdit.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @flow

import React, {Component, Fragment} from 'react';
import {connect} from 'react-redux';
import flowRight from 'lodash/flowRight';
Expand All @@ -17,10 +16,14 @@ import {
getFieldAttributes,
getFieldOptions,
getLabelOfOption,
displayUIMessage,
} from '$util/helpers';
import type {Attributes} from '$src/types';
import {reshapeSavedApplicationObject} from '$src/plotApplications/helpers';
import {transformApplicantSectionTitle} from '$src/application/helpers';
import {
transformApplicantSectionTitle,
getAreaSearchApplicationAttachmentDownloadLink,
} from '$src/application/helpers';
import Title from '$components/content/Title';
import Divider from '$components/content/Divider';
import Collapse from '$components/collapse/Collapse';
Expand All @@ -41,21 +44,24 @@ import {getInitialAreaSearchEditForm, transformApplicantInfoCheckTitle} from '$s
import FormField from '$components/form/FormField';
import TitleH3 from '$components/content/TitleH3';
import AreaSearchStatusNoteHistory from '$src/areaSearch/components/AreaSearchStatusNoteHistory';
import {getFormAttributes, getIsFetchingFormAttributes} from '$src/application/selectors';
import {getFormAttributes, getIsFetchingFormAttributes, getIsPerformingFileOperation} from '$src/application/selectors';
import {APPLICANT_SECTION_IDENTIFIER} from '$src/application/constants';
import type {Form} from '$src/application/types';
import type {AreaSearch, UploadedAreaSearchAttachmentMeta} from '$src/areaSearch/types';
import AddFileButton from '$components/form/AddFileButton';
import {uploadAttachment, setAreaSearchAttachments} from '$src/areaSearch/actions';
import RemoveButton from '$components/form/RemoveButton';

type OwnProps = {

};

type Props = {
...OwnProps,
areaSearch: Object | null,
areaSearch: AreaSearch | null,
isFetchingFormAttributes: boolean,
isPerformingFileOperation: boolean,
formAttributes: Attributes,
areaSearchAttributes: Attributes,
initialize: Function,
uploadAttachment: Function,
setAreaSearchAttachments: Function,
};

type State = {
Expand Down Expand Up @@ -91,14 +97,39 @@ class AreaSearchApplicationEdit extends Component<Props, State> {

initialize(getInitialAreaSearchEditForm(areaSearch));
}

handleFileAdded = (e: Event) => {
const {uploadAttachment, areaSearch, setAreaSearchAttachments} = this.props;
const currentFiles = areaSearch?.area_search_attachments || [];
const file = e.target.files[0];

const fileExists = currentFiles.find((currentFile) => currentFile.name === file.name);

if (fileExists) {
displayUIMessage({title: 'Virhe', body: `Tiedosto nimellä ${file.name} on jo listassa.`}, {type: 'error'});
} else {
uploadAttachment({
fileData: file,
areaSearch: areaSearch?.id,
callback: (newFile: UploadedAreaSearchAttachmentMeta) => {
setAreaSearchAttachments([...currentFiles, newFile])
},
});
}

}

render(): React$Node {
const {
areaSearch,
isFetchingFormAttributes,
isPerformingFileOperation,
formAttributes,
areaSearchAttributes,
attachments,
addAttachment,
} = this.props;

const {
selectedAreaSectionRefreshKey,
} = this.state;
Expand Down Expand Up @@ -249,18 +280,25 @@ class AreaSearchApplicationEdit extends Component<Props, State> {
]}
/>)}
<Collapse headerTitle="Liitteet" defaultOpen>
{areaSearch.area_search_attachments.map((file, index) => <Row key={file.id}>
{areaSearch.area_search_attachments.map((file, index) => {
return (<Row key={file.name}>
<Column small={3}>Liite {index + 1}</Column>
<Column small={9}>
<Column small={8}>
<FileDownloadLink
fileUrl={file.file}
fileUrl={getAreaSearchApplicationAttachmentDownloadLink(file.id)}
label={file.name}
/>
</Column>
</Row>)}
</Row>)})}
{areaSearch.area_search_attachments.length === 0 && <p>
Hakemuksella ei ole liitteitä.
</p>}
<AddFileButton
label='Lisää tiedosto'
onChange={this.handleFileAdded}
name="AreaSearchApplicationEditAttachment"
disabled={isPerformingFileOperation}
/>
</Collapse>

<Collapse headerTitle="Hakemuksen käsittely" defaultOpen>
Expand Down Expand Up @@ -352,13 +390,20 @@ class AreaSearchApplicationEdit extends Component<Props, State> {
}
}


export default (flowRight(
connect((state) => ({
areaSearch: getCurrentAreaSearch(state),
areaSearchAttributes: getAttributes(state),
formAttributes: getFormAttributes(state),
isFetchingFormAttributes: getIsFetchingFormAttributes(state),
})),
isPerformingFileOperation: getIsPerformingFileOperation(state),
}),
{
uploadAttachment,
setAreaSearchAttachments,
},
),
reduxForm({
form: FormNames.AREA_SEARCH,
}),
Expand Down
4 changes: 2 additions & 2 deletions src/areaSearch/components/AreaSearchApplicationListPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +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";
import {getUserActiveServiceUnit} from '$src/usersPermissions/selectors';
import type {UserServiceUnit} from '$src/usersPermissions/types';

const VisualizationTypes = {
MAP: 'map',
Expand Down
10 changes: 0 additions & 10 deletions src/areaSearch/components/AreaSearchApplicationPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,16 +302,6 @@ class AreaSearchApplicationPage extends Component<Props, State> {
scrollToTopPage();
}

/*
if (isEmpty(prevProps.currentAreaSearch) && !isEmpty(currentAreaSearch)) {
const storedAreaSearchId = getSessionStorageItem('areaSearchId');
if(Number(areaSearchId) === storedAreaSearchId) {
this.setState({isRestoreModalOpen: true});
}
}
*/

if (!isFetching && prevProps.isFetching) {
setPageTitle(`Hakemus ${currentAreaSearch?.identifier || ''}`);
}
Expand Down
13 changes: 12 additions & 1 deletion src/areaSearch/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import type {
ReceiveFormValidFlagsAction, ReceiveIsSaveClickedAction,
ReceiveSingleAreaSearchAction,
} from '$src/areaSearch/types';
import type {ReceiveAttributesAction, ReceiveMethodsAction} from '$src/areaSearch/types';
import type {
ReceiveAttributesAction,
ReceiveMethodsAction,
SetAreaSearchAttachmentsAction
} from '$src/areaSearch/types';
import type {ApiResponse} from '$src/types';
import merge from 'lodash/merge';

Expand Down Expand Up @@ -82,6 +86,13 @@ const currentAreaSearchReducer: Reducer<Object | null> = handleActions({
},
['mvj/areaSearch/CREATE_SPECS']: () => null,
['mvj/areaSearch/RECEIVE_SPECS_CREATED']: (state, {payload: result}) => result,
['mvj/areaSearch/SET_ATTACHMENTS']: (state, {payload: attachments}) => {
const { area_search_attachments } = state;
return {
...state,
area_search_attachments: attachments
}
},
}, null);

const isFetchingCurrentAreaSearchReducer: Reducer<boolean> = handleActions({
Expand Down
26 changes: 26 additions & 0 deletions src/areaSearch/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
receiveListMethods,
receiveMethods,
receiveSingleAreaSearch,
setAreaSearchAttachments,
showEditMode,
singleAreaSearchNotFound,
} from '$src/areaSearch/actions';
Expand Down Expand Up @@ -275,6 +276,31 @@ describe('AreaSearch', () => {
}, receiveAreaSearchEditFailed('test error'));
expect(state).to.deep.equal(newState);
});

it('should update area search attachments', () => {
const dummyFile = {
id: 1,
attachment: "http://localhost:8001/media/area_search_attachments/2024-04-19/filename.pdf",
name: "filename.pdf",
field: 1,
created_at: "2024-04-19T10:54:21.269517+03:00",
user: {
id: 1,
first_name: "Matti",
last_name: "Meikäläinen",
is_staff: false,
username: "u-abcdefg1hij23klmnvwxyzabcd"
}
};
const newState = {...defaultState, currentAreaSearch: {area_search_attachments: [dummyFile]}};

const state = areaSearchReducer({
currentAreaSearch: {
area_search_attachments: [],
},
}, setAreaSearchAttachments([dummyFile]));
expect(state).to.deep.equal(newState);
});
});
});
});
42 changes: 38 additions & 4 deletions src/areaSearch/types.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
import type {Action, ApiResponse, Attributes, Methods} from '$src/types';
import type {Action, ApiResponse, Attributes, Methods, User} from '$src/types';
import type {UploadedFileMeta} from '$src/application/types';

export type AreaSearchState = {
Expand Down Expand Up @@ -27,9 +27,42 @@ export type AreaSearchState = {
isPerformingFileOperation: boolean,
};

export type AreaSearch = Object;
export type AreaSearch = {
id: number;
address?: string;
answer: Object; // TODO: specify
applicants: Array<string>;
area_search_attachments: Array<UploadedAreaSearchAttachmentMeta>;
area_search_status: Object; // TODO: specify
description_area?: string;
description_intended_use?: string;
district: string;
end_date?: string;
form: Object; // TODO: specify
geometry: Object; // TODO: specify
identifier: string;
intended_use: number;
lessor: string;
plot: Array<string>;
preparer?: User;
received_date?: string;
service_unit?: number;
start_date: string;
state: string;
};

export type AreaSearchId = number;

export type UploadedAreaSearchAttachmentMeta = {
id: number;
attachment: string;
name: string;
field: number;
created_at: string;
user?: User;
};


export type FetchListAttributesAction = Action<'mvj/areaSearch/FETCH_LIST_ATTRIBUTES', void>;
export type ReceiveListAttributesAction = Action<'mvj/areaSearch/RECEIVE_LIST_ATTRIBUTES', Attributes>;
export type ReceiveListMethodsAction = Action<'mvj/areaSearch/RECEIVE_LIST_METHODS', Methods>;
Expand Down Expand Up @@ -74,6 +107,7 @@ export type ReceiveAreaSearchInfoCheckBatchEditFailureAction = Action<'mvj/areaS
export type EditAreaSearchAction = Action<'mvj/areaSearch/EDIT', Object>;
export type ReceiveAreaSearchEditedAction = Action<'mvj/areaSearch/RECEIVE_EDITED', void>;
export type ReceiveAreaSearchEditFailedAction = Action<'mvj/areaSearch/RECEIVE_EDIT_FAILED', Object>;
export type SetAreaSearchAttachmentsAction = Action<'mvj/areaSearch/SET_ATTACHMENTS', Array<UploadedAreaSearchAttachmentMeta>>;

export type CreateAreaSearchSpecsAction = Action<'mvj/areaSearch/CREATE_SPECS', Object>;
export type ReceiveAreaSearchSpecsCreatedAction = Action<'mvj/areaSearch/RECEIVE_SPECS_CREATED', Object>;
Expand All @@ -88,8 +122,8 @@ export type DeleteAreaSearchAttachmentAction = Action<'mvj/areaSearch/DELETE_ATT
callback?: () => void,
}>;
export type UploadAreaSearchAttachmentAction = Action<'mvj/areaSearch/UPLOAD_ATTACHMENT', {
fileData: Object,
callback?: (fileData: UploadedFileMeta) => void,
fileData: File,
callback?: (fileData: UploadedAreaSearchAttachmentMeta) => void,
areaSearch?: number,
}>;
export type ReceiveFileOperationFinishedAction = Action<'mvj/areaSearch/RECEIVE_FILE_OPERATION_FINISHED', void>;
Expand Down
8 changes: 8 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,11 @@ export type LeafletGeoJson = {
features: Array<LeafletFeature>,
type: 'FeatureCollection',
}

export type User = {
id: number;
first_name?: string;
last_name?: string;
is_staff: boolean;
username: string;
}

0 comments on commit 7d5c606

Please sign in to comment.