From 2d9c08e6dc33bd03d390408131db0445e2c7e517 Mon Sep 17 00:00:00 2001 From: Sampo Tawast <5328394+sirtawast@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:34:26 +0200 Subject: [PATCH] feat: review application edit changes before submit (hl-1062) (#2838) * refactor: rename application api view for easier editor search * feat: application history to use text field (from varchar 100) * feat: update change reason historical record from payload * feat: add review for edit changes * refactor: add modal customizations to theme files * fix: add validation to paper application date * chore: e2e tests for handler edit/review * refactor: make date function more generic with parameter --- .../api/v1/{views.py => application_views.py} | 7 + ...oricalapplication_history_change_reason.py | 18 ++ backend/benefit/applications/models.py | 1 + .../tests/test_applications_api.py | 2 +- .../applications/tests/test_view_sets.py | 2 +- backend/benefit/helsinkibenefit/urls.py | 2 +- .../step2/utils/__tests__/dates.test.ts | 11 +- .../application/step2/utils/validation.ts | 6 +- .../handler/browser-tests/constants/forms.ts | 54 ++++++ .../pages/1-new-application.testcafe.ts | 37 +--- .../pages/2-edit-application.testcafe.ts | 57 ++---- .../pages/3-edit-review.testcafe.ts | 96 ++++++++++ frontend/benefit/handler/package.json | 1 + .../handler/public/locales/en/common.json | 101 +++++++++- .../handler/public/locales/fi/common.json | 101 +++++++++- .../handler/public/locales/sv/common.json | 101 +++++++++- .../applicationForm/ApplicationForm.tsx | 35 +++- .../actionBar/ActionBarEdit.tsx | 173 ++++++++++++++++++ .../{ActionBar.tsx => ActionBarNew.tsx} | 98 ++++------ .../formContent/FormContent.tsx | 6 +- .../attachmentsList/useAttachmentsList.ts | 6 +- .../companySection/CompanySection.tsx | 5 +- .../reviewChanges/ReviewEditChanges.sc.ts | 21 +++ .../reviewChanges/ReviewEditChanges.tsx | 142 ++++++++++++++ .../applicationForm/reviewChanges/utils.ts | 107 +++++++++++ .../applicationForm/useApplicationForm.ts | 163 +++++++++++------ .../applicationForm/utils/applicationForm.ts | 18 +- .../applicationForm/utils/validation.ts | 17 +- .../HandlingApplicationActions.tsx | 3 +- frontend/benefit/handler/src/constants.ts | 2 + .../handler/src/hooks/useFormActions.tsx | 31 ++-- .../src/hooks/useUpdateApplicationQuery.ts | 2 +- .../handler/src/types/application.d.ts | 9 +- frontend/benefit/shared/src/constants.ts | 4 +- .../benefit/shared/src/types/application.d.ts | 8 +- frontend/benefit/shared/src/utils/dates.ts | 25 ++- frontend/shared/src/styles/theme.ts | 8 + .../shared/src/types/styled-components.d.ts | 5 + frontend/yarn.lock | 5 + 39 files changed, 1227 insertions(+), 263 deletions(-) rename backend/benefit/applications/api/v1/{views.py => application_views.py} (98%) create mode 100644 backend/benefit/applications/migrations/0058_alter_historicalapplication_history_change_reason.py create mode 100644 frontend/benefit/handler/browser-tests/constants/forms.ts create mode 100644 frontend/benefit/handler/browser-tests/pages/3-edit-review.testcafe.ts create mode 100644 frontend/benefit/handler/src/components/applicationForm/actionBar/ActionBarEdit.tsx rename frontend/benefit/handler/src/components/applicationForm/actionBar/{ActionBar.tsx => ActionBarNew.tsx} (55%) create mode 100644 frontend/benefit/handler/src/components/applicationForm/reviewChanges/ReviewEditChanges.sc.ts create mode 100644 frontend/benefit/handler/src/components/applicationForm/reviewChanges/ReviewEditChanges.tsx create mode 100644 frontend/benefit/handler/src/components/applicationForm/reviewChanges/utils.ts diff --git a/backend/benefit/applications/api/v1/views.py b/backend/benefit/applications/api/v1/application_views.py similarity index 98% rename from backend/benefit/applications/api/v1/views.py rename to backend/benefit/applications/api/v1/application_views.py index 42ec226b41..0f15b7d91e 100755 --- a/backend/benefit/applications/api/v1/views.py +++ b/backend/benefit/applications/api/v1/application_views.py @@ -21,6 +21,7 @@ from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.response import Response from rest_framework.views import APIView +from simple_history.utils import update_change_reason from sql_util.aggregates import SubqueryCount from applications.api.v1.serializers.application import ( @@ -190,6 +191,12 @@ def perform_update(self, serializer): # Update the object. serializer.instance = self.get_queryset().get(pk=serializer.instance.pk) + # Update change reason if provided + if self.request.data.get("change_reason") and self.request.user.is_staff: + update_change_reason( + serializer.instance, str(self.request.data.get("change_reason")) + ) + @action(methods=["get"], detail=False, url_path="simplified_list") def simplified_application_list(self, request): """ diff --git a/backend/benefit/applications/migrations/0058_alter_historicalapplication_history_change_reason.py b/backend/benefit/applications/migrations/0058_alter_historicalapplication_history_change_reason.py new file mode 100644 index 0000000000..f9017a3c72 --- /dev/null +++ b/backend/benefit/applications/migrations/0058_alter_historicalapplication_history_change_reason.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.18 on 2024-01-24 09:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("applications", "0057_attachment_type_decision_text"), + ] + + operations = [ + migrations.AlterField( + model_name="historicalapplication", + name="history_change_reason", + field=models.TextField(null=True), + ), + ] diff --git a/backend/benefit/applications/models.py b/backend/benefit/applications/models.py index 5ab4bc4fbf..c82732acb4 100755 --- a/backend/benefit/applications/models.py +++ b/backend/benefit/applications/models.py @@ -362,6 +362,7 @@ def get_available_benefit_types(self): bases = models.ManyToManyField("ApplicationBasis", related_name="applications") history = HistoricalRecords( + history_change_reason_field=models.TextField(null=True), table_name="bf_applications_application_history", cascade_delete_history=True, ) diff --git a/backend/benefit/applications/tests/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index 9f772237b5..0c258f1051 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -16,6 +16,7 @@ from PIL import Image from rest_framework.reverse import reverse +from applications.api.v1.application_views import BaseApplicationViewSet from applications.api.v1.serializers.application import ( ApplicantApplicationSerializer, HandlerApplicationSerializer, @@ -24,7 +25,6 @@ from applications.api.v1.status_transition_validator import ( ApplicantApplicationStatusValidator, ) -from applications.api.v1.views import BaseApplicationViewSet from applications.enums import ( AhjoStatus, ApplicationBatchStatus, diff --git a/backend/benefit/applications/tests/test_view_sets.py b/backend/benefit/applications/tests/test_view_sets.py index 7db80cc902..d635e91b39 100644 --- a/backend/benefit/applications/tests/test_view_sets.py +++ b/backend/benefit/applications/tests/test_view_sets.py @@ -2,7 +2,7 @@ from django.contrib.auth.models import AnonymousUser from django.db.models import QuerySet -from applications.api.v1.views import ApplicantApplicationViewSet +from applications.api.v1.application_views import ApplicantApplicationViewSet from applications.models import Application diff --git a/backend/benefit/helsinkibenefit/urls.py b/backend/benefit/helsinkibenefit/urls.py index 106a735df1..d34d9bf7f9 100644 --- a/backend/benefit/helsinkibenefit/urls.py +++ b/backend/benefit/helsinkibenefit/urls.py @@ -11,7 +11,7 @@ ) from rest_framework_nested import routers -from applications.api.v1 import application_batch_views, views as application_views +from applications.api.v1 import application_batch_views, application_views from applications.api.v1.ahjo_decision_views import DecisionProposalTemplateSectionList from applications.api.v1.ahjo_integration_views import ( AhjoAttachmentView, diff --git a/frontend/benefit/applicant/src/components/applications/forms/application/step2/utils/__tests__/dates.test.ts b/frontend/benefit/applicant/src/components/applications/forms/application/step2/utils/__tests__/dates.test.ts index 4a368c41e5..cd6fdc8f41 100644 --- a/frontend/benefit/applicant/src/components/applications/forms/application/step2/utils/__tests__/dates.test.ts +++ b/frontend/benefit/applicant/src/components/applications/forms/application/step2/utils/__tests__/dates.test.ts @@ -1,7 +1,7 @@ import { getMaxEndDate, getMinEndDate, - validateDateWithinFourMonths, + validateDateWithinMonths, } from '@frontend/benefit-shared/src/utils/dates'; import { BENEFIT_TYPES } from 'benefit-shared/constants'; @@ -70,29 +70,28 @@ describe('dates', () => { const fourMonthsAgo = new Date(); fourMonthsAgo.setMonth(fourMonthsAgo.getMonth() - 4); - expect(validateDateWithinFourMonths(fourMonthsAgo)).toBe(true); + expect(validateDateWithinMonths(fourMonthsAgo, 4)).toBe(true); }); it('should return true when date is within four months', () => { const twoMonthsAgo = new Date(); twoMonthsAgo.setMonth(twoMonthsAgo.getMonth() - 2); - expect(validateDateWithinFourMonths(twoMonthsAgo)).toBe(true); + expect(validateDateWithinMonths(twoMonthsAgo, 4)).toBe(true); }); it('should return true when date is two months in the future', () => { const twoMonthsInTheFuture = new Date(); twoMonthsInTheFuture.setMonth(twoMonthsInTheFuture.getMonth() + 2); - expect(validateDateWithinFourMonths(twoMonthsInTheFuture)).toBe(true); + expect(validateDateWithinMonths(twoMonthsInTheFuture, 4)).toBe(true); }); - it('should return false when date is more than four months in the past', () => { const fiveMonthsAgo = new Date(); fiveMonthsAgo.setMonth(fiveMonthsAgo.getMonth() - 5); - expect(validateDateWithinFourMonths(fiveMonthsAgo)).toBe(false); + expect(validateDateWithinMonths(fiveMonthsAgo, 4)).toBe(false); }); }); }); diff --git a/frontend/benefit/applicant/src/components/applications/forms/application/step2/utils/validation.ts b/frontend/benefit/applicant/src/components/applications/forms/application/step2/utils/validation.ts index 6bf6da305e..237f0ea6c3 100644 --- a/frontend/benefit/applicant/src/components/applications/forms/application/step2/utils/validation.ts +++ b/frontend/benefit/applicant/src/components/applications/forms/application/step2/utils/validation.ts @@ -12,7 +12,7 @@ import { PAY_SUBSIDY_GRANTED, VALIDATION_MESSAGE_KEYS, } from 'benefit-shared/constants'; -import { validateDateWithinFourMonths } from 'benefit-shared/utils/dates'; +import { validateDateWithinMonths } from 'benefit-shared/utils/dates'; import subMonths from 'date-fns/subMonths'; import { FinnishSSN } from 'finnish-ssn'; import { TFunction } from 'next-i18next'; @@ -45,9 +45,9 @@ export const getValidationSchema = ( .required(t(VALIDATION_MESSAGE_KEYS.REQUIRED)) .test({ message: t(VALIDATION_MESSAGE_KEYS.DATE_MIN, { - min: convertToUIDateFormat(subMonths(new Date(), 4)), + min: convertToUIDateFormat(subMonths(new Date(), 6)), }), - test: (value = '') => validateDateWithinFourMonths(value), + test: (value = '') => validateDateWithinMonths(value, 6), }), [APPLICATION_FIELDS_STEP2_KEYS.END_DATE]: Yup.string().required( t(VALIDATION_MESSAGE_KEYS.REQUIRED) diff --git a/frontend/benefit/handler/browser-tests/constants/forms.ts b/frontend/benefit/handler/browser-tests/constants/forms.ts new file mode 100644 index 0000000000..74876fb1e8 --- /dev/null +++ b/frontend/benefit/handler/browser-tests/constants/forms.ts @@ -0,0 +1,54 @@ +export const NEW_FORM_DATA = { + company: { + id: '0201256-6', + bankAccountNumber: 'FI81 4975 4587 0004 02', + firstName: 'Kuura', + lastName: 'Massi-Päällikkö', + phone: '050 000 0000', + email: 'hki-benefit@example.com', + coOperationNegotiationsDescription: 'Lorem ipsum dolor sit amet', + }, + employee: { + firstName: 'Ruu', + lastName: 'Rättisitikka', + ssn: '050632-8912', + monthlyPay: '1800', + vacationMoney: '100', + otherExpenses: '300', + jobTitle: 'Verkkosamooja', + workingHours: '30', + collectiveBargainingAgreement: 'Yleinen TES', + }, + deminimis: { + granter: 'Valtio', + amount: '2000', + grantedAt: `1.1.${new Date().getFullYear()}`, + }, +}; + +export const EDIT_FORM_DATA = { + company: { + bankAccountNumber: 'FI65 4712 8581 0006 05', + firstName: 'Malla', + lastName: 'Jout-Sen', + phone: '040 123 4567', + email: 'yjdh-helsinkilisa@example.net', + coOperationNegotiationsDescription: 'Aenean fringilla lorem tellus', + }, + employee: { + firstName: 'Cool', + lastName: 'Kanerva', + ssn: '211081-2043', + monthlyPay: '1111', + vacationMoney: '222', + otherExpenses: '333', + jobTitle: 'Some-asiantuntija', + workingHours: '18', + collectiveBargainingAgreement: '-', + }, + deminimis: { + granter: 'Hyvän tekijät Inc.', + amount: '3000', + grantedAt: `3.3.${new Date().getFullYear() - 1}`, + }, +}; diff --git a/frontend/benefit/handler/browser-tests/pages/1-new-application.testcafe.ts b/frontend/benefit/handler/browser-tests/pages/1-new-application.testcafe.ts index 9df8211c1b..f3b9b928bb 100644 --- a/frontend/benefit/handler/browser-tests/pages/1-new-application.testcafe.ts +++ b/frontend/benefit/handler/browser-tests/pages/1-new-application.testcafe.ts @@ -4,38 +4,11 @@ import { format } from 'date-fns'; import { Selector } from 'testcafe'; import fi from '../../public/locales/fi/common.json'; +import { NEW_FORM_DATA as form } from '../constants/forms'; import MainIngress from '../page-model/MainIngress'; import handlerUser from '../utils/handlerUser'; import { getFrontendUrl } from '../utils/url.utils'; -const form = { - company: { - id: '0201256-6', - bankAccountNumber: 'FI81 4975 4587 0004 02', - firstName: 'Kuura', - lastName: 'Massi-Päällikkö', - phone: '050 000 0000', - email: 'hki-benefit@example.com', - coOperationNegotiationsDescription: 'Lorem ipsum dolor sit amet', - }, - employee: { - firstName: 'Ruu', - lastName: 'Rättisitikka', - ssn: '050632-8912', - monthlyPay: '1800', - vacationMoney: '100', - otherExpenses: '300', - jobTitle: 'Verkkosamooja', - workingHours: '30', - collectiveBargainingAgreement: 'Yleinen TES', - }, - deminimis: { - granter: 'Valtio', - amount: '2000', - grantedAt: `1.1.${new Date().getFullYear()}`, - }, -}; - const url = getFrontendUrl(`/`); const uploadFileAttachment = async ( @@ -48,7 +21,7 @@ const uploadFileAttachment = async ( await t.wait(100); }; -fixture('New application') +fixture('Create new application') .page(url) .beforeEach(async (t) => { clearDataToPrintOnFailure(t); @@ -127,9 +100,13 @@ test('Fill form and submit', async (t: TestController) => { ); await uploadFileAttachment(t, '#upload_attachment_full_application'); + await t.wait(1000); await uploadFileAttachment(t, '#upload_attachment_employment_contract'); - await uploadFileAttachment(t, '#upload_attachment_pay_subsidy_decision'); + await t.wait(1000); await uploadFileAttachment(t, '#upload_attachment_education_contract'); + await t.wait(1000); + await uploadFileAttachment(t, '#upload_attachment_pay_subsidy_decision'); + await t.wait(1000); /** * Click through all applicant terms. diff --git a/frontend/benefit/handler/browser-tests/pages/2-edit-application.testcafe.ts b/frontend/benefit/handler/browser-tests/pages/2-edit-application.testcafe.ts index ab112f510f..9a9f221b15 100644 --- a/frontend/benefit/handler/browser-tests/pages/2-edit-application.testcafe.ts +++ b/frontend/benefit/handler/browser-tests/pages/2-edit-application.testcafe.ts @@ -4,37 +4,11 @@ import { format } from 'date-fns'; import { Selector } from 'testcafe'; import fi from '../../public/locales/fi/common.json'; +import { EDIT_FORM_DATA as form, NEW_FORM_DATA } from '../constants/forms'; import MainIngress from '../page-model/MainIngress'; import handlerUser from '../utils/handlerUser'; import { getFrontendUrl } from '../utils/url.utils'; -const form = { - company: { - bankAccountNumber: 'FI81 4975 4587 0004 02', - firstName: 'Malla', - lastName: 'Jout-Sen', - phone: '040 123 4567', - email: 'yjdh-helsinkilisa@example.net', - coOperationNegotiationsDescription: 'Aenean fringilla lorem tellus', - }, - employee: { - firstName: 'Cool', - lastName: 'Kanerva', - ssn: '211081-2043', - monthlyPay: '1111', - vacationMoney: '222', - otherExpenses: '333', - jobTitle: 'Some-asiantuntija', - workingHours: '18', - collectiveBargainingAgreement: '-', - }, - deminimis: { - granter: 'Hyvän tekijät Inc.', - amount: '3000', - grantedAt: `3.3.${new Date().getFullYear() - 1}`, - }, -}; - const url = getFrontendUrl(`/`); const uploadFileAttachment = async ( @@ -52,12 +26,13 @@ const clearAndFill = async ( selector: string, value: string ) => { + await t.click(selector); await t.selectText(selector); await t.pressKey('delete'); await t.typeText(selector, value ?? ''); }; -fixture('New application') +fixture('Edit existing application') .page(url) .beforeEach(async (t) => { clearDataToPrintOnFailure(t); @@ -65,19 +40,21 @@ fixture('New application') await t.navigateTo('/'); }); -test('Fill form and submit', async (t: TestController) => { +test('Open form and edit fields, then submit', async (t: TestController) => { const mainIngress = new MainIngress(fi.mainIngress.heading, 'h1'); await mainIngress.isLoaded(); - // Open already created form in index page + // Open already created application in index page const applicationLink = Selector('td') - .withText('Ruu Rättisitikka') + .withText( + `${NEW_FORM_DATA.employee.firstName} ${NEW_FORM_DATA.employee.lastName}` + ) .sibling('td') .nth(0) .find('a'); await t.click(applicationLink); - // Start handling the application + // // Start handling the application const buttonSelector = 'main button'; const handleButton = Selector(buttonSelector).withText( fi.review.actions.handle @@ -181,13 +158,15 @@ test('Fill form and submit', async (t: TestController) => { await t.click(Selector('[name="application_consent_3"]')); // Validate form and submit - const nextButton = Selector(buttonSelector).withText( - fi.applications.actions.save + const validationButton = Selector(buttonSelector).withText( + fi.applications.actions.continue ); - await t.click(nextButton); + await t.click(validationButton); - const submitButton = Selector(buttonSelector).withText( - fi.review.actions.handlingPanel - ); - await t.expect(submitButton.visible).ok(); + await t.typeText('#changeReason', 'Testing edit application'); + + const submitButton = Selector('[data-testid="confirm-ok"]'); + await t.click(submitButton); + + await t.expect(editButton.visible).ok(); }); diff --git a/frontend/benefit/handler/browser-tests/pages/3-edit-review.testcafe.ts b/frontend/benefit/handler/browser-tests/pages/3-edit-review.testcafe.ts new file mode 100644 index 0000000000..47bc88415c --- /dev/null +++ b/frontend/benefit/handler/browser-tests/pages/3-edit-review.testcafe.ts @@ -0,0 +1,96 @@ +/* eslint-disable security/detect-non-literal-fs-filename */ +import { clearDataToPrintOnFailure } from '@frontend/shared/browser-tests/utils/testcafe.utils'; +import { Selector } from 'testcafe'; +import fi from '../../public/locales/fi/common.json'; + +import { EDIT_FORM_DATA as form } from '../constants/forms'; +import handlerUser from '../utils/handlerUser'; +import { getFrontendUrl } from '../utils/url.utils'; +import { friendlyFormatIBAN } from 'ibantools'; + +const url = getFrontendUrl(`/`); + +fixture('Review edited application') + .page(url) + .beforeEach(async (t) => { + clearDataToPrintOnFailure(t); + await t.useRole(handlerUser); + await t.navigateTo('/'); + }); + +const hasCompanyValuesInReview = async ( + t: TestController +): Promise => { + await t + .expect( + Selector('main').withText( + `${form.company.firstName} ${form.company.lastName}` + ).exists + ) + .ok(); + await t + .expect( + Selector('main').withText(form.company.phone.replace(/\s+/g, '')).exists + ) + .ok(); + await t.expect(Selector('main').withText(form.company.email).exists).ok(); + await t + .expect( + Selector('main').withText(form.company.coOperationNegotiationsDescription) + .exists + ) + .ok(); + await t + .expect( + Selector('main').withText( + String(friendlyFormatIBAN(form.company.bankAccountNumber)) + ).exists + ) + .ok(); + return true; +}; + +const hasEmployeeValuesInReview = async ( + t: TestController +): Promise => { + await t + .expect( + Selector('main').withText( + `${form.employee.firstName} ${form.employee.lastName}` + ).exists + ) + .ok(); + await t.expect(Selector('main').withText(form.employee.ssn).exists).ok(); + await t + .expect( + Selector('main').withText(form.employee.collectiveBargainingAgreement) + .exists + ) + .ok(); + await t.expect(Selector('main').withText(form.employee.jobTitle).exists).ok(); + await t + .expect( + Selector('main').withText( + fi.applications.sections.fields.paySubsidyGranted.notGranted + ).exists + ) + .ok(); + return true; +}; + +test('Edit done, verify changes', async (t: TestController) => { + // Open already created application in index page + + const applicationLink = Selector('td') + .withText(`${form.employee.firstName} ${form.employee.lastName}`) + .sibling('td') + .nth(0) + .find('a'); + + await t.expect(applicationLink.visible).ok(); + await t.click(applicationLink); + + // Validate form and submit + await hasCompanyValuesInReview(t); + await hasEmployeeValuesInReview(t); +}); diff --git a/frontend/benefit/handler/package.json b/frontend/benefit/handler/package.json index d8da6065ef..7b0aa10b47 100644 --- a/frontend/benefit/handler/package.json +++ b/frontend/benefit/handler/package.json @@ -21,6 +21,7 @@ "axios": "^1.6.5", "camelcase-keys": "^7.0.2", "date-fns": "^2.24.0", + "deep-diff": "^1.0.2", "dotenv": "^16.0.0", "finnish-business-ids": "^3.1.1", "finnish-ssn": "^2.1.2", diff --git a/frontend/benefit/handler/public/locales/en/common.json b/frontend/benefit/handler/public/locales/en/common.json index 48b425352a..e5237beb53 100644 --- a/frontend/benefit/handler/public/locales/en/common.json +++ b/frontend/benefit/handler/public/locales/en/common.json @@ -89,7 +89,7 @@ } }, "mainIngress": { - "heading": "Syötä hakemus", + "heading": "Hakemukset", "btnText": "Syötä hakemus" }, "footer": { @@ -570,6 +570,10 @@ "label": "Työllistettävä on allekirjoittanut hakemuksen", "yes": "Kyllä", "no": "Ei" + }, + "changeReason": { + "label": "Selvitys", + "placeholder": "Syy muokkaukseen" } }, "salaryExpensesExplanation": "Ilmoita bruttopalkka, sivukulut ja lomaraha euroina kuukaudessa.", @@ -648,6 +652,16 @@ }, "send": { "heading1": "Ehdot" + }, + "dialog": { + "edit": { + "heading": "Ilmoita syy muokkaukselle", + "text": { + "hasChanges": "Muokkasit seuraavia tietoja:", + "noChanges": "Ei muutoksia lomaketietoihin. Mikäli poistit tai lisäsit liitetiedostoja, voit kirjata niiden syyn." + }, + "helperText": "Kirjaa milloin ja mitä kautta hakija on ollut yhteydessä. Voit myös liittää tiedoston käydystä sähköpostikeskustelusta asiakkaan kanssa. Tiedot tulevat näkyviin hakemuksen muutoshistoriassa." + } } }, "review": { @@ -964,7 +978,8 @@ "select": "Valitse", "start": "Alkamispäivä", "end": "Päättymispäivä", - "perMonth": "/kk" + "perMonth": "/kk", + "empty": "Tyhjä" }, "status": { "draft": "Luonnos", @@ -1226,5 +1241,87 @@ }, "toast": { "closeToast": "Sulje ilmoitus" + }, + "changes": { + "fields": { + "jobTitle": { + "label": "Työllistettävän tehtävänimike" + }, + "monthlyPay": { + "label": "Bruttopalkka / kk" + }, + "vacationMoney": { + "label": "Lomaraha / kk" + }, + "otherExpenses": { + "label": "Sivukulut / kk" + }, + "workingHours": { + "label": "Työaika" + }, + "collectiveBargainingAgreement": { + "label": "Työehtosopimus" + }, + "companyBankAccountNumber": { + "label": "Tilinumero" + }, + "companyContactPersonFirstName": { + "label": "Yhteyshenkilön etunimi" + }, + "companyContactPersonLastName": { + "label": "Yhteyshenkilön sukunimi" + }, + "companyContactPersonPhoneNumber": { + "label": "Yhteyshenkilön puhelinnumero" + }, + "companyContactPersonEmail": { + "label": "Yhteyshenkilön sähköposti" + }, + "deMinimisAid": { + "label": "De minimis -tuet" + }, + "paperApplicationDate": { + "label": "Paperihakemuksen saapumispäivä" + }, + "firstName": { + "label": "Työllistettävän etunimi" + }, + "lastName": { + "label": "Työllistettävän sukunimi" + }, + "socialSecurityNumber": { + "label": "Työllistettävän henkilötunnus" + }, + "phoneNumber": { + "label": "Puhelin" + }, + "coOperationNegotiations":{ + "label": "Muutosneuvottelut" + }, + "coOperationNegotiationsDescription": { + "label": "Muutosneuvottelut, selvitys" + }, + "alternativeAddress": { + "label": "Toimitusosoite, johon päätös toimitetaan" + }, + "useAlternativeAddress": { + "label": "Käytä toista postiosoitetta" + }, + "alternativeCompanyStreetAddress": { + "label": "Osoite" + }, + "alternativeCompanyPostcode": { + "label": "Postinumero" + }, + "alternativeCompanyCity": { + "label": "Postitoimipaikka" + }, + "startDate": { + "label": "Työsuhde alkaa" + }, + "endDate": { + "label": "Työsuhde päättyy" + } + } } } diff --git a/frontend/benefit/handler/public/locales/fi/common.json b/frontend/benefit/handler/public/locales/fi/common.json index c44d7526bb..305295f4df 100644 --- a/frontend/benefit/handler/public/locales/fi/common.json +++ b/frontend/benefit/handler/public/locales/fi/common.json @@ -89,7 +89,7 @@ } }, "mainIngress": { - "heading": "Syötä hakemus", + "heading": "Hakemukset", "btnText": "Syötä hakemus" }, "footer": { @@ -570,6 +570,10 @@ "label": "Työllistettävä on allekirjoittanut hakemuksen", "yes": "Kyllä", "no": "Ei" + }, + "changeReason": { + "label": "Selvitys", + "placeholder": "Syy muokkaukseen" } }, "salaryExpensesExplanation": "Ilmoita bruttopalkka, sivukulut ja lomaraha euroina kuukaudessa.", @@ -648,6 +652,16 @@ }, "send": { "heading1": "Ehdot" + }, + "dialog": { + "edit": { + "heading": "Ilmoita syy muokkaukselle", + "text": { + "hasChanges": "Muokkasit seuraavia tietoja:", + "noChanges": "Ei muutoksia lomaketietoihin. Mikäli poistit tai lisäsit liitetiedostoja, voit kirjata niiden syyn." + }, + "helperText": "Kirjaa milloin ja mitä kautta hakija on ollut yhteydessä. Voit myös liittää tiedoston käydystä sähköpostikeskustelusta asiakkaan kanssa. Tiedot tulevat näkyviin hakemuksen muutoshistoriassa." + } } }, "review": { @@ -964,7 +978,8 @@ "select": "Valitse", "start": "Alkamispäivä", "end": "Päättymispäivä", - "perMonth": "/kk" + "perMonth": "/kk", + "empty": "Tyhjä" }, "status": { "draft": "Luonnos", @@ -1226,5 +1241,87 @@ }, "toast": { "closeToast": "Sulje ilmoitus" + }, + "changes": { + "fields": { + "jobTitle": { + "label": "Työllistettävän tehtävänimike" + }, + "monthlyPay": { + "label": "Bruttopalkka / kk" + }, + "vacationMoney": { + "label": "Lomaraha / kk" + }, + "otherExpenses": { + "label": "Sivukulut / kk" + }, + "workingHours": { + "label": "Työaika" + }, + "collectiveBargainingAgreement": { + "label": "Työehtosopimus" + }, + "companyBankAccountNumber": { + "label": "Tilinumero" + }, + "companyContactPersonFirstName": { + "label": "Yhteyshenkilön etunimi" + }, + "companyContactPersonLastName": { + "label": "Yhteyshenkilön sukunimi" + }, + "companyContactPersonPhoneNumber": { + "label": "Yhteyshenkilön puhelinnumero" + }, + "companyContactPersonEmail": { + "label": "Yhteyshenkilön sähköposti" + }, + "deMinimisAid": { + "label": "De minimis -tuet" + }, + "paperApplicationDate": { + "label": "Paperihakemuksen saapumispäivä" + }, + "firstName": { + "label": "Työllistettävän etunimi" + }, + "lastName": { + "label": "Työllistettävän sukunimi" + }, + "socialSecurityNumber": { + "label": "Työllistettävän henkilötunnus" + }, + "phoneNumber": { + "label": "Puhelin" + }, + "alternativeAddress": { + "label": "Toimitusosoite, johon päätös toimitetaan" + }, + "useAlternativeAddress": { + "label": "Käytä toista postiosoitetta" + }, + "alternativeCompanyStreetAddress": { + "label": "Osoite" + }, + "coOperationNegotiations":{ + "label": "Muutosneuvottelut" + }, + "coOperationNegotiationsDescription": { + "label": "Muutosneuvottelut, selvitys" + }, + "alternativeCompanyPostcode": { + "label": "Postinumero" + }, + "alternativeCompanyCity": { + "label": "Postitoimipaikka" + }, + "startDate": { + "label": "Työsuhde alkaa" + }, + "endDate": { + "label": "Työsuhde päättyy" + } + } } } diff --git a/frontend/benefit/handler/public/locales/sv/common.json b/frontend/benefit/handler/public/locales/sv/common.json index 48b425352a..e5237beb53 100644 --- a/frontend/benefit/handler/public/locales/sv/common.json +++ b/frontend/benefit/handler/public/locales/sv/common.json @@ -89,7 +89,7 @@ } }, "mainIngress": { - "heading": "Syötä hakemus", + "heading": "Hakemukset", "btnText": "Syötä hakemus" }, "footer": { @@ -570,6 +570,10 @@ "label": "Työllistettävä on allekirjoittanut hakemuksen", "yes": "Kyllä", "no": "Ei" + }, + "changeReason": { + "label": "Selvitys", + "placeholder": "Syy muokkaukseen" } }, "salaryExpensesExplanation": "Ilmoita bruttopalkka, sivukulut ja lomaraha euroina kuukaudessa.", @@ -648,6 +652,16 @@ }, "send": { "heading1": "Ehdot" + }, + "dialog": { + "edit": { + "heading": "Ilmoita syy muokkaukselle", + "text": { + "hasChanges": "Muokkasit seuraavia tietoja:", + "noChanges": "Ei muutoksia lomaketietoihin. Mikäli poistit tai lisäsit liitetiedostoja, voit kirjata niiden syyn." + }, + "helperText": "Kirjaa milloin ja mitä kautta hakija on ollut yhteydessä. Voit myös liittää tiedoston käydystä sähköpostikeskustelusta asiakkaan kanssa. Tiedot tulevat näkyviin hakemuksen muutoshistoriassa." + } } }, "review": { @@ -964,7 +978,8 @@ "select": "Valitse", "start": "Alkamispäivä", "end": "Päättymispäivä", - "perMonth": "/kk" + "perMonth": "/kk", + "empty": "Tyhjä" }, "status": { "draft": "Luonnos", @@ -1226,5 +1241,87 @@ }, "toast": { "closeToast": "Sulje ilmoitus" + }, + "changes": { + "fields": { + "jobTitle": { + "label": "Työllistettävän tehtävänimike" + }, + "monthlyPay": { + "label": "Bruttopalkka / kk" + }, + "vacationMoney": { + "label": "Lomaraha / kk" + }, + "otherExpenses": { + "label": "Sivukulut / kk" + }, + "workingHours": { + "label": "Työaika" + }, + "collectiveBargainingAgreement": { + "label": "Työehtosopimus" + }, + "companyBankAccountNumber": { + "label": "Tilinumero" + }, + "companyContactPersonFirstName": { + "label": "Yhteyshenkilön etunimi" + }, + "companyContactPersonLastName": { + "label": "Yhteyshenkilön sukunimi" + }, + "companyContactPersonPhoneNumber": { + "label": "Yhteyshenkilön puhelinnumero" + }, + "companyContactPersonEmail": { + "label": "Yhteyshenkilön sähköposti" + }, + "deMinimisAid": { + "label": "De minimis -tuet" + }, + "paperApplicationDate": { + "label": "Paperihakemuksen saapumispäivä" + }, + "firstName": { + "label": "Työllistettävän etunimi" + }, + "lastName": { + "label": "Työllistettävän sukunimi" + }, + "socialSecurityNumber": { + "label": "Työllistettävän henkilötunnus" + }, + "phoneNumber": { + "label": "Puhelin" + }, + "coOperationNegotiations":{ + "label": "Muutosneuvottelut" + }, + "coOperationNegotiationsDescription": { + "label": "Muutosneuvottelut, selvitys" + }, + "alternativeAddress": { + "label": "Toimitusosoite, johon päätös toimitetaan" + }, + "useAlternativeAddress": { + "label": "Käytä toista postiosoitetta" + }, + "alternativeCompanyStreetAddress": { + "label": "Osoite" + }, + "alternativeCompanyPostcode": { + "label": "Postinumero" + }, + "alternativeCompanyCity": { + "label": "Postitoimipaikka" + }, + "startDate": { + "label": "Työsuhde alkaa" + }, + "endDate": { + "label": "Työsuhde päättyy" + } + } } } diff --git a/frontend/benefit/handler/src/components/applicationForm/ApplicationForm.tsx b/frontend/benefit/handler/src/components/applicationForm/ApplicationForm.tsx index 826f0779ea..9bd4cc8273 100644 --- a/frontend/benefit/handler/src/components/applicationForm/ApplicationForm.tsx +++ b/frontend/benefit/handler/src/components/applicationForm/ApplicationForm.tsx @@ -18,7 +18,8 @@ import { import theme from 'shared/styles/theme'; import ApplicationHeader from '../applicationHeader/ApplicationHeader'; -import ActionBar from './actionBar/ActionBar'; +import ActionBarEdit from './actionBar/ActionBarEdit'; +import ActionBarNew from './actionBar/ActionBarNew'; import { $MainHeading, $SpinnerContainer } from './ApplicationForm.sc'; import CompanySearch from './companySearch/CompanySearch'; import FormContent from './formContent/FormContent'; @@ -41,6 +42,7 @@ const ApplicationForm: React.FC = () => { handleSubmit, handleSaveDraft, handleDelete, + handleValidation, showDeminimisSection, minEndDate, maxEndDate, @@ -53,6 +55,7 @@ const ApplicationForm: React.FC = () => { checkedConsentArray, getConsentErrorText, handleConsentClick, + initialApplication, } = useApplicationForm(); const { isFormActionEdit, isFormActionNew } = useApplicationFormContext(); @@ -149,7 +152,7 @@ const ApplicationForm: React.FC = () => { formik={formik} fields={fields} handleSave={handleSave} - handleQuietSave={handleQuietSave} + handleQuietSave={isFormActionNew ? handleQuietSave : null} showDeminimisSection={showDeminimisSection} minEndDate={minEndDate} maxEndDate={maxEndDate} @@ -160,12 +163,24 @@ const ApplicationForm: React.FC = () => { getConsentErrorText={getConsentErrorText} handleConsentClick={handleConsentClick} /> - + {isFormActionEdit && ( + + )} + {isFormActionNew && ( + + )} )} {stepState.activeStepIndex === 2 && isFormActionNew && ( @@ -175,7 +190,7 @@ const ApplicationForm: React.FC = () => { fields={fields} dispatchStep={dispatchStep} /> - { close={() => setIsConfirmationModalOpen(false)} closeButtonLabelText={t(`${translationsBase}.close`)} variant="primary" - theme={{ '--accent-line-color': 'var(--color-coat-of-arms)' }} + theme={theme.components.modal.coat} > void; + handleValidation: () => Promise; + id: string | string[] | undefined; + formik: FormikProps>; + fields: ApplicationFields; + initialApplication: Application; +}; + +const ActionBarEdit: React.FC = ({ + handleSave, + handleValidation, + id, + formik, + fields, + initialApplication, +}: ActionBarProps) => { + const { t } = useTranslation(); + const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false); + const translationsBase = 'common:applications.actions'; + const router = useRouter(); + + const { getErrorMessage } = useFormContent(formik, fields); + + const { isFormActionEdit, isFormActionNew } = useApplicationFormContext(); + + return ( + <> + <$Grid> + <$GridCell $colSpan={6}> + <$ButtonContainer> + {handleValidation && ( + + )} + {isFormActionEdit && ( + + )} + + + + + {isConfirmationModalOpen && handleSave && ( + setIsConfirmationModalOpen(false)} + handleSubmit={noop} + customContent={ + <> + + <$Grid> + <$GridCell $colSpan={12}> + +
+ + <$GridCell $colSpan={12}> +

{t(`common:applications.dialog.edit.helperText`)}

+