Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/actions/hearingEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const deleteSectionAttachment = (sectionId, attachment) => (dispatch) =>
.then(() => dispatch(createAction(EditorActions.DELETE_ATTACHMENT)({ sectionId, attachment })));
};

export const changeProject = (projectId, projectLists) => createAction(EditorActions.CHANGE_PROJECT)(projectId, projectLists);
export const changeProject = (hearingSlug, projectId, projectLists) => createAction(EditorActions.CHANGE_PROJECT)(hearingSlug, projectId, projectLists);

export const updateProjectLanguage = (languages) => createAction(EditorActions.UPDATE_PROJECT_LANGUAGE)({ languages });

Expand Down
139 changes: 44 additions & 95 deletions src/components/admin/HearingFormStep5.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/* eslint-disable react/forbid-prop-types */
import React from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { v1 as uuid } from 'uuid';
import { connect, useDispatch } from 'react-redux';
import { isEmpty } from 'lodash';
import { Button, Notification, Select, TextInput } from 'hds-react';
import { injectIntl, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Select } from 'hds-react';
import { injectIntl, FormattedMessage, useIntl } from 'react-intl';

import Icon from '../../utils/Icon';
import { createNotificationPayload, NOTIFICATION_TYPES } from '../../utils/notify';
import * as ProjectsSelector from '../../selectors/projectLists';
import Phase from './Phase';
import { hearingShape } from '../../types';
import {
changeProjectName,
Expand All @@ -22,17 +19,41 @@ import {
changePhase,
} from '../../actions/hearingEditor';
import { addToast } from '../../actions/toast';
import Project from './Project';

const HearingFormStep5 = ({ errors, hearing, hearingLanguages, language, projects, intl }) => {
const HearingFormStep5 = ({ errors, hearing, hearingLanguages, language, projects }) => {
const dispatch = useDispatch();
const intl = useIntl();

const onChangeProject = (selected) =>
const defaultProjectOptions = [
{ value: uuid(), label: intl.formatMessage({ id: 'noProject' }) },
{ value: '', label: intl.formatMessage({ id: 'defaultProject' }) },
];

const projectsOptions = projects.map((project) => ({
value: project.id,
label: `${
project.title[language] || project.title.fi || project.title.en || project.title.sv || 'Default project'
}`,
}));

const options = [...defaultProjectOptions, ...projectsOptions];

const [selectedProject, setSelectedProject] = useState(hearing.project);

useEffect(() => {
setSelectedProject(hearing.project);
}, [hearing.project]);

const onChangeProject = (selected) => {
dispatch(
changeProject({
hearingSlug: hearing.slug,
projectId: selected.value,
projectLists: projects,
}),
);
};

const addPhase = () => {
if (!isEmpty(hearingLanguages)) {
Expand All @@ -51,100 +72,31 @@ const HearingFormStep5 = ({ errors, hearing, hearingLanguages, language, project

const onActivePhase = (phaseId) => dispatch(activePhase(phaseId));

const renderProject = (selectedProject) => {
const phasesLength = hearing.project ? hearing.project.phases.length : null;
const errorStyle = !errors.project_phase_active && phasesLength === 0 ? 'has-error' : null;

return (
<div>
{selectedProject &&
hearingLanguages.map((usedLanguage) => (
<div id='projectName' key={usedLanguage}>
<TextInput
id='projectName'
name='projectName'
label={
<>
<FormattedMessage id='projectName' /> ({usedLanguage})
</>
}
maxLength={100}
value={selectedProject.title[usedLanguage]}
onBlur={(event) => onChangeProjectName(usedLanguage, event.target.value)}
invalid={!!errors.project_title}
errorText={errors.project_title}
style={{ marginBottom: 'var(--spacing-s)' }}
required
/>
</div>
))}
<div className='phases-container'>
{selectedProject &&
selectedProject.phases.map((phase, index) => {
const key = index;

return (
<Phase
onChange={onChangePhase}
phaseInfo={phase}
key={key}
indexNumber={index}
onDelete={deletePhase}
onActive={onActivePhase}
languages={hearingLanguages}
errors={errors}
/>
);
})}
</div>
{selectedProject && (
<div>
<Button className={classNames([errorStyle, 'kerrokantasi-btn'])} onClick={addPhase} size='small'>
<Icon className='icon' name='plus' /> <FormattedMessage id='addProcess'>{(txt) => txt}</FormattedMessage>
</Button>
</div>
)}
{!!errors.project_phase_active && phasesLength === 0 && (
<Notification type='error' size='small'>
{errors.project_phase_active}
</Notification>
)}
</div>
);
};

const selectedProject = hearing.project;

const defaultProjectOptions = [
{ value: uuid(), label: intl.formatMessage({ id: 'noProject' }) },
{ value: '', label: intl.formatMessage({ id: 'defaultProject' }) },
];

const projectsOptions = projects.map((project) => ({
value: project.id,
label: `${
project.title[language] || project.title.fi || project.title.en || project.title.sv || 'Default project'
}`,
}));

const options = [...defaultProjectOptions, ...projectsOptions];

const projectsInitialValue = selectedProject?.id ? selectedProject.id : options[0];
const projectValue = options.find((option) => option.value === hearing.project?.id);

return (
<div>
<div id='projectLists' style={{ marginBottom: 'var(--spacing-s)' }}>
<Select
optionKeyField='value'
id='commenting'
name='commenting'
id='project'
name='project'
label={<FormattedMessage id='projectSelection' />}
options={options}
onChange={onChangeProject}
defaultValue={projectsInitialValue}
value={projectValue}
/>
</div>
{renderProject(selectedProject)}
<Project
project={selectedProject}
errors={errors}
hearingLanguages={hearingLanguages}
onChangeProjectName={onChangeProjectName}
onChangePhase={onChangePhase}
deletePhase={deletePhase}
onActivePhase={onActivePhase}
addPhase={addPhase}
/>
</div>
);
};
Expand All @@ -155,13 +107,10 @@ HearingFormStep5.propTypes = {
language: PropTypes.string,
hearing: hearingShape,
hearingLanguages: PropTypes.arrayOf(PropTypes.string),
intl: PropTypes.object,
};

const mapStateToProps = (state) => ({
projects: ProjectsSelector.getProjects(state),
});

const WrappedHearingFormStep5 = connect(mapStateToProps)(injectIntl(HearingFormStep5));

export default WrappedHearingFormStep5;
export default connect(mapStateToProps)(injectIntl(HearingFormStep5));
66 changes: 43 additions & 23 deletions src/components/admin/Phase.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable react/forbid-prop-types */
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, useIntl } from 'react-intl';
import { Button, Checkbox, IconTrash, TextInput } from 'hds-react';
import { useDispatch } from 'react-redux';
import { isEmpty } from 'lodash';

import { createNotificationPayload } from '../../utils/notify';
import { addToast } from '../../actions/toast';
Expand All @@ -12,29 +13,49 @@ const Phase = ({ phaseInfo, indexNumber, onDelete, onChange, onActive, languages
const dispatch = useDispatch();
const intl = useIntl();

const durationsInitial = languages.reduce((acc, current) => {
acc[current] = phaseInfo.schedule[current];
const getValuePerLanguage = (selector) =>
languages.reduce((acc, current) => {
acc[current] = selector[current];

return acc;
}, {});
return acc;
}, {});

const descriptionsInitial = languages.reduce((acc, current) => {
acc[current] = phaseInfo.description[current];

return acc;
}, {});
const titlesInitial = getValuePerLanguage(phaseInfo.title);
const durationsInitial = getValuePerLanguage(phaseInfo.schedule);
const descriptionsInitial = getValuePerLanguage(phaseInfo.description);

const [phaseTitles, setPhaseTitles] = useState(titlesInitial);
const [phaseDurations, setPhaseDurations] = useState(durationsInitial);
const [phaseDescriptions, setPhaseDescriptions] = useState(descriptionsInitial);
const [phaseIsActive, setPhaseIsActive] = useState(phaseInfo.is_active);

useEffect(() => {
setPhaseTitles(!isEmpty(getValuePerLanguage(phaseInfo.title)) ? getValuePerLanguage(phaseInfo.title) : undefined);
setPhaseDurations(
!isEmpty(getValuePerLanguage(phaseInfo.schedule)) ? getValuePerLanguage(phaseInfo.schedule) : undefined,
);
setPhaseDescriptions(
!isEmpty(getValuePerLanguage(phaseInfo.description)) ? getValuePerLanguage(phaseInfo.description) : undefined,
);

setPhaseIsActive(phaseInfo.is_active ? phaseInfo.is_active : undefined);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [phaseInfo]);

const handleRadioOnChange = (event) => {
setPhaseIsActive(event.target.checked);

if (event.target.checked) {
onActive(phaseInfo.id || phaseInfo.frontId);
} else {
onActive(null);
}
};

if (!phaseInfo) {
return null;
}

return (
<div>
{languages.map((usedLanguage, index) => (
Expand All @@ -49,11 +70,14 @@ const Phase = ({ phaseInfo, indexNumber, onDelete, onChange, onActive, languages
<FormattedMessage id='phase' /> {indexNumber + 1} ({usedLanguage})
</>
}
value={phaseInfo.title[usedLanguage]}
value={phaseTitles ? phaseTitles[usedLanguage] : ''}
maxLength={100}
onBlur={(event) =>
onChange(phaseInfo.id || phaseInfo.frontId, 'title', usedLanguage, event.target.value)
}
onChange={(event) => {
const { value } = event.target;

setPhaseTitles((prevState) => ({ ...prevState, [usedLanguage]: value }));
onChange(phaseInfo.id || phaseInfo.frontId, 'title', usedLanguage, value);
}}
invalid={!!errors.project_phase_title}
errorText={errors.project_phase_title}
required
Expand Down Expand Up @@ -90,11 +114,9 @@ const Phase = ({ phaseInfo, indexNumber, onDelete, onChange, onActive, languages
onChange={(event) => {
const { value } = event.target;

setPhaseDurations({ ...phaseDurations, [usedLanguage]: value });
setPhaseDurations((prevState) => ({ ...prevState, [usedLanguage]: value }));
onChange(phaseInfo.id || phaseInfo.frontId, 'schedule', usedLanguage, value);
}}
onBlur={(event) =>
onChange(phaseInfo.id || phaseInfo.frontId, 'schedule', usedLanguage, event.target.value)
}
/>
</div>
<div className='hearing-form-column'>
Expand All @@ -107,11 +129,9 @@ const Phase = ({ phaseInfo, indexNumber, onDelete, onChange, onActive, languages
onChange={(event) => {
const { value } = event.target;

setPhaseDescriptions({ ...phaseDescriptions, [usedLanguage]: value });
setPhaseDescriptions((prevState) => ({ ...prevState, [usedLanguage]: value }));
onChange(phaseInfo.id || phaseInfo.frontId, 'description', usedLanguage, value);
}}
onBlur={(event) =>
onChange(phaseInfo.id || phaseInfo.frontId, 'description', usedLanguage, event.target.value)
}
/>
</div>
</div>
Expand All @@ -122,7 +142,7 @@ const Phase = ({ phaseInfo, indexNumber, onDelete, onChange, onActive, languages
name={`phase-active-${indexNumber + 1}`}
label={intl.formatMessage({ id: 'phaseActive' })}
onChange={handleRadioOnChange}
checked={phaseInfo.is_active}
checked={phaseIsActive}
errorText={errors.project_phase_active}
/>
)}
Expand Down
Loading
Loading