Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HAI-1881 Save hanke form in summary page #393

Merged
merged 6 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
64 changes: 64 additions & 0 deletions src/domain/hanke/edit/HankeForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import HankeForm from './HankeForm';
import HankeFormContainer from './HankeFormContainer';
import { HANKE_VAIHE, HANKE_TYOMAATYYPPI } from '../../types/hanke';
import { render, cleanup, fireEvent, waitFor, screen } from '../../../testUtils/render';
import hankkeet from '../../mocks/data/hankkeet-data';

afterEach(cleanup);

Expand Down Expand Up @@ -36,6 +37,36 @@ const hankeData: HankeDataDraft = {
};
*/

function fillBasicInformation(
options: {
name?: string;
description?: string;
address?: string;
phase?: 'Ohjelmointi' | 'Suunnittelu' | 'Rakentaminen';
isYKT?: boolean;
} = {},
) {
const {
name = nimi,
description = hankkeenKuvaus,
address = hankkeenOsoite,
phase = 'Ohjelmointi',
isYKT = false,
} = options;

fireEvent.change(screen.getByLabelText(/hankkeen nimi/i), {
target: { value: name },
});
fireEvent.change(screen.getByLabelText(/hankkeen kuvaus/i), { target: { value: description } });
fireEvent.change(screen.getByLabelText(/katuosoite/i), {
target: { value: address },
});
fireEvent.click(screen.getByRole('radio', { name: phase }));
if (isYKT) {
fireEvent.click(screen.getByRole('checkbox', { name: 'Hanke on YKT-hanke' }));
}
}

const formData: HankeDataFormState = {
vaihe: HANKE_VAIHE.OHJELMOINTI,
rakennuttajat: [],
Expand Down Expand Up @@ -195,4 +226,37 @@ describe('HankeForm', () => {
await user.click(screen.getByText(/toteuttajan tiedot/i));
await user.click(screen.getByText(/muiden tahojen tiedot/i));
});

test('Should be able to save and quit', async () => {
const { user } = render(<HankeFormContainer />);

fillBasicInformation();

await user.click(screen.getByRole('button', { name: 'Tallenna ja keskeytä' }));

expect(window.location.pathname).toBe('/fi/hankesalkku/HAI22-13');
expect(screen.getByText(`Hanke ${nimi} (HAI22-13) tallennettu omiin hankkeisiin.`));
});

test('Should be able to save hanke in the last page', async () => {
const hanke = hankkeet[1];

const { user } = render(
<HankeForm
formData={hanke as HankeDataFormState}
onIsDirtyChange={() => ({})}
onFormClose={() => ({})}
>
children
</HankeForm>,
);

await user.click(screen.getByRole('button', { name: /yhteenveto/i }));
await user.click(screen.getByRole('button', { name: 'Tallenna' }));

expect(window.location.pathname).toBe(`/fi/hankesalkku/${hanke.hankeTunnus}`);
expect(
screen.getByText(`Hanke ${hanke.nimi} (${hanke.hankeTunnus}) tallennettu omiin hankkeisiin.`),
);
});
});
97 changes: 46 additions & 51 deletions src/domain/hanke/edit/HankeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,18 @@ import HankeFormHaitat from './HankeFormHaitat';
import HankeFormSummary from './HankeFormSummary';
import FormNotifications from './components/FormNotifications';
import './HankeForm.styles.scss';
import { HankeData, HANKE_SAVETYPE } from '../../types/hanke';
import { HankeData } from '../../types/hanke';
import { convertFormStateToHankeData } from './utils';
import api from '../../api/api';
import MultipageForm from '../../forms/MultipageForm';
import FormActions from '../../forms/components/FormActions';
import { useLocalizedRoutes } from '../../../common/hooks/useLocalizedRoutes';
import ApplicationAddDialog from '../../application/components/ApplicationAddDialog';
import { useGlobalNotification } from '../../../common/components/globalNotification/GlobalNotificationContext';

async function saveHanke({
data,
saveType = HANKE_SAVETYPE.DRAFT,
}: {
data: HankeDataFormState;
saveType?: HANKE_SAVETYPE;
navigateTo?: string;
}) {
if (!data.alueet?.length) {
return data;
}

async function saveHanke(data: HankeDataFormState) {
const requestData = {
...convertFormStateToHankeData(data),
saveType,
};

const response = data.hankeTunnus
Expand All @@ -62,6 +51,7 @@ const HankeForm: React.FC<React.PropsWithChildren<Props>> = ({
const { t } = useTranslation();
const { HANKEPORTFOLIO } = useLocalizedRoutes();
const navigate = useNavigate();
const { setNotification } = useGlobalNotification();
const [showNotification, setShowNotification] = useState<FormNotification | null>(null);
const [showAddApplicationDialog, setShowAddApplicationDialog] = useState(false);
const formContext = useForm<HankeDataFormState>({
Expand All @@ -76,15 +66,15 @@ const HankeForm: React.FC<React.PropsWithChildren<Props>> = ({

const {
register,
formState: { errors, isDirty, isValid },
formState: { errors, isDirty },
getValues,
setValue,
handleSubmit,
} = formContext;

const isNewHanke = !formData.hankeTunnus;

const formValues = getValues();
const isHankePublic = formValues.status === 'PUBLIC';
const formHeading = isNewHanke
? t('hankeForm:pageHeaderNew')
: t('hankeForm:pageHeaderEdit', { hankeTunnus: formData.hankeTunnus });
Expand All @@ -96,36 +86,41 @@ const HankeForm: React.FC<React.PropsWithChildren<Props>> = ({
onError() {
setShowNotification('error');
},
onSuccess(data, { navigateTo }) {
onSuccess(data) {
setValue('hankeTunnus', data.hankeTunnus);
setValue('tormaystarkasteluTulos', data.tormaystarkasteluTulos);
if (data.alueet) {
setShowNotification('success');
}
if (navigateTo) {
navigate(navigateTo);
}
setValue('status', data.status);
setShowNotification('success');
},
});

function saveDraft() {
hankeMutation.mutate({ data: getValues() });
}

function saveDraftAndQuit() {
hankeMutation.mutate({ data: getValues(), navigateTo: HANKEPORTFOLIO.path });
function save() {
hankeMutation.mutate(getValues());
}

function save() {
hankeMutation.mutate({
data: getValues(),
saveType: HANKE_SAVETYPE.SUBMIT,
navigateTo: HANKEPORTFOLIO.path,
function saveAndQuit() {
hankeMutation.mutate(getValues(), {
onSuccess(data) {
setNotification(true, {
position: 'top-right',
dismissible: true,
autoClose: true,
autoCloseDuration: 8000,
label: t('hankeForm:saveAndQuitSuccessHeader'),
message: t('hankeForm:saveAndQuitSuccessText', {
name: data.nimi,
hankeTunnus: data.hankeTunnus,
}),
type: 'success',
closeButtonLabelText: t('common:components:notification:closeButtonLabelText'),
});
navigate(`${HANKEPORTFOLIO.path}/${data.hankeTunnus}`);
},
});
}

function saveAndAddApplication() {
hankeMutation.mutate({ data: getValues(), saveType: HANKE_SAVETYPE.SUBMIT });
save();
setShowAddApplicationDialog(true);
}

Expand Down Expand Up @@ -174,12 +169,7 @@ const HankeForm: React.FC<React.PropsWithChildren<Props>> = ({
hanke={getValues() as HankeData}
/>
<div className="hankeForm">
<MultipageForm
heading={formHeading}
formSteps={formSteps}
onStepChange={saveDraft}
onSubmit={handleSubmit(save)}
>
<MultipageForm heading={formHeading} formSteps={formSteps} onStepChange={save}>
{function renderFormActions(activeStepIndex, handlePrevious, handleNext) {
const lastStep = activeStepIndex === formSteps.length - 1;
return (
Expand All @@ -202,26 +192,31 @@ const HankeForm: React.FC<React.PropsWithChildren<Props>> = ({
<Button
variant="supplementary"
iconLeft={<IconSaveDiskette aria-hidden="true" />}
onClick={saveDraftAndQuit}
onClick={saveAndQuit}
data-testid="save-form-btn"
isLoading={hankeMutation.isLoading}
loadingText={t('common:buttons:savingText')}
>
{t('hankeForm:saveDraftButton')}
</Button>
)}
{lastStep && (
<>
<Button
variant="secondary"
iconLeft={<IconPlusCircle aria-hidden />}
onClick={saveAndAddApplication}
disabled={!isValid}
>
{t('hankeForm:saveAndAddButton')}
</Button>
{isHankePublic && (
<Button
variant="secondary"
iconLeft={<IconPlusCircle aria-hidden />}
onClick={saveAndAddApplication}
>
{t('hankeForm:saveAndAddButton')}
</Button>
)}
<Button
variant="primary"
iconLeft={<IconSaveDiskette aria-hidden />}
type="submit"
onClick={saveAndQuit}
isLoading={hankeMutation.isLoading}
loadingText={t('common:buttons:savingText')}
>
{t('hankeForm:saveButton')}
</Button>
Expand Down
4 changes: 3 additions & 1 deletion src/domain/hanke/edit/components/ContactsSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const ContactSummary: React.FC<{ contact: HankeContact | HankeMuuTaho }> = ({ co
if (CONTACT_FORMFIELD.TYYPPI in contact) {
return (
<div style={{ marginBottom: 'var(--spacing-s)' }}>
<p>{t(`form:yhteystiedot:contactType:${contact.tyyppi}`)}</p>
{contact.tyyppi !== undefined && (
<p>{t(`form:yhteystiedot:contactType:${contact.tyyppi}`)}</p>
)}
<p>{contact.nimi}</p>
<p>{contact.ytunnus}</p>
<p>{contact.email}</p>
Expand Down
30 changes: 13 additions & 17 deletions src/domain/hanke/edit/components/HankeDraftStateNotification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Notification } from 'hds-react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { HankeData } from '../../../types/hanke';
import { useIsHankeValid } from '../hooks/useIsHankeValid';

type Props = {
/** Hanke data */
Expand All @@ -12,28 +11,25 @@ type Props = {
};

/**
* Show hanke draft state notification if there are missing required fields in hanke
* Show hanke draft state notification if it's status is DRAFT
*/
const HankeDraftStateNotification: React.FC<Props> = ({ hanke, className }) => {
const { t } = useTranslation();

// Check if hanke has all the required fields filled
const isHankeValid = useIsHankeValid(hanke);

if (isHankeValid) {
return null;
if (hanke.status === 'DRAFT') {
return (
<Notification
size="small"
label={t('hankePortfolio:draftStateLabel')}
className={className}
type="alert"
>
{t('hankePortfolio:draftState')}
</Notification>
);
}

return (
<Notification
size="small"
label={t('hankePortfolio:draftStateLabel')}
className={className}
type="alert"
>
{t('hankePortfolio:draftState')}
</Notification>
);
return null;
};

export default HankeDraftStateNotification;
2 changes: 1 addition & 1 deletion src/domain/hanke/hankeUsers/UserRightsCheck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function UserRightsCheck({
requiredRight: keyof typeof Rights;
/** hankeTunnus of the hanke that the right is required for */
hankeTunnus?: string;
children: React.ReactElement;
children: React.ReactElement | null;
}) {
const { data: signedInUser } = useUserRightsForHanke(hankeTunnus);

Expand Down
13 changes: 10 additions & 3 deletions src/domain/hanke/hankeView/HankeView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,20 @@ test('Draft state notification is rendered when hanke is in draft state', async
expect(draftStateElements[0]).toBeInTheDocument();
});

test('Add application and End hanke buttons are disabled when hanke is in draft state', async () => {
test('Add application button is displayed when hanke is in PUBLIC state', async () => {
render(<HankeViewContainer hankeTunnus="HAI22-2" />);

await waitForLoadingToFinish();

expect(screen.queryByRole('button', { name: /lisää hakemus/i })).toBeInTheDocument();
});

test('Add application button is hidden when hanke is not in PUBLIC state', async () => {
render(<HankeViewContainer hankeTunnus="HAI22-1" />);

await waitForLoadingToFinish();

expect(screen.getByRole('button', { name: /lisää hakemus/i })).toBeDisabled();
expect(screen.getByRole('button', { name: /päätä hanke/i })).toBeDisabled();
expect(screen.queryByRole('button', { name: /lisää hakemus/i })).not.toBeInTheDocument();
});

test('Draft state notification is not rendered when hanke is not in draft state', async () => {
Expand Down
Loading
Loading