From 279d1d2e7b5fe72537ddf1660415e65a75e24125 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 26 May 2023 10:22:42 +0300 Subject: [PATCH 001/102] Add handler to a batch --- .../applications/api/v1/serializers/batch.py | 11 ++++++++ .../migrations/0032_auto_20230526_1010.py | 27 +++++++++++++++++++ backend/benefit/applications/models.py | 8 ++++++ 3 files changed, 46 insertions(+) create mode 100755 backend/benefit/applications/migrations/0032_auto_20230526_1010.py diff --git a/backend/benefit/applications/api/v1/serializers/batch.py b/backend/benefit/applications/api/v1/serializers/batch.py index 1f887fbc79..43e6a3d910 100755 --- a/backend/benefit/applications/api/v1/serializers/batch.py +++ b/backend/benefit/applications/api/v1/serializers/batch.py @@ -8,6 +8,7 @@ ) from applications.enums import ApplicationBatchStatus, ApplicationStatus from applications.models import Application, ApplicationBatch, Company, Employee +from users.api.v1.serializers import UserSerializer class ApplicationBatchSerializer(serializers.ModelSerializer): @@ -34,6 +35,14 @@ class ApplicationBatchSerializer(serializers.ModelSerializer): help_text="Proposed decision for Ahjo", ) + handler = UserSerializer( + help_text=( + "The handler object, with fields, currently assigned to this calculation" + " and application (read-only)" + ), + read_only=True, + ) + class Meta: model = ApplicationBatch fields = [ @@ -48,6 +57,7 @@ class Meta: "expert_inspector_name", "expert_inspector_email", "created_at", + "handler", ] read_only_fields = [ "created_at", @@ -165,6 +175,7 @@ class Meta: class BatchApplicationSerializer(ReadOnlySerializer): company = BatchCompanySerializer(read_only=True) employee = BatchEmployeeSerializer(read_only=True) + handled_at = serializers.SerializerMethodField( "get_handled_at", help_text=( diff --git a/backend/benefit/applications/migrations/0032_auto_20230526_1010.py b/backend/benefit/applications/migrations/0032_auto_20230526_1010.py new file mode 100755 index 0000000000..1e843226c8 --- /dev/null +++ b/backend/benefit/applications/migrations/0032_auto_20230526_1010.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.18 on 2023-05-26 07:10 + +import applications.models +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('applications', '0031_application_batches_expert_inspector_title'), + ] + + operations = [ + migrations.AddField( + model_name='applicationbatch', + name='handler', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='applicationbatch', + name='decision_date', + field=models.DateField(blank=True, null=True, validators=[applications.models.validate_decision_date], verbose_name='date of the decision in Ahjo'), + ), + ] diff --git a/backend/benefit/applications/models.py b/backend/benefit/applications/models.py index 98499be215..1cc51e1911 100755 --- a/backend/benefit/applications/models.py +++ b/backend/benefit/applications/models.py @@ -23,6 +23,7 @@ BatchCompletionRequiredFieldsError, BatchTooManyDraftsError, ) +from users.models import User from common.localized_iban_field import LocalizedIBANField from common.utils import DurationMixin from companies.models import Company @@ -476,6 +477,13 @@ class ApplicationBatch(UUIDModel, TimeStampedModel): * Transferring payment data to Talpa """ + handler = models.ForeignKey( + User, + on_delete=models.SET_NULL, + null=True, + blank=True, + ) + status = models.CharField( max_length=64, verbose_name=_("status of batch"), From ff2113c737f333f18cd1eca0a2d3ff6f8a96c01b Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 26 May 2023 11:09:11 +0300 Subject: [PATCH 002/102] Attach user to batch if it's created --- backend/benefit/applications/api/v1/application_batch_views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/benefit/applications/api/v1/application_batch_views.py b/backend/benefit/applications/api/v1/application_batch_views.py index fc0fbeeadd..bd3951f21a 100755 --- a/backend/benefit/applications/api/v1/application_batch_views.py +++ b/backend/benefit/applications/api/v1/application_batch_views.py @@ -191,7 +191,8 @@ def assign_applications(self, request): def create_application_batch_by_ids(app_status, apps): if apps: batch = ApplicationBatch.objects.create( - proposal_for_decision=app_status + proposal_for_decision=app_status, + handler=request.user ) return batch From a8e7188ab1d4374d866f2f34790bd93474802fff Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 26 May 2023 11:18:22 +0300 Subject: [PATCH 003/102] Delete batch if there's no applications left --- .../applications/api/v1/application_batch_views.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/benefit/applications/api/v1/application_batch_views.py b/backend/benefit/applications/api/v1/application_batch_views.py index bd3951f21a..5920a60b1d 100755 --- a/backend/benefit/applications/api/v1/application_batch_views.py +++ b/backend/benefit/applications/api/v1/application_batch_views.py @@ -240,16 +240,21 @@ def deassign_applications(self, request, pk=None): application_ids = request.data.get("application_ids") batch = self.get_batch(pk) - apps = Application.objects.filter( + apps_in_batch = Application.objects.filter(batch=batch) + + deassign_apps = apps_in_batch.filter( pk__in=application_ids, status__in=[ApplicationStatus.ACCEPTED, ApplicationStatus.REJECTED], - batch=batch, ) - if apps: - for app in apps: + + if deassign_apps: + for app in deassign_apps: app.batch = None app.save() + if len(apps_in_batch) == len(deassign_apps): + batch.delete() return Response(status=status.HTTP_200_OK) + return Response( {"detail": "Applications were not applicable to be detached."}, status=status.HTTP_404_NOT_FOUND, From 9f95dcf6f787dcd9e421273e6d807869a2edadf1 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Mon, 29 May 2023 14:51:35 +0300 Subject: [PATCH 004/102] Allow batch to be in AHJO_REPORT_CREATED state, include it to "two batches" limitation --- .../api/v1/application_batch_views.py | 25 ++++++++++++------- backend/benefit/applications/models.py | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/backend/benefit/applications/api/v1/application_batch_views.py b/backend/benefit/applications/api/v1/application_batch_views.py index 5920a60b1d..4c206119a0 100755 --- a/backend/benefit/applications/api/v1/application_batch_views.py +++ b/backend/benefit/applications/api/v1/application_batch_views.py @@ -210,16 +210,22 @@ def create_application_batch_by_ids(app_status, apps): ) # Try finding an existing batch - batch = ( - ApplicationBatch.objects.filter( - status=ApplicationBatchStatus.DRAFT, proposal_for_decision=app_status - ).first() - ) or create_application_batch_by_ids( - app_status, - apps, - ) + try: + batch = ( + ApplicationBatch.objects.filter( + status=ApplicationBatchStatus.DRAFT, proposal_for_decision=app_status + ).first() + ) or create_application_batch_by_ids( + app_status, + apps, + ) + except BatchTooManyDraftsError: + return Response( + {"errorKey": "batchInvalidDraftAlreadyExists"}, + status=status.HTTP_400_BAD_REQUEST, + ) - if batch: + if batch and batch.status == ApplicationBatchStatus.DRAFT: apps.update(batch=batch) batch = ApplicationBatchSerializer(batch) return Response(batch.data, status=status.HTTP_200_OK) @@ -270,6 +276,7 @@ def status(self, request, pk=None): batch = self.get_batch(pk) if new_status not in [ ApplicationBatchStatus.DRAFT, + ApplicationBatchStatus.AHJO_REPORT_CREATED, ApplicationBatchStatus.AWAITING_AHJO_DECISION, ApplicationBatchStatus.DECIDED_ACCEPTED, ApplicationBatchStatus.DECIDED_REJECTED, diff --git a/backend/benefit/applications/models.py b/backend/benefit/applications/models.py index 1cc51e1911..566d389c38 100755 --- a/backend/benefit/applications/models.py +++ b/backend/benefit/applications/models.py @@ -532,7 +532,7 @@ def _clean_one_draft_per_decision(self): and self.status == ApplicationBatchStatus.DRAFT ): drafts = ApplicationBatch.objects.filter( - status=self.status, proposal_for_decision=self.proposal_for_decision + status__in=[ApplicationBatchStatus.DRAFT, ApplicationBatchStatus.AHJO_REPORT_CREATED], proposal_for_decision=self.proposal_for_decision ).exclude(id=self.id) if len(drafts) > 0: raise BatchTooManyDraftsError( From 48ee3b9c1a7074b02bcb113b248592c6ebbe34d7 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Mon, 29 May 2023 14:52:05 +0300 Subject: [PATCH 005/102] Remove batch if it doesn't have applications --- .../applications/api/v1/application_batch_views.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/backend/benefit/applications/api/v1/application_batch_views.py b/backend/benefit/applications/api/v1/application_batch_views.py index 4c206119a0..8e2436a19e 100755 --- a/backend/benefit/applications/api/v1/application_batch_views.py +++ b/backend/benefit/applications/api/v1/application_batch_views.py @@ -246,19 +246,18 @@ def deassign_applications(self, request, pk=None): application_ids = request.data.get("application_ids") batch = self.get_batch(pk) - apps_in_batch = Application.objects.filter(batch=batch) - - deassign_apps = apps_in_batch.filter( + deassign_apps = Application.objects.filter( + batch=batch, pk__in=application_ids, status__in=[ApplicationStatus.ACCEPTED, ApplicationStatus.REJECTED], ) - if deassign_apps: for app in deassign_apps: app.batch = None app.save() - if len(apps_in_batch) == len(deassign_apps): - batch.delete() + remaining_apps = Application.objects.filter(batch=batch) + if len(remaining_apps) == 0: + batch.delete() return Response(status=status.HTTP_200_OK) return Response( From 0eedc6f43a2b42d423b9388d95cb9180357ab885 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Mon, 29 May 2023 15:00:08 +0300 Subject: [PATCH 006/102] Add new status state for batch before going in Ahjo --- .../handler/public/locales/fi/common.json | 2 + .../batchProcessing/BatchActionsToAhjo.tsx | 113 +++++++++++------- .../batchProcessing/BatchApplicationList.tsx | 12 +- .../batchProcessing/BatchProposals.tsx | 2 +- .../components/batchProcessing/useBatches.ts | 2 +- .../handler/src/hooks/useBatchQuery.ts | 8 +- .../handler/src/pages/batches/index.tsx | 9 +- frontend/benefit/shared/src/constants.ts | 1 + 8 files changed, 97 insertions(+), 52 deletions(-) diff --git a/frontend/benefit/handler/public/locales/fi/common.json b/frontend/benefit/handler/public/locales/fi/common.json index 80a6b95c17..3361c6097c 100644 --- a/frontend/benefit/handler/public/locales/fi/common.json +++ b/frontend/benefit/handler/public/locales/fi/common.json @@ -499,6 +499,7 @@ }, "registerToAhjo": { "awaiting_ahjo_decision": "Koonti merkitty Ahjoon viedyksi", + "exported_ahjo_report": "Ahjo-valmistelu aloitettu, koonti lukittu", "draft": "Koonti palautettu takaisin odottamaan päätösvalmistelua", "accepted": "Koonti lähetetty Talpaan ja arkistoitu", "rejected": "Koonti arkistoitu" @@ -511,6 +512,7 @@ } }, "actions": { + "markAsReadyForAhjo": "Aloita Ahjo-valmistelu", "markAsRegisteredToAhjo": "Merkitse Ahjoon viedyksi", "markedAsRegisteredToAhjo": "Viety Ahjoon", "markToPaymentAndArchive": "Merkitse maksuun ja arkistoi", diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx index da53ad5cfe..91b171934e 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx @@ -2,7 +2,7 @@ import useBatchStatus from 'benefit/handler/hooks/useBatchStatus'; import useDownloadBatchFiles from 'benefit/handler/hooks/useDownloadBatchFiles'; import { BATCH_STATUSES } from 'benefit-shared/constants'; import { BatchProposal } from 'benefit-shared/types/application'; -import { Button, IconCheckCircleFill, IconDownload } from 'hds-react'; +import { Button, IconCheckCircleFill, IconDownload, IconLock } from 'hds-react'; import { useTranslation } from 'next-i18next'; import React from 'react'; @@ -18,9 +18,12 @@ const BatchActionsCompletion: React.FC = ({ const { t } = useTranslation(); const { mutate: changeBatchStatus } = useBatchStatus(); - const { isLoading: isDownloading, mutate: downloadBatchFiles } = - useDownloadBatchFiles(); - const [isAtAhjo, setIsAtAhjo] = React.useState('primary'); + const { + isError: isDownloadError, + isLoading: isDownloading, + mutate: downloadBatchFiles, + } = useDownloadBatchFiles(); + const [isAtAhjo] = React.useState('primary'); const [isDownloadingAttachments, setIsDownloadingAttachments] = React.useState(false); @@ -29,6 +32,9 @@ const BatchActionsCompletion: React.FC = ({ if (!isDownloading) { setIsDownloadingAttachments(false); } + if (isDownloadError) { + setIsDownloadingAttachments(false); + } }, [isDownloading]); const handleDownloadBatchFiles = (): void => { @@ -36,50 +42,77 @@ const BatchActionsCompletion: React.FC = ({ downloadBatchFiles(batch.id); }; - const markBatchAs = (markBatchAsStatus: BATCH_STATUSES): void => + const handleBatchStatusChange = ( + status: + | BATCH_STATUSES.AWAITING_FOR_DECISION + | BATCH_STATUSES.AHJO_REPORT_CREATED + | BATCH_STATUSES.DRAFT + ): void => { changeBatchStatus({ id: batch.id, - status: markBatchAsStatus, + status, }); - - const handleBatchStatusChange = (): void => { - if (isAtAhjo === 'primary') { - changeBatchStatus({ - id: batch.id, - status: BATCH_STATUSES.AWAITING_FOR_DECISION, - }); - setIsAtAhjo('secondary'); - } else { - markBatchAs(BATCH_STATUSES.DRAFT); - setIsAtAhjo('primary'); + if (status === BATCH_STATUSES.AHJO_REPORT_CREATED) { + handleDownloadBatchFiles(); } }; return ( <> - - + {batch.status === BATCH_STATUSES.DRAFT ? ( + + ) : null} + + {batch.status === BATCH_STATUSES.AHJO_REPORT_CREATED ? ( + <> + + +
+ Lukittu + +
+ + ) : null} ); }; diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx index cb6c307738..d67c57f594 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx @@ -50,8 +50,13 @@ const BatchApplicationList: React.FC = ({ batch }: BatchProps) => { const applications = React.useMemo(() => apps, [apps]); - const IS_DRAFT = status === BATCH_STATUSES.DRAFT; - const [isCollapsed, setIsCollapsed] = React.useState(!IS_DRAFT); + const IS_WAITING_FOR_AHJO = [ + BATCH_STATUSES.DRAFT, + BATCH_STATUSES.AHJO_REPORT_CREATED, + ].includes(status); + const [isCollapsed, setIsCollapsed] = React.useState( + !IS_WAITING_FOR_AHJO + ); const { mutate: removeApp } = useRemoveAppFromBatch(); const handleAppRemoval = (appId: string): void => { @@ -96,12 +101,13 @@ const BatchApplicationList: React.FC = ({ batch }: BatchProps) => { }, { transform: ({ id: appId }: { id: string }) => - IS_DRAFT ? ( + IS_WAITING_FOR_AHJO ? ( diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchProposals.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchProposals.tsx index ef4b433613..a31746a52d 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchProposals.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchProposals.tsx @@ -8,7 +8,7 @@ import BatchApplicationList from './BatchApplicationList'; import { BatchListProps, useBatchProposal } from './useBatches'; type BatchProps = { - status: BATCH_STATUSES; + status: BATCH_STATUSES[]; }; const BatchProposals: React.FC = ({ status }: BatchProps) => { diff --git a/frontend/benefit/handler/src/components/batchProcessing/useBatches.ts b/frontend/benefit/handler/src/components/batchProcessing/useBatches.ts index 070cd03c5a..c682915923 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/useBatches.ts +++ b/frontend/benefit/handler/src/components/batchProcessing/useBatches.ts @@ -19,7 +19,7 @@ export interface BatchListProps { const translationsBase = 'common:applications.list'; -const useBatchProposal = (filterByStatus: BATCH_STATUSES): BatchListProps => { +const useBatchProposal = (filterByStatus: BATCH_STATUSES[]): BatchListProps => { const { t } = useTranslation(); const query = useBatchQuery(filterByStatus); diff --git a/frontend/benefit/handler/src/hooks/useBatchQuery.ts b/frontend/benefit/handler/src/hooks/useBatchQuery.ts index 63daba841e..50faf89816 100644 --- a/frontend/benefit/handler/src/hooks/useBatchQuery.ts +++ b/frontend/benefit/handler/src/hooks/useBatchQuery.ts @@ -7,7 +7,7 @@ import showErrorToast from 'shared/components/toast/show-error-toast'; import useBackendAPI from 'shared/hooks/useBackendAPI'; const useBatchQuery = ( - status: BATCH_STATUSES + status: BATCH_STATUSES[] ): UseQueryResult => { const { axios, handleResponse } = useBackendAPI(); const { t } = useTranslation(); @@ -19,10 +19,8 @@ const useBatchQuery = ( ); }; - const params: { - status: BATCH_STATUSES; - } = { - status, + const params = { + status: status.join(','), }; return useQuery( diff --git a/frontend/benefit/handler/src/pages/batches/index.tsx b/frontend/benefit/handler/src/pages/batches/index.tsx index 09265f9c1b..372c466b71 100644 --- a/frontend/benefit/handler/src/pages/batches/index.tsx +++ b/frontend/benefit/handler/src/pages/batches/index.tsx @@ -48,12 +48,17 @@ const BatchIndex: NextPage = () => { <$Heading>{t('common:batches.tabs.pending')} - + <$Heading>{t('common:batches.tabs.toPaymentAndArchive')} - + diff --git a/frontend/benefit/shared/src/constants.ts b/frontend/benefit/shared/src/constants.ts index c258b8d32a..d0035bad85 100644 --- a/frontend/benefit/shared/src/constants.ts +++ b/frontend/benefit/shared/src/constants.ts @@ -142,6 +142,7 @@ export enum APPLICATION_STATUSES { export enum BATCH_STATUSES { DRAFT = 'draft', AWAITING_FOR_DECISION = 'awaiting_ahjo_decision', + AHJO_REPORT_CREATED = 'exported_ahjo_report', DECIDED_ACCEPTED = 'accepted', DECIDED_REJECTED = 'rejected', SENT_TO_TALPA = 'sent_to_talpa', From 6f6c18979409b287b7729e792e39d2c257dcda13 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Mon, 29 May 2023 15:02:34 +0300 Subject: [PATCH 007/102] Invalidate react-query to update batch status change --- frontend/benefit/handler/src/hooks/useBatchStatus.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/benefit/handler/src/hooks/useBatchStatus.ts b/frontend/benefit/handler/src/hooks/useBatchStatus.ts index 7428093c11..e6cee1ef60 100644 --- a/frontend/benefit/handler/src/hooks/useBatchStatus.ts +++ b/frontend/benefit/handler/src/hooks/useBatchStatus.ts @@ -61,14 +61,13 @@ const useBatchStatus = (): UseMutationResult => { }, { onSuccess: ({ status: backendStatus }: Response) => { + setTimeout(() => { + void queryClient.invalidateQueries('applicationsList'); + }, 25); showSuccessToast( t(`common:batches.notifications.registerToAhjo.${backendStatus}`), '' ); - - if (backendStatus === BATCH_STATUSES.DRAFT) { - void queryClient.invalidateQueries('applicationsList'); - } }, onError: (e: BatchError) => handleError(e), } From 3c0e73c18eb0ac683d5d1be1cc41a3ea81b6f07f Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Mon, 29 May 2023 15:14:43 +0300 Subject: [PATCH 008/102] Failing batch downloads will toast a proper error --- frontend/benefit/handler/public/locales/en/common.json | 4 ++++ frontend/benefit/handler/public/locales/fi/common.json | 4 ++++ frontend/benefit/handler/public/locales/sv/common.json | 4 ++++ frontend/benefit/handler/src/hooks/useDownloadBatchFiles.ts | 4 ++-- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/benefit/handler/public/locales/en/common.json b/frontend/benefit/handler/public/locales/en/common.json index 36f2aabf2e..711915b602 100644 --- a/frontend/benefit/handler/public/locales/en/common.json +++ b/frontend/benefit/handler/public/locales/en/common.json @@ -507,6 +507,10 @@ "batchInvalidDraftAlreadyExists": { "title": "Uuden Ahjo-koonnin valmistelu on jo käynnissä", "message": "Voit palauttaa tämän koonnin takaisin käsittelyyn vasta, kun jo valmistelussa oleva keskeneräinen koonti on viety Ahjoon" + }, + "downloadError": { + "title": "Liitetiedostojen lataus epäonnistui", + "message": "Tekninen virhe. Yhden tai useamman hakemuksen tiedot saattavat olla puuttellisia." } } }, diff --git a/frontend/benefit/handler/public/locales/fi/common.json b/frontend/benefit/handler/public/locales/fi/common.json index 3361c6097c..8665d2fbd2 100644 --- a/frontend/benefit/handler/public/locales/fi/common.json +++ b/frontend/benefit/handler/public/locales/fi/common.json @@ -508,6 +508,10 @@ "batchInvalidDraftAlreadyExists": { "title": "Uuden Ahjo-koonnin valmistelu on jo käynnissä", "message": "Voit palauttaa tämän koonnin takaisin käsittelyyn vasta, kun jo valmistelussa oleva keskeneräinen koonti on viety Ahjoon" + }, + "downloadError": { + "title": "Liitetiedostojen lataus epäonnistui", + "message": "Tekninen virhe. Yhden tai useamman hakemuksen tiedot saattavat olla puuttellisia." } } }, diff --git a/frontend/benefit/handler/public/locales/sv/common.json b/frontend/benefit/handler/public/locales/sv/common.json index 162b473c0f..5feba9270c 100644 --- a/frontend/benefit/handler/public/locales/sv/common.json +++ b/frontend/benefit/handler/public/locales/sv/common.json @@ -507,6 +507,10 @@ "batchInvalidDraftAlreadyExists": { "title": "Uuden Ahjo-koonnin valmistelu on jo käynnissä", "message": "Voit palauttaa tämän koonnin takaisin käsittelyyn vasta, kun jo valmistelussa oleva keskeneräinen koonti on viety Ahjoon" + }, + "downloadError": { + "title": "Liitetiedostojen lataus epäonnistui", + "message": "Tekninen virhe. Yhden tai useamman hakemuksen tiedot saattavat olla puuttellisia." } } }, diff --git a/frontend/benefit/handler/src/hooks/useDownloadBatchFiles.ts b/frontend/benefit/handler/src/hooks/useDownloadBatchFiles.ts index 59558dd6b2..b2bd4322b5 100644 --- a/frontend/benefit/handler/src/hooks/useDownloadBatchFiles.ts +++ b/frontend/benefit/handler/src/hooks/useDownloadBatchFiles.ts @@ -18,8 +18,8 @@ const useDownloadBatchFiles = (): UseMutationResult< const handleError = (): void => { showErrorToast( - t('common:applications.list.errors.fetch.label'), - t('common:applications.list.errors.fetch.text', { status: 'error' }) + t('common:batches.notifications.errors.downloadError.title'), + t('common:batches.notifications.errors.downloadError.message') ); }; From 3f57c470463a2fb07da1e0ea545f59bfd34810f4 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Mon, 29 May 2023 15:17:36 +0300 Subject: [PATCH 009/102] Add handler name in batch listing --- .../components/batchProcessing/BatchApplicationList.tsx | 7 +++++++ frontend/benefit/shared/src/types/application.d.ts | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx index d67c57f594..4894ec70ec 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx @@ -46,6 +46,7 @@ const BatchApplicationList: React.FC = ({ batch }: BatchProps) => { created_at, applications: apps, proposal_for_decision: proposalForDecision, + handler, } = batch; const applications = React.useMemo(() => apps, [apps]); @@ -148,6 +149,12 @@ const BatchApplicationList: React.FC = ({ batch }: BatchProps) => {
{t('common:batches.single')}
{proposalForDecisionHeader()}
+
+
{t('common:batches.list.columns.handler')}
+
+ {handler?.first_name} {handler?.last_name} +
+
{t('common:batches.list.columns.createdAt')}
diff --git a/frontend/benefit/shared/src/types/application.d.ts b/frontend/benefit/shared/src/types/application.d.ts index 08a8a1bbbe..057a703ba0 100644 --- a/frontend/benefit/shared/src/types/application.d.ts +++ b/frontend/benefit/shared/src/types/application.d.ts @@ -34,6 +34,11 @@ export type BatchData = { created_at: string; }; +export type Handler = { + first_name: string; + last_name: string; +}; + export type BatchProposal = { id: string; status: BATCH_STATUSES; @@ -46,6 +51,7 @@ export type BatchProposal = { expert_inspector_name?: string; expert_inspector_email?: string; created_at: string; + handler?: Handler; }; export type ApplicationInBatch = { From 5723bf37204dfc6eae3e2f301ed741d830c28321 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Tue, 30 May 2023 08:34:17 +0300 Subject: [PATCH 010/102] Display appropriate error when application is added to a 'locked' batch --- .../batchProcessing/BatchActionsToAhjo.tsx | 18 +++++----- .../src/hooks/useApplicationToBatch.ts | 33 +++++++++++++++---- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx index 91b171934e..b948a82e0d 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx @@ -102,15 +102,15 @@ const BatchActionsCompletion: React.FC = ({ ? t('common:batches.actions.markAsRegisteredToAhjo') : t('common:batches.actions.markedAsRegisteredToAhjo')} -
- Lukittu - -
+ ) : null} diff --git a/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts b/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts index 73bc72ee49..62f1446ce7 100644 --- a/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts +++ b/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts @@ -1,3 +1,4 @@ +import { AxiosError, AxiosResponse } from 'axios'; import { HandlerEndpoint } from 'benefit-shared/backend-api/backend-api'; import { APPLICATION_STATUSES } from 'benefit-shared/constants'; import { useTranslation } from 'next-i18next'; @@ -11,16 +12,36 @@ interface Payload { status?: APPLICATION_STATUSES; } +interface BatchErrorResponse extends AxiosResponse { + status: 400; + data: { + errorKey: string; + }; +} + +interface BatchError extends AxiosError { + response: BatchErrorResponse; +} + const useAddToBatchQuery = (): UseMutationResult => { const { axios, handleResponse } = useBackendAPI(); const { t } = useTranslation(); const queryClient = useQueryClient(); - const handleError = (): void => { - showErrorToast( - t('common:applications.list.errors.fetch.label'), - t('common:applications.list.errors.fetch.text', { status: 'error' }) - ); + const handleError = (errorResponse: BatchError): void => { + if (errorResponse.response?.data?.errorKey) { + const { errorKey } = errorResponse.response.data; + showErrorToast( + t(`common:batches.notifications.errors.${errorKey}.title`), + t(`common:batches.notifications.errors.${errorKey}.message`) + ); + return; + } else { + showErrorToast( + t('common:applications.list.errors.fetch.label'), + t('common:applications.list.errors.fetch.text', { status: 'error' }) + ); + } }; return useMutation( @@ -42,7 +63,7 @@ const useAddToBatchQuery = (): UseMutationResult => { ); void queryClient.invalidateQueries('applicationsList'); }, - onError: () => handleError(), + onError: (error: BatchError) => handleError(error), } ); }; From 9cb38b17ff37e25ddac7c5f64c18a64c78e4375b Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Tue, 30 May 2023 15:12:07 +0300 Subject: [PATCH 011/102] feat: some small visual changes to archive page --- .../ApplicationsArchive.sc.ts | 3 +++ .../ApplicationsArchive.tsx | 27 +++++++++++++------ .../useApplicationsArchive.ts | 6 ++--- .../shared/src/utils/application.utils.ts | 5 ++++ 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/frontend/benefit/handler/src/components/applicationsArchive/ApplicationsArchive.sc.ts b/frontend/benefit/handler/src/components/applicationsArchive/ApplicationsArchive.sc.ts index 726ec8c312..06dd45dc0e 100644 --- a/frontend/benefit/handler/src/components/applicationsArchive/ApplicationsArchive.sc.ts +++ b/frontend/benefit/handler/src/components/applicationsArchive/ApplicationsArchive.sc.ts @@ -15,6 +15,9 @@ export const $ArchiveCount = styled.p` `; export const $Status = styled.p` + display: flex; + flex-flow: row wrap; + align-items: center; color: ${(props) => props.status === APPLICATION_STATUSES.CANCELLED ? props.theme.colors.error diff --git a/frontend/benefit/handler/src/components/applicationsArchive/ApplicationsArchive.tsx b/frontend/benefit/handler/src/components/applicationsArchive/ApplicationsArchive.tsx index 1a062996a0..5cf94d5364 100644 --- a/frontend/benefit/handler/src/components/applicationsArchive/ApplicationsArchive.tsx +++ b/frontend/benefit/handler/src/components/applicationsArchive/ApplicationsArchive.tsx @@ -1,6 +1,12 @@ import { APPLICATION_STATUSES } from 'benefit-shared/constants'; import Fuse from 'fuse.js'; -import { LoadingSpinner, Table, TextInput } from 'hds-react'; +import { + IconCheckCircleFill, + IconCrossCircleFill, + LoadingSpinner, + Table, + TextInput, +} from 'hds-react'; import * as React from 'react'; import Container from 'shared/components/container/Container'; import { $Link } from 'shared/components/table/Table.sc'; @@ -80,7 +86,15 @@ const ApplicationsArchive: React.FC = () => { { transform: ({ status }: TableTransforms) => ( <$Status status={status}> - {t(`${translationsBase}.columns.statuses.${status}`)?.toString()} + {status === APPLICATION_STATUSES.ACCEPTED ? ( + + ) : null} + {status === APPLICATION_STATUSES.REJECTED ? ( + + ) : null} + + {t(`${translationsBase}.columns.statuses.${status}`)?.toString()} + ), headerName: t(`${translationsBase}.columns.statusArchive`)?.toString(), @@ -95,9 +109,6 @@ const ApplicationsArchive: React.FC = () => { <$Heading as="h1">{`${t( 'common:header.navigation.archive' )}`} - <$ArchiveCount>{`${t(`${translationsBase}.total.count`, { - count: 0, - })}`} ); @@ -108,9 +119,6 @@ const ApplicationsArchive: React.FC = () => { <$Heading as="h1" data-testid="main-ingress">{`${t( 'common:header.navigation.archive' )}`} - <$ArchiveCount>{`${t(`${translationsBase}.total.count`, { - count: filteredList.length, - })}`} {!shouldHideList ? ( <> { value={filterValue} css="margin-bottom: var(--spacing-m);" /> + <$ArchiveCount>{`${t(`${translationsBase}.total.count`, { + count: filteredList.length, + })}`} { const { t } = useTranslation(); const query = useApplicationsQuery( ['accepted', 'rejected', 'cancelled'], - '-submitted_at', + '-handled_at', false, true ); @@ -50,7 +50,7 @@ const useApplicationsArchive = (): ApplicationListProps => { companyName: company ? company.name : '-', companyId: company ? company.business_id : '-', employeeName: - getFullName(employee?.first_name, employee?.last_name) || '-', + getFullNameListing(employee?.first_name, employee?.last_name) || '-', handledAt: convertToUIDateFormat(handled_at) || '-', dataReceived: getBatchDataReceived(status, batch?.created_at), applicationNum, diff --git a/frontend/shared/src/utils/application.utils.ts b/frontend/shared/src/utils/application.utils.ts index 97f0a99fc2..ccbee0ef05 100644 --- a/frontend/shared/src/utils/application.utils.ts +++ b/frontend/shared/src/utils/application.utils.ts @@ -31,3 +31,8 @@ export const getFullName = ( firstName: string | undefined, lastName: string | undefined ): string => [firstName, lastName].join(' ').trim(); + +export const getFullNameListing = ( + firstName: string | undefined, + lastName: string | undefined +): string => [lastName, firstName].join(', ').trim(); From 85b3a018252eef38991542fc6be004af9d50c9ec Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Tue, 30 May 2023 15:17:09 +0300 Subject: [PATCH 012/102] fix: batch handler name's was missing; fix closing tag --- .../benefit/applications/management/commands/seed.py | 1 + .../batchProcessing/BatchApplicationList.tsx | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/backend/benefit/applications/management/commands/seed.py b/backend/benefit/applications/management/commands/seed.py index 302c2b2c59..08941ba21e 100755 --- a/backend/benefit/applications/management/commands/seed.py +++ b/backend/benefit/applications/management/commands/seed.py @@ -85,6 +85,7 @@ def _create_batch( elif proposal_for_decision == ApplicationStatus.REJECTED: apps.append(RejectedApplicationFactory()) batch.applications.set(apps) + batch.handler = User.objects.filter(is_staff=True).last() batch.save() f = faker.Faker() diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx index 4894ec70ec..4d34efc4f6 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx @@ -149,12 +149,10 @@ const BatchApplicationList: React.FC = ({ batch }: BatchProps) => {
{t('common:batches.single')}
{proposalForDecisionHeader()}
-
-
{t('common:batches.list.columns.handler')}
-
- {handler?.first_name} {handler?.last_name} -
-
+
+
+
{t('common:batches.list.columns.handler')}
+
{handler?.first_name}
{t('common:batches.list.columns.createdAt')}
From dad590c89c2ba78048e741f240c1c81a4c5517b4 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Tue, 30 May 2023 15:50:30 +0300 Subject: [PATCH 013/102] feat: Display appropriate error message when adding apps to batch but it's already locked --- .../handler/public/locales/fi/common.json | 10 ++++++++-- .../handler/src/hooks/useApplicationToBatch.ts | 4 ++-- .../benefit/handler/src/hooks/useBatchStatus.ts | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/frontend/benefit/handler/public/locales/fi/common.json b/frontend/benefit/handler/public/locales/fi/common.json index 8665d2fbd2..74041d4351 100644 --- a/frontend/benefit/handler/public/locales/fi/common.json +++ b/frontend/benefit/handler/public/locales/fi/common.json @@ -506,8 +506,14 @@ }, "errors": { "batchInvalidDraftAlreadyExists": { - "title": "Uuden Ahjo-koonnin valmistelu on jo käynnissä", - "message": "Voit palauttaa tämän koonnin takaisin käsittelyyn vasta, kun jo valmistelussa oleva keskeneräinen koonti on viety Ahjoon" + "draft": { + "title": "Uuden Ahjo-koonnin valmistelu on jo käynnissä", + "message": "Voit palauttaa tämän koonnin takaisin käsittelyyn vasta, kun jo valmistelussa oleva keskeneräinen koonti on viety Ahjoon" + }, + "locked": { + "title": "Hakemuksia ei voitu siirtää koontiin", + "message": "Kollegasi on valmistelemassa koontia Ahjoon. Voit luoda uuden Ahjo-koonnin, kun hän on ensin viimeistellyt koonnin." + } }, "downloadError": { "title": "Liitetiedostojen lataus epäonnistui", diff --git a/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts b/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts index 62f1446ce7..3bb38f0ab0 100644 --- a/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts +++ b/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts @@ -32,8 +32,8 @@ const useAddToBatchQuery = (): UseMutationResult => { if (errorResponse.response?.data?.errorKey) { const { errorKey } = errorResponse.response.data; showErrorToast( - t(`common:batches.notifications.errors.${errorKey}.title`), - t(`common:batches.notifications.errors.${errorKey}.message`) + t(`common:batches.notifications.errors.${errorKey}.locked.title`), + t(`common:batches.notifications.errors.${errorKey}.locked.message`) ); return; } else { diff --git a/frontend/benefit/handler/src/hooks/useBatchStatus.ts b/frontend/benefit/handler/src/hooks/useBatchStatus.ts index e6cee1ef60..01803090d5 100644 --- a/frontend/benefit/handler/src/hooks/useBatchStatus.ts +++ b/frontend/benefit/handler/src/hooks/useBatchStatus.ts @@ -32,12 +32,19 @@ const useBatchStatus = (): UseMutationResult => { const { t } = useTranslation(); const queryClient = useQueryClient(); - const handleError = (errorResponse: BatchError): void => { + const handleError = ( + errorResponse: BatchError, + previousStatus: BATCH_STATUSES + ): void => { if (errorResponse.response?.data?.errorKey) { const { errorKey } = errorResponse.response.data; showErrorToast( - t(`common:batches.notifications.errors.${errorKey}.title`), - t(`common:batches.notifications.errors.${errorKey}.message`) + t( + `common:batches.notifications.errors.${errorKey}.${previousStatus}.title` + ), + t( + `common:batches.notifications.errors.${errorKey}.${previousStatus}.message` + ) ); return; } @@ -69,7 +76,8 @@ const useBatchStatus = (): UseMutationResult => { '' ); }, - onError: (e: BatchError) => handleError(e), + onError: (e: BatchError, { status: previousStatus }) => + handleError(e, previousStatus), } ); }; From 89d320d17325256d85971b149fb115c6c420e59c Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Tue, 30 May 2023 15:52:43 +0300 Subject: [PATCH 014/102] feat: Trash batch, use tooltip and toggle button to remove batch from being locked --- .../handler/public/locales/fi/common.json | 7 +- .../batchProcessing/BatchActionsToAhjo.tsx | 70 ++++++++++++++++--- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/frontend/benefit/handler/public/locales/fi/common.json b/frontend/benefit/handler/public/locales/fi/common.json index 74041d4351..2c6c74f60a 100644 --- a/frontend/benefit/handler/public/locales/fi/common.json +++ b/frontend/benefit/handler/public/locales/fi/common.json @@ -490,6 +490,9 @@ }, "empty": "Tyhjä koonti" }, + "tooltips": { + "lockedBatch": "{{ name }} vie parhaillaan hakemuksia Ahjoon. Koontin ei juuri nyt voi tehdä muutoksia. Odota hetki tai avaa lukko." + }, "notifications": { "addToBatch": { "success": { @@ -528,7 +531,9 @@ "markToPaymentAndArchive": "Merkitse maksuun ja arkistoi", "markToArchive": "Arkistoi", "markAsWaitingForAhjo": "Palauta odottamaan Ahjoon vientiä", - "downloadFiles": "Lataa liitteet" + "downloadFiles": "Lataa liitteet", + "deleteBatch": "Tyhjennä koonti", + "lockedBatch": "Lukittu" }, "form": { "fields": { diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx index b948a82e0d..530d251d01 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx @@ -2,9 +2,17 @@ import useBatchStatus from 'benefit/handler/hooks/useBatchStatus'; import useDownloadBatchFiles from 'benefit/handler/hooks/useDownloadBatchFiles'; import { BATCH_STATUSES } from 'benefit-shared/constants'; import { BatchProposal } from 'benefit-shared/types/application'; -import { Button, IconCheckCircleFill, IconDownload, IconLock } from 'hds-react'; +import { + Button, + IconCheckCircleFill, + IconCross, + IconDownload, + ToggleButton, + Tooltip, +} from 'hds-react'; import { useTranslation } from 'next-i18next'; import React from 'react'; +import useRemoveAppFromBatch from 'benefit/handler/hooks/useRemoveAppFromBatch'; type BatchProps = { batch: BatchProposal; @@ -23,7 +31,13 @@ const BatchActionsCompletion: React.FC = ({ isLoading: isDownloading, mutate: downloadBatchFiles, } = useDownloadBatchFiles(); + + const { mutate: removeApp } = useRemoveAppFromBatch(); + const [isAtAhjo] = React.useState('primary'); + const [isBatchLocked, setIsBatchLocked] = React.useState( + batch.status === BATCH_STATUSES.AHJO_REPORT_CREATED + ); const [isDownloadingAttachments, setIsDownloadingAttachments] = React.useState(false); @@ -52,11 +66,22 @@ const BatchActionsCompletion: React.FC = ({ id: batch.id, status, }); + if (status === BATCH_STATUSES.DRAFT) { + setIsBatchLocked(false); + } if (status === BATCH_STATUSES.AHJO_REPORT_CREATED) { handleDownloadBatchFiles(); + setIsBatchLocked(true); } }; + const handleBatchRemoval = (): void => { + const allApps = batch.applications.map((app) => app.id); + // eslint-disable-next-line no-alert + if (window.confirm(`Oletko varma, että haluat tyhjentää koonnin?`)) + removeApp({ appIds: allApps, batchId: batch.id }); + }; + return ( <> {batch.status === BATCH_STATUSES.DRAFT ? ( @@ -93,7 +118,6 @@ const BatchActionsCompletion: React.FC = ({ style={{ marginLeft: 'var(--spacing-s)' }} variant={isAtAhjo} iconLeft={isAtAhjo === 'secondary' ? : null} - className="table-custom-action" onClick={() => handleBatchStatusChange(BATCH_STATUSES.AWAITING_FOR_DECISION) } @@ -102,17 +126,41 @@ const BatchActionsCompletion: React.FC = ({ ? t('common:batches.actions.markAsRegisteredToAhjo') : t('common:batches.actions.markedAsRegisteredToAhjo')} - +
+ +
) : null} + ); }; From 099894a373be3d1b9c622623acbfed7413b09c16 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Thu, 1 Jun 2023 13:02:46 +0300 Subject: [PATCH 015/102] Remove deprecated sections from "reports" view --- .../applicationReports/ApplicationReports.tsx | 69 +------------------ 1 file changed, 2 insertions(+), 67 deletions(-) diff --git a/frontend/benefit/handler/src/components/applicationReports/ApplicationReports.tsx b/frontend/benefit/handler/src/components/applicationReports/ApplicationReports.tsx index e599887717..0382ef67ac 100644 --- a/frontend/benefit/handler/src/components/applicationReports/ApplicationReports.tsx +++ b/frontend/benefit/handler/src/components/applicationReports/ApplicationReports.tsx @@ -1,11 +1,8 @@ -import { EXPORT_APPLICATIONS_ROUTES } from 'benefit/handler/constants'; -import { PROPOSALS_FOR_DECISION } from 'benefit-shared/constants'; import { DateInput } from 'hds-react'; import * as React from 'react'; import Container from 'shared/components/container/Container'; import DateInputWithSeparator from 'shared/components/forms/fields/dateInputWithSeparator/DateInputWithSeparator'; import { $GridCell } from 'shared/components/forms/section/FormSection.sc'; -import ExportFileType from 'shared/types/export-file-type'; import { convertToUIDateFormat, getCorrectEndDate, @@ -16,74 +13,12 @@ import ReportsSection from './ReportsSection'; import { useApplicationReports } from './useApplicationReports'; const ApplicationReports: React.FC = () => { - const { - t, - translationsBase, - exportApplications, - formik, - fields, - exportApplicationsInTimeRange, - lastAcceptedApplicationsExportDate, - lastRejectedApplicationsExportDate, - } = useApplicationReports(); + const { t, translationsBase, formik, fields, exportApplicationsInTimeRange } = + useApplicationReports(); return ( <$Heading>{`${t(`${translationsBase}.headings.main`)}`} - - - exportApplications( - type, - EXPORT_APPLICATIONS_ROUTES.ACCEPTED, - PROPOSALS_FOR_DECISION.ACCEPTED - ) - } - header={`${t( - `${translationsBase}.headings.downloadAcceptedApplications` - )}`} - buttonText={`${t( - `${translationsBase}.buttons.downloadAcceptedApplications` - )}`} - withDivider - > - <$GridCell $colSpan={11}> -

{`${t( - `${translationsBase}.fields.lastDownloadDateText`, - { - date: lastAcceptedApplicationsExportDate, - } - )}`}

- -
- - exportApplications( - type, - EXPORT_APPLICATIONS_ROUTES.REJECTED, - PROPOSALS_FOR_DECISION.REJECTED - ) - } - header={`${t( - `${translationsBase}.headings.downloadRejectedApplications` - )}`} - buttonText={`${t( - `${translationsBase}.buttons.downloadRejectedApplications` - )}`} - withDivider - > - <$GridCell $colSpan={11}> -

{`${t( - `${translationsBase}.fields.lastDownloadDateText`, - { - date: lastRejectedApplicationsExportDate, - } - )}`}

- -
- Date: Fri, 2 Jun 2023 10:55:42 +0300 Subject: [PATCH 016/102] feat: expose previous status state to frontend --- backend/benefit/applications/api/v1/application_batch_views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/benefit/applications/api/v1/application_batch_views.py b/backend/benefit/applications/api/v1/application_batch_views.py index 8e2436a19e..06a78abbf6 100755 --- a/backend/benefit/applications/api/v1/application_batch_views.py +++ b/backend/benefit/applications/api/v1/application_batch_views.py @@ -293,6 +293,7 @@ def status(self, request, pk=None): for key in request.data: setattr(batch, key, request.data.get(key)) + previous_status = batch.status batch.status = new_status try: @@ -321,6 +322,7 @@ def status(self, request, pk=None): { "id": batch.id, "status": batch.status, + "previousStatus": previous_status, "decision": batch.proposal_for_decision, }, status=status.HTTP_200_OK, From 19e347083c468d39eb8ec409491f47e67e4a05d6 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 10:57:48 +0300 Subject: [PATCH 017/102] fix: lint backend files --- .../applications/api/v1/application_batch_views.py | 8 ++++---- backend/benefit/applications/models.py | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/benefit/applications/api/v1/application_batch_views.py b/backend/benefit/applications/api/v1/application_batch_views.py index 06a78abbf6..09cb266304 100755 --- a/backend/benefit/applications/api/v1/application_batch_views.py +++ b/backend/benefit/applications/api/v1/application_batch_views.py @@ -191,8 +191,7 @@ def assign_applications(self, request): def create_application_batch_by_ids(app_status, apps): if apps: batch = ApplicationBatch.objects.create( - proposal_for_decision=app_status, - handler=request.user + proposal_for_decision=app_status, handler=request.user ) return batch @@ -213,7 +212,8 @@ def create_application_batch_by_ids(app_status, apps): try: batch = ( ApplicationBatch.objects.filter( - status=ApplicationBatchStatus.DRAFT, proposal_for_decision=app_status + status=ApplicationBatchStatus.DRAFT, + proposal_for_decision=app_status, ).first() ) or create_application_batch_by_ids( app_status, @@ -257,7 +257,7 @@ def deassign_applications(self, request, pk=None): app.save() remaining_apps = Application.objects.filter(batch=batch) if len(remaining_apps) == 0: - batch.delete() + batch.delete() return Response(status=status.HTTP_200_OK) return Response( diff --git a/backend/benefit/applications/models.py b/backend/benefit/applications/models.py index 566d389c38..0123b33c57 100755 --- a/backend/benefit/applications/models.py +++ b/backend/benefit/applications/models.py @@ -23,11 +23,11 @@ BatchCompletionRequiredFieldsError, BatchTooManyDraftsError, ) -from users.models import User from common.localized_iban_field import LocalizedIBANField from common.utils import DurationMixin from companies.models import Company from shared.models.abstract_models import TimeStampedModel, UUIDModel +from users.models import User # todo: move to some better location? APPLICATION_LANGUAGE_CHOICES = ( @@ -532,7 +532,11 @@ def _clean_one_draft_per_decision(self): and self.status == ApplicationBatchStatus.DRAFT ): drafts = ApplicationBatch.objects.filter( - status__in=[ApplicationBatchStatus.DRAFT, ApplicationBatchStatus.AHJO_REPORT_CREATED], proposal_for_decision=self.proposal_for_decision + status__in=[ + ApplicationBatchStatus.DRAFT, + ApplicationBatchStatus.AHJO_REPORT_CREATED, + ], + proposal_for_decision=self.proposal_for_decision, ).exclude(id=self.id) if len(drafts) > 0: raise BatchTooManyDraftsError( From 59aa19f5a1710e002ceb3eafce33aefe1d2a92a1 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 11:54:06 +0300 Subject: [PATCH 018/102] feat: Transition batch with close animation when status changes --- .../BatchActionsCompletion.tsx | 5 +++- .../batchProcessing/BatchActionsToAhjo.tsx | 6 +++-- .../batchProcessing/BatchApplicationList.tsx | 2 ++ .../useBatchActionsCompletion.tsx | 9 +++++-- .../src/components/table/TableExtras.sc.ts | 13 +++++++++- .../handler/src/hooks/useBatchComplete.ts | 10 ++++++-- .../handler/src/hooks/useBatchStatus.ts | 25 +++++++++++++++---- .../src/hooks/useRemoveAppFromBatch.ts | 15 ++++++++--- 8 files changed, 69 insertions(+), 16 deletions(-) diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsCompletion.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsCompletion.tsx index 0d98736100..ee661ca263 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsCompletion.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsCompletion.tsx @@ -15,17 +15,20 @@ import { useBatchActionsCompletion } from './useBatchActionsCompletion'; type BatchProps = { batch: BatchProposal; + setBatchCloseAnimation: React.Dispatch>; }; const BatchActionsCompletion: React.FC = ({ batch, + setBatchCloseAnimation, }: BatchProps) => { const { id, proposal_for_decision: proposalForDecision } = batch; const { t } = useTranslation(); const [isSubmitted, setIsSubmitted] = React.useState(false); const { formik, yearFromNow, isSuccess, isError } = useBatchActionsCompletion( id, - proposalForDecision + proposalForDecision, + setBatchCloseAnimation ); const { mutate: changeBatchStatus } = useBatchStatus(); diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx index 530d251d01..839d694141 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx @@ -16,15 +16,17 @@ import useRemoveAppFromBatch from 'benefit/handler/hooks/useRemoveAppFromBatch'; type BatchProps = { batch: BatchProposal; + setBatchCloseAnimation: React.Dispatch>; }; type ButtonAhjoStates = 'primary' | 'secondary'; const BatchActionsCompletion: React.FC = ({ batch, + setBatchCloseAnimation, }: BatchProps) => { const { t } = useTranslation(); - const { mutate: changeBatchStatus } = useBatchStatus(); + const { mutate: changeBatchStatus } = useBatchStatus(setBatchCloseAnimation); const { isError: isDownloadError, @@ -32,7 +34,7 @@ const BatchActionsCompletion: React.FC = ({ mutate: downloadBatchFiles, } = useDownloadBatchFiles(); - const { mutate: removeApp } = useRemoveAppFromBatch(); + const { mutate: removeApp } = useRemoveAppFromBatch(setBatchCloseAnimation); const [isAtAhjo] = React.useState('primary'); const [isBatchLocked, setIsBatchLocked] = React.useState( diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx index 4d34efc4f6..0e9c6e4099 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx @@ -24,6 +24,7 @@ import { $HorizontalList, $TableBody, $TableFooter, + $TableGrid, $TableWrapper, } from '../table/TableExtras.sc'; import BatchActionsCompletion from './BatchActionsCompletion'; @@ -58,6 +59,7 @@ const BatchApplicationList: React.FC = ({ batch }: BatchProps) => { const [isCollapsed, setIsCollapsed] = React.useState( !IS_WAITING_FOR_AHJO ); + const [batchCloseAnimation, setBatchCloseAnimation] = React.useState(false); const { mutate: removeApp } = useRemoveAppFromBatch(); const handleAppRemoval = (appId: string): void => { diff --git a/frontend/benefit/handler/src/components/batchProcessing/useBatchActionsCompletion.tsx b/frontend/benefit/handler/src/components/batchProcessing/useBatchActionsCompletion.tsx index e56fd4191e..c8f4c82821 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/useBatchActionsCompletion.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/useBatchActionsCompletion.tsx @@ -30,10 +30,15 @@ interface ApplicationListProps { const useBatchActionsCompletion = ( id: string, - proposalForDecision: PROPOSALS_FOR_DECISION + proposalForDecision: PROPOSALS_FOR_DECISION, + setBatchCloseAnimation: React.Dispatch> ): ApplicationListProps => { const { t } = useTranslation(); - const { isSuccess, isError, mutate: completeBatch } = useBatchComplete(); + const { + isSuccess, + isError, + mutate: completeBatch, + } = useBatchComplete(setBatchCloseAnimation); const parseLocalizedDateString = ( _: string, diff --git a/frontend/benefit/handler/src/components/table/TableExtras.sc.ts b/frontend/benefit/handler/src/components/table/TableExtras.sc.ts index c184bd46f6..b750128675 100644 --- a/frontend/benefit/handler/src/components/table/TableExtras.sc.ts +++ b/frontend/benefit/handler/src/components/table/TableExtras.sc.ts @@ -49,9 +49,20 @@ export const $HorizontalList = styled.dl` } `; +type TableGridProps = { + animateClose: boolean; +}; +export const $TableGrid = styled.div` + display: grid; + transition: all 0.65s ease-out; + grid-template-rows: ${(props) => (props.animateClose ? '0fr' : '1fr')}; + opacity: ${(props) => (props.animateClose ? '0' : '1')}; + margin-bottom: ${(props) => (props.animateClose ? '0' : 'var(--spacing-l)')}; +`; + export const $TableWrapper = styled.div` + overflow-y: hidden; background: #fff; - margin-bottom: var(--spacing-l); `; export const $TableBody = styled.div` diff --git a/frontend/benefit/handler/src/hooks/useBatchComplete.ts b/frontend/benefit/handler/src/hooks/useBatchComplete.ts index bdae1e7639..c4a4a21b6a 100644 --- a/frontend/benefit/handler/src/hooks/useBatchComplete.ts +++ b/frontend/benefit/handler/src/hooks/useBatchComplete.ts @@ -31,12 +31,15 @@ type Response = { decision: PROPOSALS_FOR_DECISION; }; -const useBatchComplete = (): UseMutationResult => { +const useBatchComplete = ( + setBatchCloseAnimation: React.Dispatch> +): UseMutationResult => { const { axios, handleResponse } = useBackendAPI(); const { t } = useTranslation(); const queryClient = useQueryClient(); const handleError = (): void => { + setBatchCloseAnimation(false); showErrorToast( t('common:applications.list.errors.fetch.label'), t('common:applications.list.errors.fetch.text', { @@ -70,7 +73,10 @@ const useBatchComplete = (): UseMutationResult => { t(`common:batches.notifications.registerToAhjo.${backendStatus}`), '' ); - void queryClient.invalidateQueries('applicationsList'); + setBatchCloseAnimation(true); + setTimeout(() => { + void queryClient.invalidateQueries('applicationsList'); + }, 700); }, onError: () => handleError(), } diff --git a/frontend/benefit/handler/src/hooks/useBatchStatus.ts b/frontend/benefit/handler/src/hooks/useBatchStatus.ts index 01803090d5..02e2c9cd8c 100644 --- a/frontend/benefit/handler/src/hooks/useBatchStatus.ts +++ b/frontend/benefit/handler/src/hooks/useBatchStatus.ts @@ -14,6 +14,7 @@ type Payload = { type Response = { status: BATCH_STATUSES; + previousStatus: BATCH_STATUSES; }; interface BatchErrorResponse extends AxiosResponse { @@ -27,7 +28,11 @@ interface BatchError extends AxiosError { response: BatchErrorResponse; } -const useBatchStatus = (): UseMutationResult => { +type SetStateFn = React.Dispatch>; + +const useBatchStatus = ( + setBatchCloseAnimation?: SetStateFn +): UseMutationResult => { const { axios, handleResponse } = useBackendAPI(); const { t } = useTranslation(); const queryClient = useQueryClient(); @@ -36,6 +41,7 @@ const useBatchStatus = (): UseMutationResult => { errorResponse: BatchError, previousStatus: BATCH_STATUSES ): void => { + setBatchCloseAnimation(false); if (errorResponse.response?.data?.errorKey) { const { errorKey } = errorResponse.response.data; showErrorToast( @@ -67,14 +73,23 @@ const useBatchStatus = (): UseMutationResult => { return handleResponse(request); }, { - onSuccess: ({ status: backendStatus }: Response) => { - setTimeout(() => { - void queryClient.invalidateQueries('applicationsList'); - }, 25); + onSuccess: ({ status: backendStatus, previousStatus }: Response) => { showSuccessToast( t(`common:batches.notifications.registerToAhjo.${backendStatus}`), '' ); + if ( + previousStatus === BATCH_STATUSES.AWAITING_FOR_DECISION || + (previousStatus === BATCH_STATUSES.AHJO_REPORT_CREATED && + backendStatus === BATCH_STATUSES.AWAITING_FOR_DECISION) + ) { + setBatchCloseAnimation(true); + setTimeout(() => { + void queryClient.invalidateQueries('applicationsList'); + }, 700); + } else { + void queryClient.invalidateQueries('applicationsList'); + } }, onError: (e: BatchError, { status: previousStatus }) => handleError(e, previousStatus), diff --git a/frontend/benefit/handler/src/hooks/useRemoveAppFromBatch.ts b/frontend/benefit/handler/src/hooks/useRemoveAppFromBatch.ts index 94904dcd49..7a1ffa0519 100644 --- a/frontend/benefit/handler/src/hooks/useRemoveAppFromBatch.ts +++ b/frontend/benefit/handler/src/hooks/useRemoveAppFromBatch.ts @@ -1,6 +1,6 @@ import { HandlerEndpoint } from 'benefit-shared/backend-api/backend-api'; import { useTranslation } from 'next-i18next'; -import { useMutation, UseMutationResult } from 'react-query'; +import { useMutation, UseMutationResult, useQueryClient } from 'react-query'; import showErrorToast from 'shared/components/toast/show-error-toast'; import useBackendAPI from 'shared/hooks/useBackendAPI'; @@ -9,11 +9,15 @@ interface Payload { batchId?: string; } -const useRemoveAppFromBatch = (): UseMutationResult => { +const useRemoveAppFromBatch = ( + setBatchCloseAnimation: React.Dispatch> +): UseMutationResult => { const { axios, handleResponse } = useBackendAPI(); const { t } = useTranslation(); + const queryClient = useQueryClient(); const handleError = (): void => { + setBatchCloseAnimation(false); showErrorToast( t('common:applications.list.errors.fetch.label'), t('common:applications.list.errors.fetch.text', { status: 'error' }) @@ -29,7 +33,12 @@ const useRemoveAppFromBatch = (): UseMutationResult => { }) ), { - onSuccess: () => {}, + onSuccess: () => { + setBatchCloseAnimation(true); + setTimeout(() => { + void queryClient.invalidateQueries('applicationsList'); + }, 700); + }, onError: () => handleError(), } ); From 200d2fec79315872e3b82a30a18524884f60794d Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 11:59:11 +0300 Subject: [PATCH 019/102] feat: refetch batches after every 60 seconds --- frontend/benefit/handler/src/hooks/useBatchQuery.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/benefit/handler/src/hooks/useBatchQuery.ts b/frontend/benefit/handler/src/hooks/useBatchQuery.ts index 50faf89816..6383a38697 100644 --- a/frontend/benefit/handler/src/hooks/useBatchQuery.ts +++ b/frontend/benefit/handler/src/hooks/useBatchQuery.ts @@ -35,6 +35,7 @@ const useBatchQuery = ( return handleResponse(res); }, { + refetchInterval: 60 * 1000, onError: () => handleError(), } ); From d4a70bd7b96449d65ecd7aa65e976e00c0de866f Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 12:31:34 +0300 Subject: [PATCH 020/102] feat: refactor and add confirmation to all batch status changes --- .../actions/ConfirmModalContent/confirm.tsx | 76 +++++ .../BatchActionsCompletion.tsx | 301 +++++++++++------- .../batchProcessing/BatchActionsToAhjo.tsx | 205 ++++++++---- .../batchProcessing/BatchApplicationList.tsx | 158 +++++---- .../batchAction/BatchActions.sc.ts | 19 ++ .../src/hooks/useApplicationToBatch.ts | 1 - .../handler/src/hooks/useBatchComplete.ts | 1 - 7 files changed, 518 insertions(+), 243 deletions(-) create mode 100644 frontend/benefit/handler/src/components/applicationReview/actions/ConfirmModalContent/confirm.tsx create mode 100644 frontend/benefit/handler/src/components/batchProcessing/batchAction/BatchActions.sc.ts diff --git a/frontend/benefit/handler/src/components/applicationReview/actions/ConfirmModalContent/confirm.tsx b/frontend/benefit/handler/src/components/applicationReview/actions/ConfirmModalContent/confirm.tsx new file mode 100644 index 0000000000..edefc72ab2 --- /dev/null +++ b/frontend/benefit/handler/src/components/applicationReview/actions/ConfirmModalContent/confirm.tsx @@ -0,0 +1,76 @@ +import { Button, Dialog, DialogVariant } from 'hds-react'; +import { useTranslation } from 'next-i18next'; +import * as React from 'react'; +import { + $Grid, + $GridCell, +} from 'shared/components/forms/section/FormSection.sc'; + +type ComponentProps = { + onClose: () => void; + onSubmit: () => void; + text?: string; + heading?: string; + variant: DialogVariant; +}; + +/** + * Generic confirmation dialog content for shared/components/modal/Modal. + * Use when you need confirmation input from the user. + * @param {function} onClose - Function to be invoked when "cancel" is pressed + * @param {function} onSubmit - Function to be invoked when "confirm" is pressed + * @param {String} heading - Heading text for the dialog window + * @param {String} text - Body text for the dialog window + * @param {DialogVariant} variant - HDS DialogVariant to change dialog style + * @return {React.ReactElement} React element + * + */ +const ConfirmModalContent: React.FC = ({ + onClose, + onSubmit, + heading, + text, + variant = 'primary', +}) => { + const { t } = useTranslation(); + + return ( + <> + + <$Grid> + {heading ? ( + <$GridCell $colSpan={12} $rowSpan={3}> +

{heading}

+ + ) : null} + + {text ? ( + <$GridCell $colSpan={12}> +

{text}

+ + ) : null} + +
+ + + + + + ); +}; + +export default ConfirmModalContent; diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsCompletion.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsCompletion.tsx index ee661ca263..528ba4ec0d 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsCompletion.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsCompletion.tsx @@ -6,10 +6,13 @@ import { } from 'benefit-shared/constants'; import { BatchProposal } from 'benefit-shared/types/application'; import { Button, DateInput, IconArrowUndo, TextInput } from 'hds-react'; +import noop from 'lodash/noop'; import { useTranslation } from 'next-i18next'; import React, { useEffect } from 'react'; import { $GridCell } from 'shared/components/forms/section/FormSection.sc'; +import Modal from 'shared/components/modal/Modal'; +import ConfirmModalContent from '../applicationReview/actions/ConfirmModalContent/confirm'; import { $FormSection } from '../table/TableExtras.sc'; import { useBatchActionsCompletion } from './useBatchActionsCompletion'; @@ -30,10 +33,21 @@ const BatchActionsCompletion: React.FC = ({ proposalForDecision, setBatchCloseAnimation ); - const { mutate: changeBatchStatus } = useBatchStatus(); - const handleBatchStatusChange = (): void => + const { mutate: changeBatchStatus } = useBatchStatus(setBatchCloseAnimation); + const [isModalBatchToDraft, setModalBatchToDraft] = React.useState(false); + const [isModalBatchToTalpa, setModalBatchToTalpa] = React.useState(false); + + const handleModalClose = (): void => { + setModalBatchToDraft(false); + setModalBatchToTalpa(false); + setIsSubmitted(false); + }; + + const handleBatchStatusChange = (): void => { changeBatchStatus({ id, status: BATCH_STATUSES.DRAFT }); + setModalBatchToDraft(false); + }; useEffect(() => { if (isSuccess || isError) { @@ -44,6 +58,20 @@ const BatchActionsCompletion: React.FC = ({ const getErrorMessage = (fieldName: string): string | undefined => getErrorText(formik.errors, formik.touched, fieldName, t, true); + const handleBatchToTalpa = async (): Promise => + formik + .submitForm() + .then(() => { + setModalBatchToTalpa(false); + setIsSubmitted(true); + return true; + }) + .catch(() => { + setModalBatchToTalpa(false); + setIsSubmitted(false); + return false; + }); + const handleSubmit = (e: React.FormEvent): void => { e.preventDefault(); formik @@ -53,124 +81,165 @@ const BatchActionsCompletion: React.FC = ({ return null; } setIsSubmitted(true); - return formik.submitForm(); + setModalBatchToTalpa(true); + return true; }) - .catch(() => setIsSubmitted(false)); + .catch(() => { + setModalBatchToTalpa(false); + setIsSubmitted(false); + }); }; return ( -
- <$FormSection> - <$GridCell $colSpan={3}> - - - - <$GridCell $colSpan={3}> - - - - <$GridCell $colSpan={2}> - - - - <$GridCell $colSpan={3}> - formik.setFieldValue('decision_date', value)} - label={t('common:batches.form.fields.decisionDate')} - id={`decision_date${id}`} - name="decision_date" - errorText={getErrorMessage('decision_date')} - disableConfirmation - invalid={!!formik.errors.decision_date} - value={String(formik.values.decision_date)} - language="fi" - required - /> - - - - <$FormSection> - <$GridCell $colSpan={3}> - - - - <$GridCell $colSpan={3}> - - - - - <$FormSection> - <$GridCell $colSpan={3}> - - - <$GridCell $colSpan={4} alignSelf="end"> - - - - + <> + + {isModalBatchToDraft ? ( + + ) : null} + {isModalBatchToTalpa ? ( + void handleBatchToTalpa()} + /> + ) : null} + + } + /> +
+ <$FormSection> + <$GridCell $colSpan={3}> + + + + <$GridCell $colSpan={3}> + + + + <$GridCell $colSpan={2}> + + + + <$GridCell $colSpan={3}> + formik.setFieldValue('decision_date', value)} + label={t('common:batches.form.fields.decisionDate')} + id={`decision_date${id}`} + name="decision_date" + errorText={getErrorMessage('decision_date')} + disableConfirmation + invalid={!!formik.errors.decision_date} + value={String(formik.values.decision_date)} + language="fi" + required + /> + + + + <$FormSection> + <$GridCell $colSpan={3}> + + + + <$GridCell $colSpan={3}> + + + + + <$FormSection> + <$GridCell $colSpan={3}> + + + <$GridCell $colSpan={4} alignSelf="end"> + + + + + ); }; diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx index 839d694141..ae4e9bf338 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchActionsToAhjo.tsx @@ -1,18 +1,25 @@ import useBatchStatus from 'benefit/handler/hooks/useBatchStatus'; import useDownloadBatchFiles from 'benefit/handler/hooks/useDownloadBatchFiles'; +import useRemoveAppFromBatch from 'benefit/handler/hooks/useRemoveAppFromBatch'; import { BATCH_STATUSES } from 'benefit-shared/constants'; import { BatchProposal } from 'benefit-shared/types/application'; import { Button, + DialogVariant, IconCheckCircleFill, IconCross, IconDownload, ToggleButton, Tooltip, } from 'hds-react'; +import noop from 'lodash/noop'; import { useTranslation } from 'next-i18next'; import React from 'react'; -import useRemoveAppFromBatch from 'benefit/handler/hooks/useRemoveAppFromBatch'; +import Modal from 'shared/components/modal/Modal'; +import { useTheme } from 'styled-components'; + +import ConfirmModalContent from '../applicationReview/actions/ConfirmModalContent/confirm'; +import { $TooltipWrapper } from './batchAction/BatchActions.sc'; type BatchProps = { batch: BatchProposal; @@ -25,6 +32,7 @@ const BatchActionsCompletion: React.FC = ({ batch, setBatchCloseAnimation, }: BatchProps) => { + const { id, status, applications, handler } = batch; const { t } = useTranslation(); const { mutate: changeBatchStatus } = useBatchStatus(setBatchCloseAnimation); @@ -38,12 +46,24 @@ const BatchActionsCompletion: React.FC = ({ const [isAtAhjo] = React.useState('primary'); const [isBatchLocked, setIsBatchLocked] = React.useState( - batch.status === BATCH_STATUSES.AHJO_REPORT_CREATED + status === BATCH_STATUSES.AHJO_REPORT_CREATED ); + const theme = useTheme(); + const [isDownloadingAttachments, setIsDownloadingAttachments] = React.useState(false); + const [isModalBatchRemoval, setModalBatchRemoval] = React.useState(false); + const [isModalBatchToDraft, setModalBatchToDraft] = React.useState(false); + const [isModalBatchToAhjo, setModalBatchToAhjo] = React.useState(false); + + const handleModalClose = (): void => { + setModalBatchRemoval(false); + setModalBatchToDraft(false); + setModalBatchToAhjo(false); + }; + React.useEffect(() => { if (!isDownloading) { setIsDownloadingAttachments(false); @@ -51,47 +71,104 @@ const BatchActionsCompletion: React.FC = ({ if (isDownloadError) { setIsDownloadingAttachments(false); } - }, [isDownloading]); + }, [isDownloading, isDownloadError]); const handleDownloadBatchFiles = (): void => { setIsDownloadingAttachments(true); - downloadBatchFiles(batch.id); + downloadBatchFiles(id); + }; + + const variantForAction = (): DialogVariant => { + if (isModalBatchRemoval) { + return 'danger'; + } + return 'primary'; }; const handleBatchStatusChange = ( - status: + newStatus: | BATCH_STATUSES.AWAITING_FOR_DECISION | BATCH_STATUSES.AHJO_REPORT_CREATED | BATCH_STATUSES.DRAFT ): void => { changeBatchStatus({ - id: batch.id, - status, + id, + status: newStatus, }); - if (status === BATCH_STATUSES.DRAFT) { + if (newStatus === BATCH_STATUSES.DRAFT) { setIsBatchLocked(false); } - if (status === BATCH_STATUSES.AHJO_REPORT_CREATED) { + if (newStatus === BATCH_STATUSES.AHJO_REPORT_CREATED) { handleDownloadBatchFiles(); setIsBatchLocked(true); } + handleModalClose(); }; const handleBatchRemoval = (): void => { - const allApps = batch.applications.map((app) => app.id); - // eslint-disable-next-line no-alert - if (window.confirm(`Oletko varma, että haluat tyhjentää koonnin?`)) - removeApp({ appIds: allApps, batchId: batch.id }); + const allApps = applications.map((app) => app.id); + removeApp({ appIds: allApps, batchId: id }); + setModalBatchRemoval(false); }; return ( <> - {batch.status === BATCH_STATUSES.DRAFT ? ( + + {isModalBatchRemoval ? ( + + ) : null} + {isModalBatchToDraft ? ( + handleBatchStatusChange(BATCH_STATUSES.DRAFT)} + /> + ) : null} + {isModalBatchToAhjo ? ( + + handleBatchStatusChange(BATCH_STATUSES.AWAITING_FOR_DECISION) + } + /> + ) : null} + + } + /> + + {status === BATCH_STATUSES.DRAFT ? ( ) : null} - {batch.status === BATCH_STATUSES.AHJO_REPORT_CREATED ? ( - <> - - -
- -
- + {status === BATCH_STATUSES.AHJO_REPORT_CREATED ? ( + ) : null} + + <$TooltipWrapper disabled={status === BATCH_STATUSES.DRAFT}> + + + diff --git a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx index 0e9c6e4099..d1ce0a5555 100644 --- a/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx +++ b/frontend/benefit/handler/src/components/batchProcessing/BatchApplicationList.tsx @@ -3,7 +3,10 @@ import { BATCH_STATUSES, PROPOSALS_FOR_DECISION, } from 'benefit-shared/constants'; -import { BatchProposal } from 'benefit-shared/types/application'; +import { + ApplicationInBatch, + BatchProposal, +} from 'benefit-shared/types/application'; import { Button, IconAngleDown, @@ -13,13 +16,16 @@ import { IconCrossCircleFill, Table, } from 'hds-react'; +import noop from 'lodash/noop'; import { useTranslation } from 'next-i18next'; import React from 'react'; +import Modal from 'shared/components/modal/Modal'; import theme from 'shared/styles/theme'; import { convertToUIDateAndTimeFormat } from 'shared/utils/date.utils'; import styled from 'styled-components'; import { $Empty } from '../applicationList/ApplicationList.sc'; +import ConfirmModalContent from '../applicationReview/actions/ConfirmModalContent/confirm'; import { $HorizontalList, $TableBody, @@ -59,19 +65,22 @@ const BatchApplicationList: React.FC = ({ batch }: BatchProps) => { const [isCollapsed, setIsCollapsed] = React.useState( !IS_WAITING_FOR_AHJO ); + const [isConfirmAppRemoval, setConfirmAppRemoval] = React.useState(false); + const [appToRemove, setAppToRemove] = + React.useState(null); const [batchCloseAnimation, setBatchCloseAnimation] = React.useState(false); - const { mutate: removeApp } = useRemoveAppFromBatch(); - const handleAppRemoval = (appId: string): void => { + const { mutate: removeApp } = useRemoveAppFromBatch(setBatchCloseAnimation); + const openAppRemovalDialog = (appId: string): void => { const selectedApp = apps.find((app) => app.id === appId); - if ( - // eslint-disable-next-line no-alert - window.confirm( - `Ota hakemus ${selectedApp.application_number} pois koonnista?` - ) - ) { - removeApp({ appIds: [selectedApp.id], batchId: id }); - } + setAppToRemove(selectedApp); + setConfirmAppRemoval(true); + }; + + const onAppRemovalSubmit = (): void => { + removeApp({ appIds: [appToRemove.id], batchId: id }); + setConfirmAppRemoval(false); + setAppToRemove(null); }; const cols = [ @@ -109,7 +118,7 @@ const BatchApplicationList: React.FC = ({ batch }: BatchProps) => { theme="black" variant="supplementary" iconLeft={} - onClick={() => handleAppRemoval(appId)} + onClick={() => openAppRemovalDialog(appId)} disabled={status !== BATCH_STATUSES.DRAFT} > {' '} @@ -146,52 +155,85 @@ const BatchApplicationList: React.FC = ({ batch }: BatchProps) => { }; return ( - <$TableWrapper> - <$HorizontalList> -
-
{t('common:batches.single')}
-
{proposalForDecisionHeader()}
-
-
-
{t('common:batches.list.columns.handler')}
-
{handler?.first_name}
-
-
-
{t('common:batches.list.columns.createdAt')}
-
{convertToUIDateAndTimeFormat(created_at)}
-
-
- {applications.length > 0 ? ( - - ) : null} -
- - {applications?.length ? ( - <$TableBody isCollapsed={isCollapsed} aria-hidden={isCollapsed}> -
- <$TableFooter> - {status === BATCH_STATUSES.AWAITING_FOR_DECISION ? ( - - ) : ( - - )} - - - ) : ( - <$Empty css="margin: var(--spacing-s) 0;"> - {t('common:batches.list.empty')} - - )} - + <$TableGrid animateClose={batchCloseAnimation}> + <$TableWrapper> + setConfirmAppRemoval(false)} + onSubmit={onAppRemovalSubmit} + /> + ) : null + } + /> + <$HorizontalList> +
+
{t('common:batches.single')}
+
{proposalForDecisionHeader()}
+
+
+
{t('common:batches.list.columns.handler')}
+
{handler?.first_name}
+
+
+
{t('common:batches.list.columns.createdAt')}
+
{convertToUIDateAndTimeFormat(created_at)}
+
+
+ {applications.length > 0 ? ( + + ) : null} +
+ + {applications?.length ? ( + <$TableBody isCollapsed={isCollapsed} aria-hidden={isCollapsed}> +
+ <$TableFooter> + {status === BATCH_STATUSES.AWAITING_FOR_DECISION ? ( + + ) : ( + + )} + + + ) : ( + <$Empty css="margin: var(--spacing-s) 0;"> + {t('common:batches.list.empty')} + + )} + + ); }; diff --git a/frontend/benefit/handler/src/components/batchProcessing/batchAction/BatchActions.sc.ts b/frontend/benefit/handler/src/components/batchProcessing/batchAction/BatchActions.sc.ts new file mode 100644 index 0000000000..7559964641 --- /dev/null +++ b/frontend/benefit/handler/src/components/batchProcessing/batchAction/BatchActions.sc.ts @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +type TooltipProps = { + disabled: boolean; +}; + +export const $TooltipWrapper = styled.div` + opacity: ${(props) => (!props.disabled ? '1' : '0.3')}; + pointer-events: ${(props) => (!props.disabled ? 'all' : 'none')}; + + label { + display: inline-flex; + align-items: center; + + span { + margin-right: var(--spacing-xs); + } + } +`; diff --git a/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts b/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts index 3bb38f0ab0..e986bbb85e 100644 --- a/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts +++ b/frontend/benefit/handler/src/hooks/useApplicationToBatch.ts @@ -35,7 +35,6 @@ const useAddToBatchQuery = (): UseMutationResult => { t(`common:batches.notifications.errors.${errorKey}.locked.title`), t(`common:batches.notifications.errors.${errorKey}.locked.message`) ); - return; } else { showErrorToast( t('common:applications.list.errors.fetch.label'), diff --git a/frontend/benefit/handler/src/hooks/useBatchComplete.ts b/frontend/benefit/handler/src/hooks/useBatchComplete.ts index c4a4a21b6a..854a981ed4 100644 --- a/frontend/benefit/handler/src/hooks/useBatchComplete.ts +++ b/frontend/benefit/handler/src/hooks/useBatchComplete.ts @@ -57,7 +57,6 @@ const useBatchComplete = ( ...form, decision_date: parsedAsDatenew, }; - const request = axios.patch( HandlerEndpoint.BATCH_STATUS_CHANGE(id), { From 3f0d774683f3f6fa593945e4a1388c25e16517a4 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 12:32:16 +0300 Subject: [PATCH 021/102] chore: add/update translation files --- .../handler/public/locales/en/common.json | 33 ++++++++++++++++--- .../handler/public/locales/fi/common.json | 14 ++++++-- .../handler/public/locales/sv/common.json | 31 ++++++++++++++--- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/frontend/benefit/handler/public/locales/en/common.json b/frontend/benefit/handler/public/locales/en/common.json index 711915b602..5de54c9230 100644 --- a/frontend/benefit/handler/public/locales/en/common.json +++ b/frontend/benefit/handler/public/locales/en/common.json @@ -389,7 +389,9 @@ "and": "ja", "home": "Palaa etusivulle", "check": "Tarkastele", - "loading": "Ladataan …" + "loading": "Ladataan …", + "cancel": "Peruuta", + "confirm": "Vahvista" }, "upload": { "isUploading": "Uploading...", @@ -490,6 +492,9 @@ }, "empty": "Tyhjä koonti" }, + "tooltips": { + "lockedBatch": "{{ name }} vie parhaillaan hakemuksia Ahjoon. Koontin ei juuri nyt voi tehdä muutoksia. Odota hetki tai avaa lukko." + }, "notifications": { "addToBatch": { "success": { @@ -499,14 +504,21 @@ }, "registerToAhjo": { "awaiting_ahjo_decision": "Koonti merkitty Ahjoon viedyksi", + "exported_ahjo_report": "Ahjo-valmistelu aloitettu, koonti lukittu", "draft": "Koonti palautettu takaisin odottamaan päätösvalmistelua", "accepted": "Koonti lähetetty Talpaan ja arkistoitu", "rejected": "Koonti arkistoitu" }, "errors": { "batchInvalidDraftAlreadyExists": { - "title": "Uuden Ahjo-koonnin valmistelu on jo käynnissä", - "message": "Voit palauttaa tämän koonnin takaisin käsittelyyn vasta, kun jo valmistelussa oleva keskeneräinen koonti on viety Ahjoon" + "draft": { + "title": "Uuden Ahjo-koonnin valmistelu on jo käynnissä", + "message": "Voit palauttaa tämän koonnin takaisin käsittelyyn vasta, kun jo valmistelussa oleva keskeneräinen koonti on viety Ahjoon" + }, + "locked": { + "title": "Hakemuksia ei voitu siirtää koontiin", + "message": "Kollegasi on valmistelemassa koontia Ahjoon. Voit luoda uuden Ahjo-koonnin, kun hän on ensin valmistellut koonnin loppuun." + } }, "downloadError": { "title": "Liitetiedostojen lataus epäonnistui", @@ -515,12 +527,23 @@ } }, "actions": { + "markAsReadyForAhjo": "Aloita Ahjo-valmistelu", "markAsRegisteredToAhjo": "Merkitse Ahjoon viedyksi", "markedAsRegisteredToAhjo": "Viety Ahjoon", "markToPaymentAndArchive": "Merkitse maksuun ja arkistoi", "markToArchive": "Arkistoi", "markAsWaitingForAhjo": "Palauta odottamaan Ahjoon vientiä", - "downloadFiles": "Lataa liitteet" + "downloadFiles": "Lataa liitteet", + "deleteBatch": "Tyhjennä koonti", + "lockedBatch": "Lukittu" + }, + "dialog": { + "batchRemoval": {"heading": "Tyhjennä koonti", "text": "Oletko varma, että haluat tuhota koonnin?"}, + "removeApplication": {"heading": "Poista hakemus koonnista", "text": "Oletko varma, että haluat poistaa hakemuksen koonnista? {{ applicationNumber }}"}, + "batchFromLockedToDraft": {"heading": "Koonti lukittu", "text": "Haluatko vapauttaa koonnin ja tehdä siihen muutoksia?"}, + "batchFromDraftToAhjo": {"heading": "Merkitse koonti Ahjoon viedyksi", "text": "Haluatko viedä koonnin Ahjoon?"}, + "batchFromAhjoToDraft": {"heading": "Palauta odottamaan Ahjoon vientiä", "text": "Oletko varma, että haluat palauttaa koonnin odottamaan Ahjoon vientiä?"}, + "batchFromAhjoToTalpa": {"heading": "Koonti on valmis", "text": "Haluatko merkitä koonnin valmiiksi ja arkistoida sen?"} }, "form": { "fields": { @@ -550,4 +573,4 @@ }, "keyword": "Hakusana" } -} \ No newline at end of file +} diff --git a/frontend/benefit/handler/public/locales/fi/common.json b/frontend/benefit/handler/public/locales/fi/common.json index 2c6c74f60a..0c33937c3d 100644 --- a/frontend/benefit/handler/public/locales/fi/common.json +++ b/frontend/benefit/handler/public/locales/fi/common.json @@ -389,7 +389,9 @@ "and": "ja", "home": "Palaa etusivulle", "check": "Tarkastele", - "loading": "Ladataan …" + "loading": "Ladataan …", + "cancel": "Peruuta", + "confirm": "Vahvista" }, "upload": { "isUploading": "Uploading...", @@ -515,7 +517,7 @@ }, "locked": { "title": "Hakemuksia ei voitu siirtää koontiin", - "message": "Kollegasi on valmistelemassa koontia Ahjoon. Voit luoda uuden Ahjo-koonnin, kun hän on ensin viimeistellyt koonnin." + "message": "Kollegasi on valmistelemassa koontia Ahjoon. Voit luoda uuden Ahjo-koonnin, kun hän on ensin valmistellut koonnin loppuun." } }, "downloadError": { @@ -535,6 +537,14 @@ "deleteBatch": "Tyhjennä koonti", "lockedBatch": "Lukittu" }, + "dialog": { + "batchRemoval": {"heading": "Tyhjennä koonti", "text": "Oletko varma, että haluat tuhota koonnin?"}, + "removeApplication": {"heading": "Poista hakemus koonnista", "text": "Oletko varma, että haluat poistaa hakemuksen koonnista? {{ applicationNumber }}"}, + "batchFromLockedToDraft": {"heading": "Koonti lukittu", "text": "Haluatko vapauttaa koonnin ja tehdä siihen muutoksia?"}, + "batchFromDraftToAhjo": {"heading": "Merkitse koonti Ahjoon viedyksi", "text": "Haluatko viedä koonnin Ahjoon?"}, + "batchFromAhjoToDraft": {"heading": "Palauta odottamaan Ahjoon vientiä", "text": "Oletko varma, että haluat palauttaa koonnin odottamaan Ahjoon vientiä?"}, + "batchFromAhjoToTalpa": {"heading": "Koonti on valmis", "text": "Haluatko merkitä koonnin valmiiksi ja arkistoida sen?"} + }, "form": { "fields": { "decisionMakerName": "Päättäjän nimi", diff --git a/frontend/benefit/handler/public/locales/sv/common.json b/frontend/benefit/handler/public/locales/sv/common.json index 5feba9270c..799d413482 100644 --- a/frontend/benefit/handler/public/locales/sv/common.json +++ b/frontend/benefit/handler/public/locales/sv/common.json @@ -389,7 +389,9 @@ "and": "ja", "home": "Palaa etusivulle", "check": "Tarkastele", - "loading": "Ladataan …" + "loading": "Ladataan …", + "cancel": "Peruuta", + "confirm": "Vahvista" }, "upload": { "isUploading": "Uploading...", @@ -490,6 +492,9 @@ }, "empty": "Tyhjä koonti" }, + "tooltips": { + "lockedBatch": "{{ name }} vie parhaillaan hakemuksia Ahjoon. Koontin ei juuri nyt voi tehdä muutoksia. Odota hetki tai avaa lukko." + }, "notifications": { "addToBatch": { "success": { @@ -499,14 +504,21 @@ }, "registerToAhjo": { "awaiting_ahjo_decision": "Koonti merkitty Ahjoon viedyksi", + "exported_ahjo_report": "Ahjo-valmistelu aloitettu, koonti lukittu", "draft": "Koonti palautettu takaisin odottamaan päätösvalmistelua", "accepted": "Koonti lähetetty Talpaan ja arkistoitu", "rejected": "Koonti arkistoitu" }, "errors": { "batchInvalidDraftAlreadyExists": { - "title": "Uuden Ahjo-koonnin valmistelu on jo käynnissä", - "message": "Voit palauttaa tämän koonnin takaisin käsittelyyn vasta, kun jo valmistelussa oleva keskeneräinen koonti on viety Ahjoon" + "draft": { + "title": "Uuden Ahjo-koonnin valmistelu on jo käynnissä", + "message": "Voit palauttaa tämän koonnin takaisin käsittelyyn vasta, kun jo valmistelussa oleva keskeneräinen koonti on viety Ahjoon" + }, + "locked": { + "title": "Hakemuksia ei voitu siirtää koontiin", + "message": "Kollegasi on valmistelemassa koontia Ahjoon. Voit luoda uuden Ahjo-koonnin, kun hän on ensin valmistellut koonnin loppuun." + } }, "downloadError": { "title": "Liitetiedostojen lataus epäonnistui", @@ -515,12 +527,23 @@ } }, "actions": { + "markAsReadyForAhjo": "Aloita Ahjo-valmistelu", "markAsRegisteredToAhjo": "Merkitse Ahjoon viedyksi", "markedAsRegisteredToAhjo": "Viety Ahjoon", "markToPaymentAndArchive": "Merkitse maksuun ja arkistoi", "markToArchive": "Arkistoi", "markAsWaitingForAhjo": "Palauta odottamaan Ahjoon vientiä", - "downloadFiles": "Lataa liitteet" + "downloadFiles": "Lataa liitteet", + "deleteBatch": "Tyhjennä koonti", + "lockedBatch": "Lukittu" + }, + "dialog": { + "batchRemoval": {"heading": "Tyhjennä koonti", "text": "Oletko varma, että haluat tuhota koonnin?"}, + "removeApplication": {"heading": "Poista hakemus koonnista", "text": "Oletko varma, että haluat poistaa hakemuksen koonnista? {{ applicationNumber }}"}, + "batchFromLockedToDraft": {"heading": "Koonti lukittu", "text": "Haluatko vapauttaa koonnin ja tehdä siihen muutoksia?"}, + "batchFromDraftToAhjo": {"heading": "Merkitse koonti Ahjoon viedyksi", "text": "Haluatko viedä koonnin Ahjoon?"}, + "batchFromAhjoToDraft": {"heading": "Palauta odottamaan Ahjoon vientiä", "text": "Oletko varma, että haluat palauttaa koonnin odottamaan Ahjoon vientiä?"}, + "batchFromAhjoToTalpa": {"heading": "Koonti on valmis", "text": "Haluatko merkitä koonnin valmiiksi ja arkistoida sen?"} }, "form": { "fields": { From a59a38aa0bbc46a63ecb7c2dc7f74c7e710f4229 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 13:18:14 +0300 Subject: [PATCH 022/102] fix: batch test failed because of new allowed status --- .../applications/tests/test_application_batch_api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/benefit/applications/tests/test_application_batch_api.py b/backend/benefit/applications/tests/test_application_batch_api.py index f28c172cce..6a82524431 100755 --- a/backend/benefit/applications/tests/test_application_batch_api.py +++ b/backend/benefit/applications/tests/test_application_batch_api.py @@ -228,7 +228,6 @@ def test_deassign_applications_from_batch(handler_api_client, application_batch) [ (ApplicationBatchStatus.COMPLETED, 400, None), (ApplicationBatchStatus.SENT_TO_TALPA, 400, None), - (ApplicationBatchStatus.AHJO_REPORT_CREATED, 400, None), (ApplicationBatchStatus.RETURNED, 400, None), (ApplicationBatchStatus.DECIDED_ACCEPTED, 400, None), (ApplicationBatchStatus.DECIDED_REJECTED, 400, None), @@ -238,6 +237,11 @@ def test_deassign_applications_from_batch(handler_api_client, application_batch) 200, ApplicationBatchStatus.AWAITING_AHJO_DECISION, ), + ( + ApplicationBatchStatus.AHJO_REPORT_CREATED, + 200, + ApplicationBatchStatus.AHJO_REPORT_CREATED, + ), ], ) def test_batch_status_change( From 3c6047e0c222202689326ec35ccbd16964c39338 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 14:09:29 +0300 Subject: [PATCH 023/102] feat: test too many draft batches and removal if emptied from apps --- .../tests/test_application_batch_api.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/backend/benefit/applications/tests/test_application_batch_api.py b/backend/benefit/applications/tests/test_application_batch_api.py index 6a82524431..fb4b17f6f1 100755 --- a/backend/benefit/applications/tests/test_application_batch_api.py +++ b/backend/benefit/applications/tests/test_application_batch_api.py @@ -13,6 +13,7 @@ from applications.api.v1.serializers.application import ApplicationBatchSerializer from applications.enums import AhjoDecision, ApplicationBatchStatus, ApplicationStatus +from applications.exceptions import BatchTooManyDraftsError from applications.models import Application, ApplicationBatch from applications.tests.conftest import * # noqa from applications.tests.factories import ( @@ -223,6 +224,21 @@ def test_deassign_applications_from_batch(handler_api_client, application_batch) assert response.status_code == 404 +def test_deassign_applications_from_batch_all(handler_api_client, application_batch): + apps = Application.objects.filter(batch=application_batch) + url = get_batch_detail_url(application_batch, "deassign_applications/") + response = handler_api_client.patch( + url, + { + "application_ids": list(map(lambda app: app.id, apps)), + "batch_id": application_batch.id, + }, + ) + assert response.status_code == 200 + with pytest.raises(ApplicationBatch.DoesNotExist): + application_batch.refresh_from_db() + + @pytest.mark.parametrize( "batch_status,status_code,changed_status", [ @@ -254,6 +270,35 @@ def test_batch_status_change( assert response.data["status"] == changed_status +def test_batch_too_many_drafts(application_batch): + # Create a second batch to get to two batch limit + ApplicationBatchFactory( + status=ApplicationBatchStatus.DRAFT, + proposal_for_decision=ApplicationStatus.REJECTED, + ), + + # Create a batch with different status and try putting it in draft + batch_with_status_change = ApplicationBatchFactory( + status=ApplicationBatchStatus.AWAITING_AHJO_DECISION, + proposal_for_decision=ApplicationStatus.REJECTED, + ) + batch_with_status_change.status = ApplicationBatchStatus.DRAFT + with pytest.raises(BatchTooManyDraftsError): + batch_with_status_change.save() + + # Create more drafts, accepted and rejected, should fail + with pytest.raises(BatchTooManyDraftsError): + ApplicationBatchFactory( + status=ApplicationBatchStatus.DRAFT, + proposal_for_decision=ApplicationStatus.ACCEPTED, + ) + with pytest.raises(BatchTooManyDraftsError): + ApplicationBatchFactory( + status=ApplicationBatchStatus.DRAFT, + proposal_for_decision=ApplicationStatus.REJECTED, + ) + + @pytest.mark.parametrize( "batch_status,delta_months,delta_days", [ From 8ab6113336a790cdae6c927089b23ffae7d61294 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 14:25:08 +0300 Subject: [PATCH 024/102] feat: test simplified_application_list's filter_archived option --- .../tests/test_applications_api.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/backend/benefit/applications/tests/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index 7e5f489e20..a3a1b85473 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -2181,6 +2181,30 @@ def test_handler_application_exlude_batched(handler_api_client): assert len(response.data) == 1 +def test_handler_application_filter_archived(handler_api_client): + apps = [ + DecidedApplicationFactory(), + DecidedApplicationFactory(), + DecidedApplicationFactory(archived=True), + ] + + response = handler_api_client.get( + reverse("v1:handler-application-simplified-application-list") + ) + assert len(response.data) == 2 + for response_app in response.data: + assert response_app["id"] in [str(apps[0].id), str(apps[1].id)] + assert not response_app["archived"] + + response = handler_api_client.get( + reverse("v1:handler-application-simplified-application-list"), + {"filter_archived": "1"}, + ) + assert len(response.data) == 1 + assert response.data[0]["id"] == str(apps[2].id) + assert response.data[0]["archived"] + + def _create_random_applications(): f = faker.Faker() combos = [ From 9fc78ab910e3296991ac2c5497b9524829ae6d00 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 14:58:27 +0300 Subject: [PATCH 025/102] chore: upgrade pdf libraries --- frontend/benefit/applicant/package.json | 6 +- frontend/package.json | 1 - frontend/yarn.lock | 669 ++++++++++++------------ 3 files changed, 347 insertions(+), 329 deletions(-) diff --git a/frontend/benefit/applicant/package.json b/frontend/benefit/applicant/package.json index fa9c3e239d..58fb105e4d 100644 --- a/frontend/benefit/applicant/package.json +++ b/frontend/benefit/applicant/package.json @@ -17,7 +17,7 @@ "dependencies": { "@frontend/shared": "*", "@frontend/benefit-shared": "*", - "@react-pdf/renderer": "^2.1.1", + "@react-pdf/renderer": "^3.1.10", "@sentry/browser": "^7.16.0", "@sentry/nextjs": "^7.16.0", "axios": "^0.27.2", @@ -34,12 +34,12 @@ "next-i18next": "^10.5.0", "next-plugin-custom-babel-config": "^1.0.5", "next-transpile-modules": "^9.0.0", - "pdfjs-dist": "^2.10.377", + "pdfjs-dist": "3.6.172", "react": "^18.0.0", "react-dom": "^18.0.0", "react-input-mask": "^2.0.4", "react-loading-skeleton": "^3.0.3", - "react-pdf": "^5.7.2", + "react-pdf": "^7.0.3", "react-query": "^3.34.0", "react-toastify": "^9.0.4", "snakecase-keys": "^5.4.2", diff --git a/frontend/package.json b/frontend/package.json index d4e643ed3c..b347873500 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -87,7 +87,6 @@ "@types/react-input-mask": "2.0.5", "@types/react-leaflet": "^2.8.2", "@types/react-leaflet-markercluster": "^3.0.0", - "@types/react-pdf": "^5.7.2", "@types/react-table": "^7.7.0", "@types/slack-node": "^0.1.4", "@types/styled-components": "^5.1.24", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1deaf84177..239b18c2be 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1044,13 +1044,20 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.17", "@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.4", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.17", "@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.20.13": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" + integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.16.7", "@babel/template@^7.3.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -2303,6 +2310,21 @@ npmlog "^4.1.2" write-file-atomic "^3.0.3" +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@miherlosev/esm@3.2.26": version "3.2.26" resolved "https://registry.yarnpkg.com/@miherlosev/esm/-/esm-3.2.26.tgz#a516b35d8b8b4eac29598f1c2c23e30a4d260200" @@ -2623,154 +2645,143 @@ resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-1.1.1.tgz#827fd05bb542cf874116176d8ef48d5b12163f81" integrity sha512-7PGLWa9MZ5x/cWy8EH2VzI4T8q5WpuHbixzCDXqixP/WyqwIrg5NDUPgYuFnB4IEIZF+6nA265mYzswFo/h1Pw== -"@react-pdf/font@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@react-pdf/font/-/font-2.1.1.tgz#18bf4de35d9a72082e5fa08dfeaf0869107c5ad6" - integrity sha512-4XwdD4S9HoiuhUQZvCGTFz+baYqowUcq7+HknRlMEk0HQOL3Rnnqe7Sxlv8ap208bHUsdsQFMjxlW8D0BS6C0w== +"@react-pdf/fns@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@react-pdf/fns/-/fns-2.0.1.tgz#8948464044fc8a69975d9d07b1a12673377b72e2" + integrity sha512-/vgecczzFYBQFkgUupH+sxXhLWQtBwdwCgweyh25XOlR4NZuaMD/UVUDl4loFHhRQqDMQq37lkTcchh7zzW6ug== dependencies: - "@babel/runtime" "^7.16.4" - "@react-pdf/fontkit" "^2.1.0" - "@react-pdf/types" "^2.0.8" + "@babel/runtime" "^7.20.13" + +"@react-pdf/font@^2.3.6": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@react-pdf/font/-/font-2.3.6.tgz#d92c6abf5fe6643842d47b0d22d52487026de311" + integrity sha512-JYV+KmVyG2tPdpCK0/iFiBy1V7VHz2fETttKCgTRsLAo+w8RpM0pUGSAYROSuRl7yqbhiKGw/A24PYWhBReiOQ== + dependencies: + "@babel/runtime" "^7.20.13" + "@react-pdf/types" "^2.3.3" cross-fetch "^3.1.5" + fontkit "^2.0.2" is-url "^1.2.4" -"@react-pdf/fontkit@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@react-pdf/fontkit/-/fontkit-2.1.0.tgz#98b0bfe23f6aaf14e047718365ab704ebb1491a7" - integrity sha512-/t7nOtH0XAgN7kPJtVfXQL78YmfUanhPc0HEIVgS0YdMTAtZJT0aIQ2HoBWyyYmlsDlsWyGkb78Hf4b1y5QaPQ== - dependencies: - "@babel/runtime" "^7.16.4" - "@react-pdf/unicode-properties" "^2.5.0" - brotli "^1.2.0" - clone "^1.0.4" - deep-equal "^1.0.0" - dfa "^1.2.0" - restructure "^0.5.3" - tiny-inflate "^1.0.2" - unicode-trie "^0.3.0" - -"@react-pdf/image@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@react-pdf/image/-/image-2.1.1.tgz#8db2adec56e8d4d4c590866dc936bed37a9a98a4" - integrity sha512-Uh9N1HBU5QGP1QxuIhpVES8FAQsSy2/IGrCHoCCzUUuvbUKf+Mezl3+gvaS4fkWbENPpZ9q6u2C3yL5IqRirsw== +"@react-pdf/image@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@react-pdf/image/-/image-2.2.1.tgz#4e3edb4df156ed36d31810de2b5054deafe65496" + integrity sha512-f0+cEP6pSBmk8eS/wP2tMsJcv2c7xjzca6cr1kwcapr1nzkPrh6fMdEeFl6kR2/HlJK/JoHo+xxlzRiQ8V2lrw== dependencies: - "@babel/runtime" "^7.16.4" - "@react-pdf/png-js" "^2.1.0" + "@babel/runtime" "^7.20.13" + "@react-pdf/png-js" "^2.2.0" cross-fetch "^3.1.5" -"@react-pdf/layout@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@react-pdf/layout/-/layout-2.1.1.tgz#edaab56de6e2d9b1772e75776ee07b03cdde65dc" - integrity sha512-ddpXNRAU1JNHL+AUQEFO2RFolDrtqs/Z0Gb8fjicCcWinthpqkeLdNMWYA5bQuUIzuJXONAQDGd6JLomv2Wayg== - dependencies: - "@babel/runtime" "^7.16.4" - "@react-pdf/image" "^2.1.1" - "@react-pdf/pdfkit" "^2.1.0" - "@react-pdf/primitives" "^2.0.2" - "@react-pdf/stylesheet" "^2.1.0" - "@react-pdf/textkit" "^2.1.0" - "@react-pdf/types" "^2.0.8" - "@react-pdf/yoga" "^2.0.4" +"@react-pdf/layout@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@react-pdf/layout/-/layout-3.6.2.tgz#10fc0b8f26cfbd4c7f774aa2317d2fb625c6f656" + integrity sha512-YD3/tDC6p5XPCXI04zH79bgX8LytjxEYfeCtsIzEFk0A2VvIHoRnRRDZ2OhZmO5g112ykyjY8vn9//ubTt+Ktg== + dependencies: + "@babel/runtime" "^7.20.13" + "@react-pdf/fns" "2.0.1" + "@react-pdf/image" "^2.2.1" + "@react-pdf/pdfkit" "^3.0.2" + "@react-pdf/primitives" "^3.0.0" + "@react-pdf/stylesheet" "^4.1.7" + "@react-pdf/textkit" "^4.2.0" + "@react-pdf/types" "^2.3.3" + "@react-pdf/yoga" "^4.1.2" cross-fetch "^3.1.5" - emoji-regex "^8.0.0" + emoji-regex "^10.2.1" queue "^6.0.1" - ramda "^0.26.1" -"@react-pdf/pdfkit@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@react-pdf/pdfkit/-/pdfkit-2.1.0.tgz#dd2754df7fab55dab617217a359b2ee42e30b907" - integrity sha512-XS/mBgQadBEyz5b79L1rGOiPiX+9oZL8TlOV9PG9mGHS9VyrK7rieysay4azI/67a1nlRs2XyRETELPlqHYsHA== +"@react-pdf/pdfkit@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@react-pdf/pdfkit/-/pdfkit-3.0.2.tgz#6ec17f416f464d86c06c0b0d8a76ea9acdff9ddb" + integrity sha512-+m5rwNCwyEH6lmnZWpsQJvdqb6YaCCR0nMWrc/KKDwznuPMrGmGWyNxqCja+bQPORcHZyl6Cd/iFL0glyB3QGw== dependencies: - "@babel/runtime" "^7.16.4" - "@react-pdf/fontkit" "^2.1.0" - "@react-pdf/png-js" "^2.1.0" + "@babel/runtime" "^7.20.13" + "@react-pdf/png-js" "^2.2.0" + browserify-zlib "^0.2.0" crypto-js "^4.0.0" + fontkit "^2.0.2" + vite-compatible-readable-stream "^3.6.1" -"@react-pdf/png-js@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@react-pdf/png-js/-/png-js-2.1.0.tgz#1a6b8858fb7ad6c3e17ae628078928ac8faea951" - integrity sha512-S5T5qGOlDK6VUJBVGkltNcPFEOWJW5FAD5IWkp9ATYPehC7L1d0CwuFlkFDaHh9ySmm46fKRHfn4YNQguq9gmw== - -"@react-pdf/primitives@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@react-pdf/primitives/-/primitives-2.0.2.tgz#7cf4a711f2a1ffa51e8d8133d7571ac756346fc4" - integrity sha512-NkbOje/Sd/ziqfp9eYFc0ACeytmZB9MIrhx0j1rDT3gIhrjo19sS7R6Iap50gNgSphgx4Nh7GxWu/usBiuTmnw== - -"@react-pdf/render@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@react-pdf/render/-/render-2.1.0.tgz#6278f428bd2f61cfc9dfcee4745eb699bb9766be" - integrity sha512-OacYR/eY47OzuuBXr0TaIqdP2m8GWIMWVkzTVaSxB6ZEs0wFCv6Hw5LYmh1k617o1YbZkRRDJIyGUtTmZuw6Ng== +"@react-pdf/png-js@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@react-pdf/png-js/-/png-js-2.2.0.tgz#c40ec2ae745f2feb7bd557024af8f366c2c8c00e" + integrity sha512-csZU5lfNW73tq7s7zB/1rWXGro+Z9cQhxtsXwxS418TSszHUiM6PwddouiKJxdGhbVLjRIcuuFVa0aR5cDOC6w== dependencies: - "@babel/runtime" "^7.16.4" - "@react-pdf/primitives" "^2.0.2" - "@react-pdf/textkit" "^2.1.0" - "@react-pdf/types" "^2.0.8" + browserify-zlib "^0.2.0" + +"@react-pdf/primitives@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@react-pdf/primitives/-/primitives-3.0.1.tgz#3b2bfebdb1fef6fc7f99214ccfd0932267b8e0cd" + integrity sha512-0HGcknrLNwyhxe+SZCBL29JY4M85mXKdvTZE9uhjNbADGgTc8wVnkc5+e4S/lDvugbVISXyuIhZnYwtK9eDnyQ== + +"@react-pdf/render@^3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@react-pdf/render/-/render-3.2.6.tgz#9d6a6e3c89568fa05a37dac4e29c220eec0689c6" + integrity sha512-nsd1sleWMzBdrYGv5BwChPgVwoTZilfdiadE5wQiblFqG1C7EYINadalnEl1tjldKAzofSPBLKJbnSGR5r2lIQ== + dependencies: + "@babel/runtime" "^7.20.13" + "@react-pdf/fns" "2.0.1" + "@react-pdf/primitives" "^3.0.0" + "@react-pdf/textkit" "^4.2.0" + "@react-pdf/types" "^2.3.3" abs-svg-path "^0.1.1" color-string "^1.5.3" normalize-svg-path "^1.1.0" parse-svg-path "^0.1.2" - ramda "^0.26.1" svg-arc-to-cubic-bezier "^3.2.0" -"@react-pdf/renderer@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@react-pdf/renderer/-/renderer-2.1.1.tgz#e0be312d1011b9bc4053a945e287f1db3c36f33c" - integrity sha512-W6Ma/4/zfj8KAJQkBeyHmzR3OuuMZFYZFrFtcfFXQNCZC3NDEasTNN0qYwcBAPkCyDAm+jhA/CKezhAPwnh3QQ== - dependencies: - "@babel/runtime" "^7.16.4" - "@react-pdf/font" "^2.1.1" - "@react-pdf/layout" "^2.1.1" - "@react-pdf/pdfkit" "^2.1.0" - "@react-pdf/primitives" "^2.0.2" - "@react-pdf/render" "^2.1.0" - "@react-pdf/types" "^2.0.8" - blob-stream "^0.1.3" +"@react-pdf/renderer@^3.1.10": + version "3.1.11" + resolved "https://registry.yarnpkg.com/@react-pdf/renderer/-/renderer-3.1.11.tgz#7a7f80e2a021de6e12a1463847e5bc9ca39586c0" + integrity sha512-te/RTcyTkIg6zlvuoUYs5cEXPyZPjADrhcm/6EA2OudMmmwHlTrsinMgLuB8aGlSjmgo3anlu1soIC2j+KsyTQ== + dependencies: + "@babel/runtime" "^7.20.13" + "@react-pdf/font" "^2.3.6" + "@react-pdf/layout" "^3.6.2" + "@react-pdf/pdfkit" "^3.0.2" + "@react-pdf/primitives" "^3.0.0" + "@react-pdf/render" "^3.2.6" + "@react-pdf/types" "^2.3.3" + events "^3.3.0" + object-assign "^4.1.1" + prop-types "^15.6.2" queue "^6.0.1" - ramda "^0.26.1" - react-reconciler "^0.23.0" scheduler "^0.17.0" -"@react-pdf/stylesheet@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@react-pdf/stylesheet/-/stylesheet-2.1.0.tgz#8287f5db5f5636b6b76d7ffab3d35f913aec26eb" - integrity sha512-F9v++z1QhlnCONkwdUJm1C/FNM9WkTiTRp+OCe6za2iZvrs9sGhSnTHFMOb5HOZYHsAXx+pLgHqE4WP+60SunQ== +"@react-pdf/stylesheet@^4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@react-pdf/stylesheet/-/stylesheet-4.1.7.tgz#f0ac1396e70d356262de59aeb8efa17c7d9a2a0c" + integrity sha512-3n0Vg0XFszPyo0MpH75DkLRvsS4JOE0HzBH6XqFHDiquZDrC4mNgmMhZEbsOED+8xDGoCeVh8fLU3L6Tu0HWqg== dependencies: - "@babel/runtime" "^7.16.4" - "@react-pdf/types" "^2.0.8" + "@babel/runtime" "^7.20.13" + "@react-pdf/fns" "2.0.1" + "@react-pdf/types" "^2.3.3" color-string "^1.5.3" hsl-to-hex "^1.0.0" media-engine "^1.0.3" postcss-value-parser "^4.1.0" - ramda "^0.26.1" -"@react-pdf/textkit@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@react-pdf/textkit/-/textkit-2.1.0.tgz#5ece2e09a1c5ac3c732e0e4af57373c2be588511" - integrity sha512-caFluGk2aHgPjeGxqcYvH3rake/01K5zQfjQ7RVtjye5ZvlSgEFSuorRynwFPs92Vw7tA9TjvVnNc3GDsWghgQ== +"@react-pdf/textkit@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@react-pdf/textkit/-/textkit-4.2.0.tgz#bd8299708ddb7a9b154706aa2516dd3666230cf1" + integrity sha512-R90pEOW6NdhUx4p99iROvKmwB06IRYdXMhh0QcmUeoPOLe64ZdMfs3LZliNUWgI5fCmq71J+nv868i/EakFPDg== dependencies: - "@babel/runtime" "^7.16.4" - "@react-pdf/unicode-properties" "^2.5.0" + "@babel/runtime" "^7.20.13" + "@react-pdf/fns" "2.0.1" hyphen "^1.6.4" - ramda "^0.26.1" - -"@react-pdf/types@^2.0.8": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@react-pdf/types/-/types-2.0.8.tgz#2f0f3c43e1a7c975100f152d5af78e71f7173fdd" - integrity sha512-/LngaZV8Z7+XEvbT2IVPbSxmU7pPYqCgdrrAkjGtdHsuGHRJegJMTBUEOellOY5Pqaqy3yvNTz/kZ9URrv+McQ== + unicode-properties "^1.4.1" -"@react-pdf/unicode-properties@^2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@react-pdf/unicode-properties/-/unicode-properties-2.5.0.tgz#f77f2ba1a55ad1f69cd2ed886a60cb5700c1f1c5" - integrity sha512-L311rb7e2LInUcGpujxBjNQSU8C+4DHk83j7Q93mJwmsapCzwQmas2LxAJElGGQie90db21/X8sUniSLjrlceA== - dependencies: - unicode-trie "^0.3.0" +"@react-pdf/types@^2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@react-pdf/types/-/types-2.3.3.tgz#96a0d0514d74291bf1dbc7c75322025b3aa72bbb" + integrity sha512-I3BVu5vF0xxX6rvqZHt4gCjFAt6X+mak5bwYQyf6bm21IIMDXXBtgXqWEl1wosWizArox7fcN/XbEnysrf/8Dw== -"@react-pdf/yoga@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@react-pdf/yoga/-/yoga-2.0.4.tgz#6b14c6f244dc551db209acd05a06354a19fed66e" - integrity sha512-bsU48GQ8E4LEQ38AtyQPQZ9oEATMpolGPFewgI4sBXOZBNH2miLtoBTbyB/xEOMuBcyqtvJQwSNg2czSZjrlyQ== +"@react-pdf/yoga@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@react-pdf/yoga/-/yoga-4.1.2.tgz#cc901f7384f0c1976d7ddeba5cc77e26d768ba77" + integrity sha512-OlMZkFrJDj4GyKZ70thiObwwPVZ52B7mlPyfzwa+sgwsioqHXg9nMWOO+7SQFNUbbOGagMUu0bCuTv+iXYZuaQ== dependencies: - "@types/yoga-layout" "^1.9.3" + "@babel/runtime" "^7.20.13" "@react-spring/animated@~9.3.0": version "9.3.2" @@ -3018,6 +3029,13 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@swc/helpers@^0.4.2": + version "0.4.14" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.14.tgz#1352ac6d95e3617ccb7c1498ff019654f1e12a74" + integrity sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw== + dependencies: + tslib "^2.4.0" + "@testing-library/dom@^7.27.1": version "7.31.2" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a" @@ -3262,7 +3280,7 @@ resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3" integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA== -"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -3379,14 +3397,6 @@ "@types/leaflet" "*" "@types/react" "*" -"@types/react-pdf@^5.7.2": - version "5.7.2" - resolved "https://registry.yarnpkg.com/@types/react-pdf/-/react-pdf-5.7.2.tgz#8e0ec89efeb4e574ec62b2370495bd3ee11d8ed8" - integrity sha512-6cUselXlQSNd9pMswJGvHqki3Lq0cnls/3hNwrFizdDeHBAfTFXTScEBObfGPznEmtO2LvmZMeced43BV9Wbog== - dependencies: - "@types/react" "*" - pdfjs-dist "^2.10.377" - "@types/react-table@^7.7.0": version "7.7.10" resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.10.tgz#ca8bb5420bfeae964ff61682f31f1cadfcfee726" @@ -3480,11 +3490,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yoga-layout@^1.9.3": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@types/yoga-layout/-/yoga-layout-1.9.4.tgz#fad54fef623464dacd9e765d854f0e76ff1184f7" - integrity sha512-RRHc1+8Hc5mf/2lZKnom6kCnqcNS07s8keahniWTOva0KELF6RgDJmaEcvGEKUUJgN4UgessmEsWuidaOycIOw== - "@typescript-eslint/eslint-plugin@^5.28.0": version "5.28.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.28.0.tgz#6204ac33bdd05ab27c7f77960f1023951115d403" @@ -3767,12 +3772,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6: +ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.6: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3856,11 +3856,19 @@ aproba@^1.0.3: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -aproba@^2.0.0: +"aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + are-we-there-yet@~1.1.2: version "1.1.7" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" @@ -4040,15 +4048,6 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== -ast-transform@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/ast-transform/-/ast-transform-0.0.0.tgz#74944058887d8283e189d954600947bc98fe0062" - integrity sha1-dJRAWIh9goPhidlUYAlHvJj+AGI= - dependencies: - escodegen "~1.2.0" - esprima "~1.0.4" - through "~2.3.4" - ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" @@ -4059,11 +4058,6 @@ ast-types@0.13.2: resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48" integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA== -ast-types@^0.7.0: - version "0.7.8" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.7.8.tgz#902d2e0d60d071bdcd46dc115e1809ed11c138a9" - integrity sha1-kC0uDWDQcb3NRtwRXhgJ7RHBOKk= - astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -4295,7 +4289,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.0.2, base64-js@^1.1.2, base64-js@^1.3.1: +base64-js@^1.0.2, base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -4341,18 +4335,6 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -blob-stream@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/blob-stream/-/blob-stream-0.1.3.tgz#98d668af6996e0f32ef666d06e215ccc7d77686c" - integrity sha1-mNZor2mW4PMu9mbQbiFczH13aGw= - dependencies: - blob "0.0.4" - -blob@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" - integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE= - bluebird@^3.5.0: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -4428,25 +4410,25 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= -brotli@^1.2.0, brotli@^1.3.1: +brotli@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.2.tgz#525a9cad4fcba96475d7d388f6aecb13eed52f46" integrity sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y= dependencies: base64-js "^1.1.2" +brotli@^1.3.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.3.tgz#7365d8cc00f12cf765d2b2c898716bcf4b604d48" + integrity sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg== + dependencies: + base64-js "^1.1.2" + browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browser-resolve@^1.8.1: - version "1.11.3" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" - integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== - dependencies: - resolve "1.1.7" - browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -4478,15 +4460,6 @@ browserify-des@^1.0.0: inherits "^2.0.1" safe-buffer "^5.1.2" -browserify-optional@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-optional/-/browserify-optional-1.0.1.tgz#1e13722cfde0d85f121676c2a72ced533a018869" - integrity sha1-HhNyLP3g2F8SFnbCpyztUzoBiGk= - dependencies: - ast-transform "0.0.0" - ast-types "^0.7.0" - browser-resolve "^1.8.1" - browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" @@ -4723,6 +4696,15 @@ caniuse-lite@^1.0.30001202, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.300012 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz#2b4ad19b77aa36f61f2eaf72e636d7481d55e606" integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ== +canvas@^2.11.2: + version "2.11.2" + resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860" + integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + nan "^2.17.0" + simple-get "^3.0.3" + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -4954,16 +4936,26 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone@^1.0.2, clone@^1.0.4: +clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +clone@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + clsx@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + cmd-shim@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" @@ -5031,6 +5023,11 @@ color-string@^1.9.0: color-name "^1.0.0" simple-swizzle "^0.2.2" +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + color@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" @@ -5144,7 +5141,7 @@ console-browserify@^1.1.0: resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -console-control-strings@^1.0.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= @@ -5646,6 +5643,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -5675,18 +5679,6 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" -deep-equal@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== - dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" - deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -6030,6 +6022,11 @@ emittery@^0.8.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== +emoji-regex@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.2.1.tgz#a41c330d957191efd3d9dfe6e1e8e1e9ab048b3f" + integrity sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -6045,11 +6042,6 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -6220,17 +6212,6 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -escodegen@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.2.0.tgz#09de7967791cc958b7f89a2ddb6d23451af327e1" - integrity sha1-Cd55Z3kcyVi3+Jot220jRRrzJ+E= - dependencies: - esprima "~1.0.4" - estraverse "~1.5.0" - esutils "~1.0.0" - optionalDependencies: - source-map "~0.1.30" - eslint-ast-utils@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-ast-utils/-/eslint-ast-utils-1.1.0.tgz#3d58ba557801cfb1c941d68131ee9f8c34bd1586" @@ -6695,11 +6676,6 @@ esprima@^4.0.0, esprima@^4.0.1: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esprima@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.0.4.tgz#9f557e08fc3b4d26ece9dd34f8fbf476b62585ad" - integrity sha1-n1V+CPw7TSbs6d00+Pv0drYlha0= - esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" @@ -6724,11 +6700,6 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estraverse@~1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.5.1.tgz#867a3e8e58a9f84618afb6c2ddbcd916b7cbaf71" - integrity sha1-hno+jlip+EYYr7bC3bzZFrfLr3E= - estree-walker@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" @@ -6739,11 +6710,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -esutils@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.0.0.tgz#8151d358e20c8acc7fb745e7472c0025fe496570" - integrity sha1-gVHTWOIMisx/t0XnRywAJf5JZXA= - etag@1.8.1, etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -6767,7 +6733,7 @@ eventemitter3@^4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.0.0: +events@^3.0.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -6987,14 +6953,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-loader@^6.0.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -7090,6 +7048,21 @@ follow-redirects@^1.14.9: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4" integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== +fontkit@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fontkit/-/fontkit-2.0.2.tgz#ac5384f3ecab8327c6d2ea2e4d384afc544b48fd" + integrity sha512-jc4k5Yr8iov8QfS6u8w2CnHWVmbOGtdBtOXMze5Y+QD966Rx6PEVWXSEGwXlsDlKtu1G12cJjcsybnqhSk/+LA== + dependencies: + "@swc/helpers" "^0.4.2" + brotli "^1.3.2" + clone "^2.1.2" + dfa "^1.2.0" + fast-deep-equal "^3.1.3" + restructure "^3.0.0" + tiny-inflate "^1.0.3" + unicode-properties "^1.4.0" + unicode-trie "^2.0.0" + foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -7234,6 +7207,21 @@ fuse.js@^6.6.2: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111" integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -8339,7 +8327,7 @@ is-proto-prop@^2.0.0: lowercase-keys "^1.0.0" proto-props "^2.0.0" -is-regex@^1.0.4, is-regex@^1.1.4: +is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== @@ -9366,15 +9354,6 @@ loader-utils@1.2.3: emojis-list "^2.0.0" json5 "^1.0.1" -loader-utils@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" - integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - localforage@^1.8.1: version "1.10.0" resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" @@ -9649,7 +9628,7 @@ make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0, make-dir@^3.0.2: +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -9661,10 +9640,10 @@ make-error@1.x, make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -make-event-props@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-event-props/-/make-event-props-1.3.0.tgz#2434cb390d58bcf40898d009ef5b1f936de9671b" - integrity sha512-oWiDZMcVB1/A487251hEWza1xzgCzl6MXxe9aF24l5Bt9N9UEbqTqKumEfuuLhmlhRZYnc+suVvW4vUs8bwO7Q== +make-event-props@^1.5.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/make-event-props/-/make-event-props-1.6.1.tgz#1d587017c3f1f3b42719b775af93d5253656ccdd" + integrity sha512-JhvWq/iz1BvlmnPvLJjXv+xnMPJZuychrDC68V+yCGQJn5chcA8rLGKo5EP1XwIKVrigSXKLmbeXAGkf36wdCQ== make-fetch-happen@^8.0.9: version "8.0.14" @@ -9808,20 +9787,17 @@ meow@^8.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" -merge-class-names@^1.1.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/merge-class-names/-/merge-class-names-1.4.2.tgz#78d6d95ab259e7e647252a7988fd25a27d5a8835" - integrity sha512-bOl98VzwCGi25Gcn3xKxnR5p/WrhWFQB59MS/aGENcmUc6iSm96yrFDF0XSNurX9qN4LbJm0R9kfvsQ17i8zCw== - merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= -merge-refs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/merge-refs/-/merge-refs-1.0.0.tgz#388348bce22e623782c6df9d3c4fc55888276120" - integrity sha512-WZ4S5wqD9FCR9hxkLgvcHJCBxzXzy3VVE6p8W2OzxRzB+hLRlcadGE2bW9xp2KSzk10rvp4y+pwwKO6JQVguMg== +merge-refs@^1.1.3: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge-refs/-/merge-refs-1.2.1.tgz#abddc800375395a4a4eb5c45ebf2a52557fdbe34" + integrity sha512-pRPz39HQz2xzHdXAGvtJ9S8aEpNgpUjzb5yPC3ytozodmsHg+9nqgRs7/YOmn9fM/TLzntAC8AdGTidKxOq9TQ== + dependencies: + "@types/react" "*" merge-stream@^1.0.1: version "1.0.1" @@ -9906,6 +9882,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -10009,6 +9990,11 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: dependencies: yallist "^4.0.0" +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -10125,6 +10111,11 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" +nan@^2.17.0: + version "2.17.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== + nano-css@^5.3.1: version "5.3.4" resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.4.tgz#40af6a83a76f84204f346e8ccaa9169cdae9167b" @@ -10558,6 +10549,16 @@ npmlog@^4.0.1, npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -11050,6 +11051,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path2d-polyfill@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz#24c554a738f42700d6961992bf5f1049672f2391" + integrity sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA== + pathval@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" @@ -11073,17 +11079,15 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pdfjs-dist@2.12.313: - version "2.12.313" - resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.12.313.tgz#62f2273737bb956267ae2e02cdfaddcb1099819c" - integrity sha512-1x6iXO4Qnv6Eb+YFdN5JdUzt4pAkxSp3aLAYPX93eQCyg/m7QFzXVWJHJVtoW48CI8HCXju4dSkhQZwoheL5mA== - -pdfjs-dist@^2.10.377: - version "2.13.216" - resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.13.216.tgz#251a11c9c8c6db19baacd833a4e6986c517d1ab3" - integrity sha512-qn/9a/3IHIKZarTK6ajeeFXBkG15Lg1Fx99PxU09PAU2i874X8mTcHJYyDJxu7WDfNhV6hM7bRQBZU384anoqQ== +pdfjs-dist@3.6.172: + version "3.6.172" + resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-3.6.172.tgz#f9efdfc5e850e1fecfc70b7f6f45c5dc990d8096" + integrity sha512-bfOhCg+S9DXh/ImWhWYTOiq3aVMFSCvzGiBzsIJtdMC71kVWDBw7UXr32xh0y56qc5wMVylIeqV3hBaRsu+e+w== dependencies: - web-streams-polyfill "^3.2.0" + path2d-polyfill "^2.0.1" + web-streams-polyfill "^3.2.1" + optionalDependencies: + canvas "^2.11.2" performance-now@^2.1.0: version "2.1.0" @@ -11501,11 +11505,6 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== -ramda@^0.26.1: - version "0.26.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" - integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== - ramda@^0.27.1: version "0.27.2" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.2.tgz#84463226f7f36dc33592f6f4ed6374c48306c3f1" @@ -11645,18 +11644,16 @@ react-merge-refs@1.1.0: resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== -react-pdf@^5.7.2: - version "5.7.2" - resolved "https://registry.yarnpkg.com/react-pdf/-/react-pdf-5.7.2.tgz#c458dedf7983822668b40dcac1eae052c1f6e056" - integrity sha512-hdDwvf007V0i2rPCqQVS1fa70CXut17SN3laJYlRHzuqcu8sLLjEoeXihty6c0Ev5g1mw31b8OT8EwRw1s8C4g== +react-pdf@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/react-pdf/-/react-pdf-7.0.3.tgz#3d566cef40be8a761d33e53226cba8aeca131214" + integrity sha512-S+xF0dWo1mebcTgOpVejAdNscYY8MkoTantTFNJwvCs76ENZhoKXNS9AEPaa4/Aqw+01ByeejPX7RX4ypN3yHQ== dependencies: - "@babel/runtime" "^7.0.0" - file-loader "^6.0.0" + clsx "^1.2.1" make-cancellable-promise "^1.0.0" - make-event-props "^1.1.0" - merge-class-names "^1.1.1" - merge-refs "^1.0.0" - pdfjs-dist "2.12.313" + make-event-props "^1.5.0" + merge-refs "^1.1.3" + pdfjs-dist "3.6.172" prop-types "^15.6.2" tiny-invariant "^1.0.0" tiny-warning "^1.0.0" @@ -11678,16 +11675,6 @@ react-query@^3.34.0: broadcast-channel "^3.4.1" match-sorter "^6.0.2" -react-reconciler@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.23.0.tgz#5f0bfc35dda030b0220c07de11f93131c5d6db63" - integrity sha512-vV0KlLimP9a/NuRcM6GRVakkmT6MKSzhfo8K72fjHMnlXMOhz9GlPe+/tCp5CWBkg+lsMUt/CR1nypJBTPfwuw== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.17.0" - react-refresh@0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -11930,6 +11917,11 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" @@ -11947,7 +11939,7 @@ regexp-tree@^0.1.21, regexp-tree@^0.1.24, regexp-tree@~0.1.1: resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.24.tgz#3d6fa238450a4d66e5bc9c4c14bb720e2196829d" integrity sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw== -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.1: +regexp.prototype.flags@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== @@ -12123,11 +12115,6 @@ resolve.exports@^1.1.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= - resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" @@ -12153,12 +12140,10 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -restructure@^0.5.3: - version "0.5.4" - resolved "https://registry.yarnpkg.com/restructure/-/restructure-0.5.4.tgz#f54e7dd563590fb34fd6bf55876109aeccb28de8" - integrity sha1-9U591WNZD7NP1r9Vh2EJrsyyjeg= - dependencies: - browserify-optional "^1.0.0" +restructure@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/restructure/-/restructure-3.0.0.tgz#a55031d7ed3314bf585f815836fff9da3d65101d" + integrity sha512-Xj8/MEIhhfj9X2rmD9iJ4Gga9EFqVlpMj3vfLnV2r/Mh5jRMryNV+6lWh9GdJtDBcBSPIqzRdfBQ3wDtNFv/uw== retry@^0.12.0: version "0.12.0" @@ -12288,15 +12273,6 @@ scheduler@^0.21.0: dependencies: loose-envify "^1.1.0" -schema-utils@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - screenfull@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba" @@ -12465,6 +12441,15 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== +simple-get@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55" + integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-get@^4.0.0, simple-get@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" @@ -12640,7 +12625,7 @@ source-map@0.8.0-beta.0: dependencies: whatwg-url "^7.0.0" -source-map@^0.1.38, source-map@~0.1.30: +source-map@^0.1.38: version "0.1.43" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" integrity sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y= @@ -13173,6 +13158,18 @@ tar@^6.0.2, tar@^6.1.0: mkdirp "^1.0.3" yallist "^4.0.0" +tar@^6.1.11: + version "6.1.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" + integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -13529,7 +13526,7 @@ timers-browserify@2.0.12, timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -tiny-inflate@^1.0.0, tiny-inflate@^1.0.2: +tiny-inflate@^1.0.0, tiny-inflate@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== @@ -13741,6 +13738,11 @@ tslib@^2.0.3, tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +tslib@^2.4.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338" + integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -13907,15 +13909,23 @@ unicode-match-property-value-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== +unicode-properties@^1.4.0, unicode-properties@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/unicode-properties/-/unicode-properties-1.4.1.tgz#96a9cffb7e619a0dc7368c28da27e05fc8f9be5f" + integrity sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg== + dependencies: + base64-js "^1.3.0" + unicode-trie "^2.0.0" + unicode-property-aliases-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== -unicode-trie@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-0.3.1.tgz#d671dddd89101a08bac37b6a5161010602052085" - integrity sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU= +unicode-trie@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8" + integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== dependencies: pako "^0.2.5" tiny-inflate "^1.0.0" @@ -14122,6 +14132,15 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite-compatible-readable-stream@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz#27267aebbdc9893c0ddf65a421279cbb1e31d8cd" + integrity sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + vm-browserify@1.1.2, vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" @@ -14206,10 +14225,10 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" -web-streams-polyfill@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" - integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== +web-streams-polyfill@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== webauth@^1.1.0: version "1.1.0" @@ -14335,7 +14354,7 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: +wide-align@^1.1.0, wide-align@^1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== From 493bb2643ed0ed42de6482d4766be52d220a5df1 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 2 Jun 2023 14:59:27 +0300 Subject: [PATCH 026/102] chore: remove weird pdf.js CDN link BREAKING CHANGE: see if pdf views work in production --- .../applicant/src/components/pdfViewer/PdfViewer.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/benefit/applicant/src/components/pdfViewer/PdfViewer.tsx b/frontend/benefit/applicant/src/components/pdfViewer/PdfViewer.tsx index 9694c0fe8d..03d8df09c9 100644 --- a/frontend/benefit/applicant/src/components/pdfViewer/PdfViewer.tsx +++ b/frontend/benefit/applicant/src/components/pdfViewer/PdfViewer.tsx @@ -1,6 +1,6 @@ import { Button } from 'hds-react'; import React from 'react'; -import { Document, Page, pdfjs } from 'react-pdf'; +import { Document, Page } from 'react-pdf'; import { $Grid, $GridCell, @@ -10,12 +10,6 @@ import { useTheme } from 'styled-components'; import { $ActionsWrapper, $ViewerWrapper } from './PdfViewer.sc'; import { usePdfViewer } from './usePdfViewer'; -// TODO: Test if it works in production -// Update as of 2023-01-03: Not sure if this is necessary AT ALL -if (process.env.NODE_ENV === 'production') { - pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; -} - type PdfViewerProps = { file: string; documentMarginLeft?: string; // FIXME: A way to crop pdf white margins will be better because not all pdf have same margins From 37a4714f7af6a98ea2d7d9de82ef0ccb73058438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestil=C3=A4?= Date: Tue, 6 Jun 2023 14:42:28 +0300 Subject: [PATCH 027/102] Validate state_aid_max_percentage on the backend --- .../benefit/calculator/api/v1/serializers.py | 9 ++++ .../calculator/tests/test_calculator_api.py | 51 +++++++++++++++++++ .../benefit/locale/fi/LC_MESSAGES/django.po | 3 ++ 3 files changed, 63 insertions(+) diff --git a/backend/benefit/calculator/api/v1/serializers.py b/backend/benefit/calculator/api/v1/serializers.py index d74be3daa8..26fd36036a 100644 --- a/backend/benefit/calculator/api/v1/serializers.py +++ b/backend/benefit/calculator/api/v1/serializers.py @@ -272,6 +272,15 @@ def validate(self, data): raise serializers.ValidationError( {"end_date": _("End date cannot be empty")} ) + calculation = self.context["request"].data["calculation"] + if calculation["state_aid_max_percentage"] is None: + raise serializers.ValidationError( + { + "state_aid_max_percentage": _( + "State aid maximum percentage cannot be empty" + ) + } + ) return data class Meta: diff --git a/backend/benefit/calculator/tests/test_calculator_api.py b/backend/benefit/calculator/tests/test_calculator_api.py index 26ab9a0e55..3014a90e16 100644 --- a/backend/benefit/calculator/tests/test_calculator_api.py +++ b/backend/benefit/calculator/tests/test_calculator_api.py @@ -1,5 +1,6 @@ import copy import decimal +from datetime import datetime, timedelta from unittest import mock import factory @@ -41,6 +42,26 @@ def _set_two_pay_subsidies_with_empty_dates(data: dict) -> dict: return data +def _set_two_pay_subsidies_with_non_empty_dates(data: dict) -> dict: + start = datetime.now() + timedelta(days=1) + end = datetime.now() + timedelta(weeks=8) + data["pay_subsidies"] = [ + { + "start_date": start.strftime("%Y-%m-%d"), + "end_date": end.strftime("%Y-%m-%d"), + "pay_subsidy_percent": 100, + "work_time_percent": 40, + }, + { + "start_date": start.strftime("%Y-%m-%d"), + "end_date": end.strftime("%Y-%m-%d"), + "pay_subsidy_percent": 40, + "work_time_percent": 40, + }, + ] + return data + + def test_application_retrieve_calculation_as_handler( handler_api_client, handling_application ): @@ -395,6 +416,36 @@ def test_ignore_pay_subsidy_dates_when_application_is_received( assert response.status_code == 200 +def test_subsidies_validation_when_state_aid_max_percentage_is_not_set( + handler_api_client, + mock_get_organisation_roles_and_create_company, +): + with factory.Faker.override_default_locale("fi_FI"): + handling_application = ReceivedApplicationFactory( + status=ApplicationStatus.HANDLING, + apprenticeship_program=False, + benefit_type=BenefitType.SALARY_BENEFIT, + company=mock_get_organisation_roles_and_create_company, + pay_subsidy_granted=True, + pay_subsidy_percent=100, + additional_pay_subsidy_percent=40, + ) + + data = HandlerApplicationSerializer(handling_application).data + _set_two_pay_subsidies_with_non_empty_dates(data) + assert data["calculation"]["state_aid_max_percentage"] is None + + response = handler_api_client.put( + get_handler_detail_url(handling_application), data + ) + + assert response.status_code == 400 + assert "pay_subsidies" in response.json() + assert { + "state_aid_max_percentage": ["State aid maximum percentage cannot be empty"] + } in response.json()["pay_subsidies"] + + @pytest.mark.parametrize("benefit_type", BenefitType.values) @pytest.mark.parametrize( "override_monthly_benefit_amount,override_monthly_benefit_amount_comment", diff --git a/backend/benefit/locale/fi/LC_MESSAGES/django.po b/backend/benefit/locale/fi/LC_MESSAGES/django.po index 4b431a3521..37fd138db4 100644 --- a/backend/benefit/locale/fi/LC_MESSAGES/django.po +++ b/backend/benefit/locale/fi/LC_MESSAGES/django.po @@ -512,6 +512,9 @@ msgstr "Aloituspäivä ei voi olla tyhjä" msgid "End date cannot be empty" msgstr "Päättymispäivä ei voi olla tyhjä" +msgid "State aid maximum percentage cannot be empty" +msgstr "Valtiontuen maksimimäärä ei voi olla tyhjä" + msgid "Description row, amount ignored" msgstr "Kuvausrivi, rahamäärä ohitettu" From de6f864e627adb5826016e988100b7b4d2f5507b Mon Sep 17 00:00:00 2001 From: Maks Turtiainen Date: Wed, 7 Jun 2023 21:54:01 +0300 Subject: [PATCH 028/102] Add Accessibility Statement --- backend/benefit/helsinkibenefit/settings.py | 7 + .../applicant/public/locales/fi/common.json | 102 +++++++++++-- .../src/components/footer/Footer.tsx | 4 +- .../src/components/layout/Layout.tsx | 6 +- .../supportingContent/SupportingContent.sc.ts | 7 - .../supportingContent/SupportingContent.tsx | 25 ---- frontend/benefit/applicant/src/constants.ts | 1 + .../applicant/src/hooks/useUserQuery.ts | 12 +- frontend/benefit/applicant/src/pages/_app.tsx | 21 ++- .../src/pages/accessibility-statement.tsx | 135 ++++++++++++++++++ 10 files changed, 268 insertions(+), 52 deletions(-) delete mode 100644 frontend/benefit/applicant/src/components/supportingContent/SupportingContent.sc.ts delete mode 100644 frontend/benefit/applicant/src/components/supportingContent/SupportingContent.tsx create mode 100644 frontend/benefit/applicant/src/pages/accessibility-statement.tsx diff --git a/backend/benefit/helsinkibenefit/settings.py b/backend/benefit/helsinkibenefit/settings.py index 4193add1cd..0e45bbee58 100644 --- a/backend/benefit/helsinkibenefit/settings.py +++ b/backend/benefit/helsinkibenefit/settings.py @@ -1,4 +1,5 @@ import os +from corsheaders.defaults import default_headers import environ import sentry_sdk @@ -493,3 +494,9 @@ AWS_ACCESS_KEY_ID = env("S3_ACCESS_KEY_ID") AWS_SECRET_ACCESS_KEY = env("S3_SECRET_ACCESS_KEY") AWS_STORAGE_BUCKET_NAME = env("S3_STORAGE_BUCKET_NAME") + +CORS_ALLOW_HEADERS = ( + *default_headers, + "baggage", + "sentry-trace", +) diff --git a/frontend/benefit/applicant/public/locales/fi/common.json b/frontend/benefit/applicant/public/locales/fi/common.json index 74ac09b961..7380bb5ac0 100644 --- a/frontend/benefit/applicant/public/locales/fi/common.json +++ b/frontend/benefit/applicant/public/locales/fi/common.json @@ -548,14 +548,6 @@ "noMessages": "Ei viestejä", "close": "Sulje" }, - "supportingContent": { - "contact": { - "title": "", - "text": "Tarvitsetko apua? Jos jokin asia jäi epäselväksi, voit lähettää meille sähköpostia", - "emailAddress": "helsinkilisa@hel.fi", - "buttonText": "" - } - }, "application": { "tooltipShowInfo": "Näytä info" }, @@ -564,6 +556,98 @@ "home": "Helsinki-lisä - Etusivu", "createApplication": "Helsinki-lisä - Hakemus - Luo uusi", "editApplication": "Helsinki-lisä - Hakemus - Muokkaa hakemusta", - "viewApplication": "Helsinki-lisä - Hakemus - Tarkastele hakemusta" + "viewApplication": "Helsinki-lisä - Hakemus - Tarkastele hakemusta", + "accessibilityStatement": "Helsinki-lisä - Saavutettavuusseloste" + }, + "accessibilityStatement": { + "h1": "Saavutettavuusseloste", + "sections": { + "section1": { + "heading1": "Digitaalinen työllisyyden Helsinki-lisä -asiointipalvelu", + "content1": "Tämä saavutettavuusseloste koskee digitaalista työllisyyden Helsinki-lisä -asiointipalvelua. Palvelun osoite on https://helsinki-lisa.hel.fi" + }, + "section2": { + "heading1": "Helsingin kaupungin tavoite", + "content1": "Digitaalisten palveluiden saavutettavuudessa Helsingin tavoitteena on pyrkiä vähintään WCAG ohjeiston mukaiseen AA- tai sitä parempaan tasoon, mikäli se on kohtuudella mahdollista.", + "content2": "Helsingin tavoitteena on pyrkiä digitaalisten palveluiden saavutettavuudessa vähintään WCAG-ohjeistuksen mukaiseen AA- tai sitä parempaan tasoon." + }, + "section3": { + "heading1": "Helsinki-lisä -asiointipalvelun saavutettavuuden tila", + "content1": "Helsinki-lisä -asiointipalvelu täyttää lain asettamat kriittiset saavutettavuusvaatimukset WCAG v2.1 -tason AA mukaisesti seuraavin havaituin puuttein.", + "heading2": "Ei-saavutettava sisältö, havaitut puutteet ja puutteiden korjaus", + "content2": "Jäljempänä on esitetty havaittuja vielä korjaamattomia puutteita.", + "list": { + "item1": { + "heading": "Ruudunlukuohjelman virheellinen kohdistusjärjestys", + "text": "Kun käyttäjä avaa uuden hakemuksen, ei ruudunlukuohjelman kohdistusta viedä lomakkeen alkuun, vaan kohdistus sijaitsee virheellisesti keskellä Työnantajan tiedot-osiota." + }, + "item2": { + "heading": "Nykyistä työvaihetta ei välitetä avustavalle teknologialle", + "text": "Lomakkeen työvaihelistauksesta ei välity teknistä informaatiota avustavalle teknologialle siitä, missä työvaiheessa käyttäjä parhaillaan on." + }, + "item3": { + "heading": "Useamman virheellisen lomakentän ilmoittaminen", + "text": "Palvelun virheilmoitukset esitetään useimmiten siten, että lomakkeen seuraavaan osioon siirtymisyrityksen yhteydessä käyttäjän selainkohdistus viedään lomakkeen ensimmäiseen virheelliseen lomakekenttään. Jos useammassa kentässä on virhe, muiden virheellisten kohtien löytäminen on hankalaa." + }, + "item4": { + "heading": "Virheilmoitusten tekstit liian yleisluontoisia", + "text": "Monissa kentissä käytettiin samaa virheilmoitusta eli ”Virheellinen arvo”. Tämä virheilmoitus on esim. Etunimi, Sukunimi, Lomaraha ja Työaika tuntia viikossa -kentissä." + }, + "item5": { + "heading": "Päivämäärien asettaminen ei onnistu tekstinsyöttökenttien avulla", + "text": "Ongelman vakavuutta pienentää se, että kalenteritoiminto on saavutettava ruudunlukuohjelmaa ja näppäimistöä käytettäessä." + } + }, + "heading3": "Puutteiden korjaus", + "content3": "Havaittuja saavutettavuuspuutteita pyritään korjaamaan jatkuvasti. Tässä selosteessa havaittujen saavutettavuuspuutteiden listausta päivitetään sen mukaan, kun puutteita saadaan korjattua." + }, + "section4": { + "heading1": "Saavutettavuusselosteen laatiminen", + "content1": "Tämä seloste on päivitetty 05.06.2023." + }, + "section5": { + "heading1": "Saavutettavuuden arviointi", + "content1": "Saavutettavuuden arvioinnissa on noudatettu Helsingin kaupungin työohjetta ja menetelmiä, jotka pyrkivät varmistamaan sivuston saavutettavuuden kaikissa työvaiheissa.", + "content2": "Saavutettavuus on tarkistettu ulkopuolisen asiantuntijan suorittamana auditointina. Ulkopuolisen asiantuntija-auditoinnin on suorittanut Siteimprov 23.3.2022.", + "content3": "Saavutettavuus on tarkistettu käyttäen ohjelmallista saavutettavuustarkistusta sekä sivuston ja sisällön manuaalista tarkistusta. Ohjelmallinen tarkistus on suoritettu käyttäen Siteimproven saavutettavuuden automaattista testaustyökalua ja selainlaajennusta.", + "content4": "Manuaalisessa testauksessa on käytetty Chrome- ja Firefox-selaimia, niiden 200% tiloja sekä tietoteknisiä apuvälineitä, kuten ruudunlukuohjelmia, ohjaimia ja erikoisnäppäimistöjä. Mobiilitestaus toteutettiin iOS- ja Android-käyttöjärjestelmillä ja niille tarkoitetuilla ruudunlukuohjelmilla." + }, + "section6": { + "heading1": "Saavutettavuusselosteen päivittäminen", + "content1": "Sivuston saavutettavuudesta huolehditaan jatkuvalla valvonnalla tekniikan tai sisällön muuttuessa, sekä määräajoin suoritettavalla tarkistuksella. Tätä selostetta päivitetään sivuston muutosten ja saavutettavuuden tarkistusten yhteydessä." + }, + "section7": { + "heading1": "Huomasitko puutteita saavutettavuudessa?", + "content1": "Pyrimme jatkuvasti parantamaan verkkopalvelun saavutettavuutta. Jos löydät ongelmia, joita ei ole kuvattu tällä sivulla, ilmoita niistä meille ja teemme parhaamme puutteiden korjaamiseksi.", + "content2": "Anna palautetta tällä verkkolomakkeella ", + "content3": "Palautekanavan kautta voit myös pyytää saavutettavaan muotoon muokattuja tietoja Hel.fi-sivuston sisällöstä." + }, + "section8": { + "heading1": "Tietojen pyytäminen saavutettavassa muodossa", + "content1": "Mikäli et koe saavasi sivuston sisältöä saavutettavassa muodossa, voit pyytää tietoja palautelomakkeella ", + "content1end": ". Tiedusteluun pyritään vastaamaan kohtuullisessa ajassa." + }, + "section9": { + "heading1": "Saavutettavuuden valvonta", + "content1": "Etelä-Suomen aluehallintovirasto valvoo saavutettavuusvaatimusten toteutumista. Jos et ole tyytyväinen saamaasi vastaukseen tai et saa vastausta lainkaan kahden viikon aikana, voit antaa palautteen Etelä-Suomen aluehallintovirastoon.", + "contact1": "Etelä-Suomen aluehallintovirasto,", + "contact2": "Saavutettavuuden valvonnan yksikkö", + "contact3": "www.saavutettavuusvaatimukset.fi", + "contact4": "saavutettavuus@avi.fi", + "content2": "Helsinki-lisä -asiointi palvelun saavutettavuusselosteesta vastaa Helsingin kaupungin työllisyyspalvelut -yksikkö." + }, + "section10": { + "heading1": "Helsingin kaupunki ja saavutettavuus", + "content1": "Kaupunki edistää digitaalisten palveluiden saavutettavuutta yhdenmukaistamalla julkaisutyötä ja järjestämällä saavutettavuuteen keskittyvää koulutusta henkilökunnalleen.", + "content2": "Sivustojen saavutettavuuden tasoa seurataan jatkuvasti sivustoja ylläpidettäessä. Havaittuihin puutteisiin reagoidaan välittömästi. Tarvittavat muutokset pyritään suorittamaan mahdollisimman nopeasti." + }, + "section11": { + "heading1": "Vammaiset ja avustavien teknologioiden käyttäjät", + "content1": "Kaupunki tarjoaa neuvontaa ja tukea vammaisille ja avustavien teknologioiden käyttäjille. Tukea on saatavilla kaupungin sivuilla ilmoitetuilta neuvontasivuilta sekä puhelinneuvonnasta." + }, + "section12": { + "heading1": "Palaute ja yhteystiedot" + } + } } } diff --git a/frontend/benefit/applicant/src/components/footer/Footer.tsx b/frontend/benefit/applicant/src/components/footer/Footer.tsx index 4579c02a86..d071a29bca 100644 --- a/frontend/benefit/applicant/src/components/footer/Footer.tsx +++ b/frontend/benefit/applicant/src/components/footer/Footer.tsx @@ -1,11 +1,13 @@ import { useTranslation } from 'benefit/applicant/i18n'; import { Footer } from 'hds-react'; import React from 'react'; +import useLocale from 'shared/hooks/useLocale'; import { $FooterWrapper } from './Footer.sc'; const FooterSection: React.FC = () => { const { t } = useTranslation(); + const locale = useLocale(); return ( <$FooterWrapper> @@ -18,7 +20,7 @@ const FooterSection: React.FC = () => { as="a" rel="noopener noreferrer" target="_blank" - href={t('common:footer.accessibilityStatementLink')} + href={`/${locale}/accessibility-statement`} label={t('common:footer.accessibilityStatement')} /> = ({ children, ...rest }) => { setIsTermsOfSerivceApproved={setIsTermsOfSerivceApproved} /> ) : ( - <> - {children} - - + children )}
diff --git a/frontend/benefit/applicant/src/components/supportingContent/SupportingContent.sc.ts b/frontend/benefit/applicant/src/components/supportingContent/SupportingContent.sc.ts deleted file mode 100644 index 4471a2ab83..0000000000 --- a/frontend/benefit/applicant/src/components/supportingContent/SupportingContent.sc.ts +++ /dev/null @@ -1,7 +0,0 @@ -import styled from 'styled-components'; - -export const $SupportingContentSection = styled.div` - margin-top: var(--spacing-m); - padding: var(--spacing-l); - background-color: #ffe977; -`; diff --git a/frontend/benefit/applicant/src/components/supportingContent/SupportingContent.tsx b/frontend/benefit/applicant/src/components/supportingContent/SupportingContent.tsx deleted file mode 100644 index cee65d1dae..0000000000 --- a/frontend/benefit/applicant/src/components/supportingContent/SupportingContent.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; -import { useTranslation } from 'react-i18next'; -import Container from 'shared/components/container/Container'; - -import { $SupportingContentSection } from './SupportingContent.sc'; - -const SupportingContent: React.FC = () => { - const { t } = useTranslation(); - const translationBase = 'common:supportingContent'; - - const emailAddress = t(`${translationBase}.contact.emailAddress`); - const href = `mailto:${emailAddress}`; - - return ( - - <$SupportingContentSection> - {t(`${translationBase}.contact.text`)} - {emailAddress} - . - - - ); -}; - -export default SupportingContent; diff --git a/frontend/benefit/applicant/src/constants.ts b/frontend/benefit/applicant/src/constants.ts index 5b2c0e73a6..016e518f05 100644 --- a/frontend/benefit/applicant/src/constants.ts +++ b/frontend/benefit/applicant/src/constants.ts @@ -12,6 +12,7 @@ export enum ROUTES { APPLICATION_FORM = '/application', LOGIN = '/login', TERMS_OF_SERVICE = '/terms-of-service', + ACCESSIBILITY_STATEMENT = '/accessibility-statement', } export const MAX_DEMINIMIS_AID_TOTAL_AMOUNT = 200_000; diff --git a/frontend/benefit/applicant/src/hooks/useUserQuery.ts b/frontend/benefit/applicant/src/hooks/useUserQuery.ts index afe2c4095a..646f03bed3 100644 --- a/frontend/benefit/applicant/src/hooks/useUserQuery.ts +++ b/frontend/benefit/applicant/src/hooks/useUserQuery.ts @@ -23,6 +23,16 @@ const useUserQuery = ( const locale = useLocale(); const { axios, handleResponse } = useBackendAPI(); + const isEnabled = (): boolean => { + if (logout) { + return false; + } + if (router.route === '/accessibility-statement') { + return false; + } + return true; + }; + const handleError = (error: Error): void => { if (logout) { void router.push(`${locale}/login?logout=true`); @@ -41,7 +51,7 @@ const useUserQuery = ( () => handleResponse(axios.get(BackendEndpoint.USER_ME)), { refetchInterval: FIVE_MINUTES, - enabled: !logout, + enabled: isEnabled(), retry: false, select: (data) => camelcaseKeys(data, { deep: true }), onError: (error) => handleError(error), diff --git a/frontend/benefit/applicant/src/pages/_app.tsx b/frontend/benefit/applicant/src/pages/_app.tsx index 6ea4ba2f86..97c401d21d 100644 --- a/frontend/benefit/applicant/src/pages/_app.tsx +++ b/frontend/benefit/applicant/src/pages/_app.tsx @@ -33,10 +33,23 @@ const App: React.FC = (appProps) => { useEffect(() => { setAppLoaded(); - if (router.route === ROUTES.HOME) - document.title = t('common:pageTitles.home'); - else if (router.route === ROUTES.LOGIN) - document.title = t('common:pageTitles.login'); + switch (router.route) { + case ROUTES.HOME: + document.title = t('common:pageTitles.home'); + break; + + case ROUTES.LOGIN: + document.title = t('common:pageTitles.login'); + break; + + case ROUTES.ACCESSIBILITY_STATEMENT: + document.title = t('common:pageTitles.accessibilityStatement'); + break; + + default: + document.title = t('common:pageTitles.home'); + break; + } }, [router, t]); return ( diff --git a/frontend/benefit/applicant/src/pages/accessibility-statement.tsx b/frontend/benefit/applicant/src/pages/accessibility-statement.tsx new file mode 100644 index 0000000000..1c1a9dab11 --- /dev/null +++ b/frontend/benefit/applicant/src/pages/accessibility-statement.tsx @@ -0,0 +1,135 @@ +import { useTranslation } from 'benefit/applicant/i18n'; +import { GetStaticProps, NextPage } from 'next'; +import Head from 'next/head'; +import * as React from 'react'; +import Container from 'shared/components/container/Container'; +import useLocale from 'shared/hooks/useLocale'; +import getServerSideTranslations from 'shared/i18n/get-server-side-translations'; +import styled from 'styled-components'; + +const $TextWrapper = styled.div` + display: grid; + grid-template-columns: repeat(12, 1fr); +`; + +const $TextContainer = styled.main` + grid-column: span 8; +`; + +const AccessibilityStatement: NextPage = () => { + const { t } = useTranslation(); + const locale = useLocale(); + const tBase = 'common:accessibilityStatement'; + + const FeedbackLink: React.FC = () => ( + + palautteet.hel.fi + + ); + + return ( + <> + + {t('common:appName')} + + + + <$TextWrapper> + <$TextContainer> +

{t(`${tBase}.h1`)}

+
+

{t(`${tBase}.sections.section1.heading1`)}

+

{t(`${tBase}.sections.section1.content1`)}

+
+
+

{t(`${tBase}.sections.section2.heading1`)}

+

{t(`${tBase}.sections.section2.content1`)}

+

{t(`${tBase}.sections.section2.content2`)}

+
+
+

{t(`${tBase}.sections.section3.heading1`)}

+

{t(`${tBase}.sections.section3.content1`)}

+

{t(`${tBase}.sections.section3.heading2`)}

+

{t(`${tBase}.sections.section3.content2`)}

+
    + {[1, 2, 3, 4, 5].map((num) => ( +
  1. + {t(`${tBase}.sections.section3.list.item${num}.heading`)} +

    + {t(`${tBase}.sections.section3.list.item${num}.text`)} +

    +
  2. + ))} +
+
+
+

{t(`${tBase}.sections.section4.heading1`)}

+

{t(`${tBase}.sections.section4.content1`)}

+
+
+

{t(`${tBase}.sections.section5.heading1`)}

+

{t(`${tBase}.sections.section5.content1`)}

+

{t(`${tBase}.sections.section5.content2`)}

+

{t(`${tBase}.sections.section5.content3`)}

+

{t(`${tBase}.sections.section5.content4`)}

+
+
+

{t(`${tBase}.sections.section6.heading1`)}

+

{t(`${tBase}.sections.section6.content1`)}

+
+
+

{t(`${tBase}.sections.section7.heading1`)}

+

{t(`${tBase}.sections.section7.content1`)}

+

+ {t(`${tBase}.sections.section7.content2`)}( + + ). +

+

{t(`${tBase}.sections.section7.content3`)}

+
+
+

{t(`${tBase}.sections.section8.heading1`)}

+

+ {t(`${tBase}.sections.section8.content1`)} + + {t(`${tBase}.sections.section8.content1end`)} +

+
+
+

{t(`${tBase}.sections.section9.heading1`)}

+

{t(`${tBase}.sections.section9.content1`)}

+

+ {t(`${tBase}.sections.section9.contact1`)} +
+ {t(`${tBase}.sections.section9.contact2`)} +
+ {t(`${tBase}.sections.section9.contact3`)} +
+ {t(`${tBase}.sections.section9.contact4`)} +

+

{t(`${tBase}.sections.section9.content2`)}

+
+
+

{t(`${tBase}.sections.section10.heading1`)}

+

{t(`${tBase}.sections.section10.content1`)}

+

{t(`${tBase}.sections.section10.content2`)}

+
+
+

{t(`${tBase}.sections.section11.heading1`)}

+

{t(`${tBase}.sections.section11.content1`)}

+
+ + +
+ + ); +}; + +export const getStaticProps: GetStaticProps = + getServerSideTranslations('common'); + +export default AccessibilityStatement; From eee21466af2723ad7d6213349b3bda026d73607b Mon Sep 17 00:00:00 2001 From: Maks Turtiainen Date: Thu, 8 Jun 2023 20:09:32 +0300 Subject: [PATCH 029/102] Applicant login page modifications --- .../applicant/public/locales/en/common.json | 110 +++++++++++++++- .../applicant/public/locales/fi/common.json | 16 ++- .../applicant/public/locales/sv/common.json | 118 ++++++++++++++++-- .../src/components/layout/Layout.sc.ts | 11 +- .../src/components/layout/Layout.tsx | 6 +- .../src/components/pages/Pages.sc.ts | 23 ++++ .../src/pages/accessibility-statement.tsx | 22 ++-- .../benefit/applicant/src/pages/login.tsx | 104 ++++++++++----- 8 files changed, 346 insertions(+), 64 deletions(-) create mode 100644 frontend/benefit/applicant/src/components/pages/Pages.sc.ts diff --git a/frontend/benefit/applicant/public/locales/en/common.json b/frontend/benefit/applicant/public/locales/en/common.json index 070d5ea547..4f15fc2ad7 100644 --- a/frontend/benefit/applicant/public/locales/en/common.json +++ b/frontend/benefit/applicant/public/locales/en/common.json @@ -470,12 +470,20 @@ "login": { "login": "Sign in to the service", "logoutMessageLabel": "You have logged out", - "errorLabel": "An unexpected error occurred. Please, sign in again. ", + "errorLabel": "An unexpected error occurred. Please, sign in again.", "sessionExpiredLabel": "User session expired. Sign in again", - "infoLabel": "Using the service requires strong authentication", + "infoLabel": "Problems logging in?", "logoutInfoContent": "You have been logged out. If you want to continue to use the service, you have to log in again click the button here below.", - "infoContent": "You should identify yourself to be able to advance to the service. You can choose yourself which identification method you want to use. Click on the bottom below to identify yourself.", - "termsOfServiceHeader": "Information about the processing of the employer representatives’ personal data" + "infoContent": "Send us an email to helsinkilisa@hel.fi.", + "termsOfServiceHeader": "Information about the processing of the employer representatives’ personal data", + "header": "Log in to the Helsinki benefit service", + "infoText1": "Apply for Helsinki benefit, a discretionary support for a private or third-sector employer that hires an unemployed Helsinki resident. You need a Suomi.fi e-Authorization to do business on behalf of your organization.", + "smallHeader1": "Log in with you bank access codes or mobile certificate", + "infoText2": "When you log in for the first time, your own Helsinki profile is automatically created for you.", + "smallHeader2": "Acquire Suomi.fi e-Authorization", + "infoText3": "If you do not yet have the right to do business on behalf of your organization, you can get yourself a Suomi.fi e-Authorization.", + "authorization": "See the instructions for obtaining authorization", + "suomifiUrl": "https://www.suomi.fi/e-authorizations" }, "errorPage": { "title": "An error has unfortunately occurred", @@ -564,6 +572,98 @@ "home": "Helsinki benefit - Frontpage", "createApplication": "Helsinki benefit - Application - Create", "editApplication": "Helsinki benefit - Application - Edit", - "viewApplication": "Helsinki benefit - Application - View" + "viewApplication": "Helsinki benefit - Application - View", + "accessibilityStatement": "Helsinki benefit - Accessibility statement" + }, + "accessibilityStatement": { + "h1": "Saavutettavuusseloste", + "sections": { + "section1": { + "heading1": "Digitaalinen työllisyyden Helsinki-lisä -asiointipalvelu", + "content1": "Tämä saavutettavuusseloste koskee digitaalista työllisyyden Helsinki-lisä -asiointipalvelua. Palvelun osoite on https://helsinki-lisa.hel.fi" + }, + "section2": { + "heading1": "Helsingin kaupungin tavoite", + "content1": "Digitaalisten palveluiden saavutettavuudessa Helsingin tavoitteena on pyrkiä vähintään WCAG ohjeiston mukaiseen AA- tai sitä parempaan tasoon, mikäli se on kohtuudella mahdollista.", + "content2": "Helsingin tavoitteena on pyrkiä digitaalisten palveluiden saavutettavuudessa vähintään WCAG-ohjeistuksen mukaiseen AA- tai sitä parempaan tasoon." + }, + "section3": { + "heading1": "Helsinki-lisä -asiointipalvelun saavutettavuuden tila", + "content1": "Helsinki-lisä -asiointipalvelu täyttää lain asettamat kriittiset saavutettavuusvaatimukset WCAG v2.1 -tason AA mukaisesti seuraavin havaituin puuttein.", + "heading2": "Ei-saavutettava sisältö, havaitut puutteet ja puutteiden korjaus", + "content2": "Jäljempänä on esitetty havaittuja vielä korjaamattomia puutteita.", + "list": { + "item1": { + "heading": "Ruudunlukuohjelman virheellinen kohdistusjärjestys", + "text": "Kun käyttäjä avaa uuden hakemuksen, ei ruudunlukuohjelman kohdistusta viedä lomakkeen alkuun, vaan kohdistus sijaitsee virheellisesti keskellä Työnantajan tiedot-osiota." + }, + "item2": { + "heading": "Nykyistä työvaihetta ei välitetä avustavalle teknologialle", + "text": "Lomakkeen työvaihelistauksesta ei välity teknistä informaatiota avustavalle teknologialle siitä, missä työvaiheessa käyttäjä parhaillaan on." + }, + "item3": { + "heading": "Useamman virheellisen lomakentän ilmoittaminen", + "text": "Palvelun virheilmoitukset esitetään useimmiten siten, että lomakkeen seuraavaan osioon siirtymisyrityksen yhteydessä käyttäjän selainkohdistus viedään lomakkeen ensimmäiseen virheelliseen lomakekenttään. Jos useammassa kentässä on virhe, muiden virheellisten kohtien löytäminen on hankalaa." + }, + "item4": { + "heading": "Virheilmoitusten tekstit liian yleisluontoisia", + "text": "Monissa kentissä käytettiin samaa virheilmoitusta eli ”Virheellinen arvo”. Tämä virheilmoitus on esim. Etunimi, Sukunimi, Lomaraha ja Työaika tuntia viikossa -kentissä." + }, + "item5": { + "heading": "Päivämäärien asettaminen ei onnistu tekstinsyöttökenttien avulla", + "text": "Ongelman vakavuutta pienentää se, että kalenteritoiminto on saavutettava ruudunlukuohjelmaa ja näppäimistöä käytettäessä." + } + }, + "heading3": "Puutteiden korjaus", + "content3": "Havaittuja saavutettavuuspuutteita pyritään korjaamaan jatkuvasti. Tässä selosteessa havaittujen saavutettavuuspuutteiden listausta päivitetään sen mukaan, kun puutteita saadaan korjattua." + }, + "section4": { + "heading1": "Saavutettavuusselosteen laatiminen", + "content1": "Tämä seloste on päivitetty 05.06.2023." + }, + "section5": { + "heading1": "Saavutettavuuden arviointi", + "content1": "Saavutettavuuden arvioinnissa on noudatettu Helsingin kaupungin työohjetta ja menetelmiä, jotka pyrkivät varmistamaan sivuston saavutettavuuden kaikissa työvaiheissa.", + "content2": "Saavutettavuus on tarkistettu ulkopuolisen asiantuntijan suorittamana auditointina. Ulkopuolisen asiantuntija-auditoinnin on suorittanut Siteimprov 23.3.2022.", + "content3": "Saavutettavuus on tarkistettu käyttäen ohjelmallista saavutettavuustarkistusta sekä sivuston ja sisällön manuaalista tarkistusta. Ohjelmallinen tarkistus on suoritettu käyttäen Siteimproven saavutettavuuden automaattista testaustyökalua ja selainlaajennusta.", + "content4": "Manuaalisessa testauksessa on käytetty Chrome- ja Firefox-selaimia, niiden 200% tiloja sekä tietoteknisiä apuvälineitä, kuten ruudunlukuohjelmia, ohjaimia ja erikoisnäppäimistöjä. Mobiilitestaus toteutettiin iOS- ja Android-käyttöjärjestelmillä ja niille tarkoitetuilla ruudunlukuohjelmilla." + }, + "section6": { + "heading1": "Saavutettavuusselosteen päivittäminen", + "content1": "Sivuston saavutettavuudesta huolehditaan jatkuvalla valvonnalla tekniikan tai sisällön muuttuessa, sekä määräajoin suoritettavalla tarkistuksella. Tätä selostetta päivitetään sivuston muutosten ja saavutettavuuden tarkistusten yhteydessä." + }, + "section7": { + "heading1": "Huomasitko puutteita saavutettavuudessa?", + "content1": "Pyrimme jatkuvasti parantamaan verkkopalvelun saavutettavuutta. Jos löydät ongelmia, joita ei ole kuvattu tällä sivulla, ilmoita niistä meille ja teemme parhaamme puutteiden korjaamiseksi.", + "content2": "Anna palautetta tällä verkkolomakkeella ", + "content3": "Palautekanavan kautta voit myös pyytää saavutettavaan muotoon muokattuja tietoja Hel.fi-sivuston sisällöstä." + }, + "section8": { + "heading1": "Tietojen pyytäminen saavutettavassa muodossa", + "content1": "Mikäli et koe saavasi sivuston sisältöä saavutettavassa muodossa, voit pyytää tietoja palautelomakkeella ", + "content1end": ". Tiedusteluun pyritään vastaamaan kohtuullisessa ajassa." + }, + "section9": { + "heading1": "Saavutettavuuden valvonta", + "content1": "Etelä-Suomen aluehallintovirasto valvoo saavutettavuusvaatimusten toteutumista. Jos et ole tyytyväinen saamaasi vastaukseen tai et saa vastausta lainkaan kahden viikon aikana, voit antaa palautteen Etelä-Suomen aluehallintovirastoon.", + "contact1": "Etelä-Suomen aluehallintovirasto,", + "contact2": "Saavutettavuuden valvonnan yksikkö", + "contact3": "www.saavutettavuusvaatimukset.fi", + "contact4": "saavutettavuus@avi.fi", + "content2": "Helsinki-lisä -asiointi palvelun saavutettavuusselosteesta vastaa Helsingin kaupungin työllisyyspalvelut -yksikkö." + }, + "section10": { + "heading1": "Helsingin kaupunki ja saavutettavuus", + "content1": "Kaupunki edistää digitaalisten palveluiden saavutettavuutta yhdenmukaistamalla julkaisutyötä ja järjestämällä saavutettavuuteen keskittyvää koulutusta henkilökunnalleen.", + "content2": "Sivustojen saavutettavuuden tasoa seurataan jatkuvasti sivustoja ylläpidettäessä. Havaittuihin puutteisiin reagoidaan välittömästi. Tarvittavat muutokset pyritään suorittamaan mahdollisimman nopeasti." + }, + "section11": { + "heading1": "Vammaiset ja avustavien teknologioiden käyttäjät", + "content1": "Kaupunki tarjoaa neuvontaa ja tukea vammaisille ja avustavien teknologioiden käyttäjille. Tukea on saatavilla kaupungin sivuilla ilmoitetuilta neuvontasivuilta sekä puhelinneuvonnasta." + }, + "section12": { + "heading1": "Palaute ja yhteystiedot" + } + } } } diff --git a/frontend/benefit/applicant/public/locales/fi/common.json b/frontend/benefit/applicant/public/locales/fi/common.json index 7380bb5ac0..713560a651 100644 --- a/frontend/benefit/applicant/public/locales/fi/common.json +++ b/frontend/benefit/applicant/public/locales/fi/common.json @@ -468,14 +468,22 @@ }, "select": "Valitse", "login": { - "login": "Kirjaudu palveluun", + "login": "Kirjaudu sisään", "logoutMessageLabel": "Olet kirjautunut ulos", "errorLabel": "Tapahtui tuntematon virhe. Kirjaudu uudelleen sisään", "sessionExpiredLabel": "Käyttäjäsessio vanhentui. Kirjaudu uudelleen sisään", - "infoLabel": "Palvelun käyttäminen edellyttää vahvaa tunnistautumista", + "infoLabel": "Ongelmia kirjautumisessa?", "logoutInfoContent": "Sinut on kirjattu ulos. Jos haluat jatkaa asiointia palvelussa, kirjaudu uudelleen sisään klikkaamalla alla olevasta painikkeesta.", - "infoContent": "Jotta voit edetä palvelussa, sinun täytyy tunnistautua. Voit valita itse, mitä tunnistustapaa haluat käyttää. Tunnistustietosi kulkevat suojatussa yhteydessä. Klikkaa alla olevasta painikkeesta tunnistautuaksesi.", - "termsOfServiceHeader": "Tietoa työnantajan edustajien henkilötietojen käsittelystä" + "infoContent": "Lähetä meille sähköposti osoitteeseen helsinkilisa@hel.fi.", + "termsOfServiceHeader": "Tietoa työnantajan edustajien henkilötietojen käsittelystä", + "header": "Kirjaudu Helsinki-lisän asiointipalveluun", + "infoText1": "Hae organisaatiollesi taloudellista tukea, Helsinki-lisää, työttömän helsinkiläisen työllistämiseen. Tarvitset Suomi.fi-valtuutuksen asioimiseen organisaatiosi puolesta.", + "smallHeader1": "Kirjaudu pankkitunnuksilla tai mobiilivarmenteella", + "infoText2": "Kun kirjaudut ensimmäisen kerran, sinulle luodaan automaattisesti oma Helsinki-profiili tunnus.", + "smallHeader2": "Hanki Suomi.fi -valtuutus", + "infoText3": "Jos sinulla ei ole vielä oikeutta asioida organisaatiosi puolesta, voit hankkia itsellesi Suomi.fi -valtuuden.", + "authorization": "Katso ohjeet valtuutuksen hankkimiseen", + "suomifiUrl": "https://www.suomi.fi/valtuudet" }, "errorPage": { "title": "Palvelussa on valitettavasti tapahtunut virhe", diff --git a/frontend/benefit/applicant/public/locales/sv/common.json b/frontend/benefit/applicant/public/locales/sv/common.json index 6ba1adccc9..f86e0c3be2 100644 --- a/frontend/benefit/applicant/public/locales/sv/common.json +++ b/frontend/benefit/applicant/public/locales/sv/common.json @@ -391,7 +391,7 @@ }, "applicationSaved": { "label": "Applikationen sparas", - "message": "Helsinforstillägg applikationen {{applicationNumber}} {{applicantName}} sparas." + "message": "Helsingforstillägg applikationen {{applicationNumber}} {{applicantName}} sparas." }, "applicationDeleted": { "label": "Utkast raderat", @@ -472,10 +472,18 @@ "logoutMessageLabel": "Du har loggat ut", "errorLabel": "Ett okänt fel inträffade. Logga in på nytt.", "sessionExpiredLabel": "Användarsessionen föråldrades. Logga in på nytt.", - "infoLabel": "Att använda tjänsten kräver stark autentisering", + "infoLabel": "Problem med att logga in?", "logoutInfoContent": "Du har nu loggats ut. Om du vill fortsätta använda tjänsten, logga in på nytt genom att klicka på knappen nedan.", - "infoContent": "Du bör identifiera dig för att kunna avancera till tjänsten. Du kan själv välja det identifieringssätt du vill använda. Identifieringsuppgifterna rör sig i en säker anslutning. Klicka på nedanstående knapp för att identifiera dig.", - "termsOfServiceHeader": "Information om behandlingen av arbetsgivarens representanters uppgifter" + "infoContent": "Skicka ett mejl till helsinkilisa@hel.fi.", + "termsOfServiceHeader": "Information om behandlingen av arbetsgivarens representanters uppgifter", + "header": "Logga in i Helsingforstillägg tjänsten", + "infoText1": "Sök ekonomiskt stöd, Helsingforstillägg, för att sysselsätta arbetslösa helsingforsbor. Du behöver Suomi.fi-fullmakter för att utföra ärenden för din organisation.", + "smallHeader1": "Logga in med bankkoder eller mobilt certifikat", + "infoText2": "När du loggar in för första gången skapas automatiskt en egen Helsingforsprofil för dig.", + "smallHeader2": "Skaffa Suomi.fi-fullmakter", + "infoText3": "Om du ännu inte har rättighet att handla på din organisations vägnar kan du skaffa Suomi.fi-fullmakter för dig själv.", + "authorization": "Se instruktioner för att skaffa autorisering", + "suomifiUrl": "https://www.suomi.fi/fullmakter" }, "errorPage": { "title": "Tyvärr har det inträffat ett fel i tjänsten", @@ -560,10 +568,102 @@ "tooltipShowInfo": "Visa information" }, "pageTitles": { - "login": "Helsinforstillägg - Logga in", - "home": "Helsinforstillägg - Framsida", - "createApplication": "Helsinforstillägg - Ansökan - Skapa", - "editApplication": "Helsinforstillägg - Ansökan - Redigera", - "viewApplication": "Helsinforstillägg - Ansökan - Granska" + "login": "Helsingforstillägg - Logga in", + "home": "Helsingforstillägg - Framsida", + "createApplication": "Helsingforstillägg - Ansökan - Skapa", + "editApplication": "Helsingforstillägg - Ansökan - Redigera", + "viewApplication": "Helsingforstillägg - Ansökan - Granska", + "accessibilityStatement": "Helsingforstillägg - Tillgänglighetsförklaring" + }, + "accessibilityStatement": { + "h1": "Saavutettavuusseloste", + "sections": { + "section1": { + "heading1": "Digitaalinen työllisyyden Helsinki-lisä -asiointipalvelu", + "content1": "Tämä saavutettavuusseloste koskee digitaalista työllisyyden Helsinki-lisä -asiointipalvelua. Palvelun osoite on https://helsinki-lisa.hel.fi" + }, + "section2": { + "heading1": "Helsingin kaupungin tavoite", + "content1": "Digitaalisten palveluiden saavutettavuudessa Helsingin tavoitteena on pyrkiä vähintään WCAG ohjeiston mukaiseen AA- tai sitä parempaan tasoon, mikäli se on kohtuudella mahdollista.", + "content2": "Helsingin tavoitteena on pyrkiä digitaalisten palveluiden saavutettavuudessa vähintään WCAG-ohjeistuksen mukaiseen AA- tai sitä parempaan tasoon." + }, + "section3": { + "heading1": "Helsinki-lisä -asiointipalvelun saavutettavuuden tila", + "content1": "Helsinki-lisä -asiointipalvelu täyttää lain asettamat kriittiset saavutettavuusvaatimukset WCAG v2.1 -tason AA mukaisesti seuraavin havaituin puuttein.", + "heading2": "Ei-saavutettava sisältö, havaitut puutteet ja puutteiden korjaus", + "content2": "Jäljempänä on esitetty havaittuja vielä korjaamattomia puutteita.", + "list": { + "item1": { + "heading": "Ruudunlukuohjelman virheellinen kohdistusjärjestys", + "text": "Kun käyttäjä avaa uuden hakemuksen, ei ruudunlukuohjelman kohdistusta viedä lomakkeen alkuun, vaan kohdistus sijaitsee virheellisesti keskellä Työnantajan tiedot-osiota." + }, + "item2": { + "heading": "Nykyistä työvaihetta ei välitetä avustavalle teknologialle", + "text": "Lomakkeen työvaihelistauksesta ei välity teknistä informaatiota avustavalle teknologialle siitä, missä työvaiheessa käyttäjä parhaillaan on." + }, + "item3": { + "heading": "Useamman virheellisen lomakentän ilmoittaminen", + "text": "Palvelun virheilmoitukset esitetään useimmiten siten, että lomakkeen seuraavaan osioon siirtymisyrityksen yhteydessä käyttäjän selainkohdistus viedään lomakkeen ensimmäiseen virheelliseen lomakekenttään. Jos useammassa kentässä on virhe, muiden virheellisten kohtien löytäminen on hankalaa." + }, + "item4": { + "heading": "Virheilmoitusten tekstit liian yleisluontoisia", + "text": "Monissa kentissä käytettiin samaa virheilmoitusta eli ”Virheellinen arvo”. Tämä virheilmoitus on esim. Etunimi, Sukunimi, Lomaraha ja Työaika tuntia viikossa -kentissä." + }, + "item5": { + "heading": "Päivämäärien asettaminen ei onnistu tekstinsyöttökenttien avulla", + "text": "Ongelman vakavuutta pienentää se, että kalenteritoiminto on saavutettava ruudunlukuohjelmaa ja näppäimistöä käytettäessä." + } + }, + "heading3": "Puutteiden korjaus", + "content3": "Havaittuja saavutettavuuspuutteita pyritään korjaamaan jatkuvasti. Tässä selosteessa havaittujen saavutettavuuspuutteiden listausta päivitetään sen mukaan, kun puutteita saadaan korjattua." + }, + "section4": { + "heading1": "Saavutettavuusselosteen laatiminen", + "content1": "Tämä seloste on päivitetty 05.06.2023." + }, + "section5": { + "heading1": "Saavutettavuuden arviointi", + "content1": "Saavutettavuuden arvioinnissa on noudatettu Helsingin kaupungin työohjetta ja menetelmiä, jotka pyrkivät varmistamaan sivuston saavutettavuuden kaikissa työvaiheissa.", + "content2": "Saavutettavuus on tarkistettu ulkopuolisen asiantuntijan suorittamana auditointina. Ulkopuolisen asiantuntija-auditoinnin on suorittanut Siteimprov 23.3.2022.", + "content3": "Saavutettavuus on tarkistettu käyttäen ohjelmallista saavutettavuustarkistusta sekä sivuston ja sisällön manuaalista tarkistusta. Ohjelmallinen tarkistus on suoritettu käyttäen Siteimproven saavutettavuuden automaattista testaustyökalua ja selainlaajennusta.", + "content4": "Manuaalisessa testauksessa on käytetty Chrome- ja Firefox-selaimia, niiden 200% tiloja sekä tietoteknisiä apuvälineitä, kuten ruudunlukuohjelmia, ohjaimia ja erikoisnäppäimistöjä. Mobiilitestaus toteutettiin iOS- ja Android-käyttöjärjestelmillä ja niille tarkoitetuilla ruudunlukuohjelmilla." + }, + "section6": { + "heading1": "Saavutettavuusselosteen päivittäminen", + "content1": "Sivuston saavutettavuudesta huolehditaan jatkuvalla valvonnalla tekniikan tai sisällön muuttuessa, sekä määräajoin suoritettavalla tarkistuksella. Tätä selostetta päivitetään sivuston muutosten ja saavutettavuuden tarkistusten yhteydessä." + }, + "section7": { + "heading1": "Huomasitko puutteita saavutettavuudessa?", + "content1": "Pyrimme jatkuvasti parantamaan verkkopalvelun saavutettavuutta. Jos löydät ongelmia, joita ei ole kuvattu tällä sivulla, ilmoita niistä meille ja teemme parhaamme puutteiden korjaamiseksi.", + "content2": "Anna palautetta tällä verkkolomakkeella ", + "content3": "Palautekanavan kautta voit myös pyytää saavutettavaan muotoon muokattuja tietoja Hel.fi-sivuston sisällöstä." + }, + "section8": { + "heading1": "Tietojen pyytäminen saavutettavassa muodossa", + "content1": "Mikäli et koe saavasi sivuston sisältöä saavutettavassa muodossa, voit pyytää tietoja palautelomakkeella ", + "content1end": ". Tiedusteluun pyritään vastaamaan kohtuullisessa ajassa." + }, + "section9": { + "heading1": "Saavutettavuuden valvonta", + "content1": "Etelä-Suomen aluehallintovirasto valvoo saavutettavuusvaatimusten toteutumista. Jos et ole tyytyväinen saamaasi vastaukseen tai et saa vastausta lainkaan kahden viikon aikana, voit antaa palautteen Etelä-Suomen aluehallintovirastoon.", + "contact1": "Etelä-Suomen aluehallintovirasto,", + "contact2": "Saavutettavuuden valvonnan yksikkö", + "contact3": "www.saavutettavuusvaatimukset.fi", + "contact4": "saavutettavuus@avi.fi", + "content2": "Helsinki-lisä -asiointi palvelun saavutettavuusselosteesta vastaa Helsingin kaupungin työllisyyspalvelut -yksikkö." + }, + "section10": { + "heading1": "Helsingin kaupunki ja saavutettavuus", + "content1": "Kaupunki edistää digitaalisten palveluiden saavutettavuutta yhdenmukaistamalla julkaisutyötä ja järjestämällä saavutettavuuteen keskittyvää koulutusta henkilökunnalleen.", + "content2": "Sivustojen saavutettavuuden tasoa seurataan jatkuvasti sivustoja ylläpidettäessä. Havaittuihin puutteisiin reagoidaan välittömästi. Tarvittavat muutokset pyritään suorittamaan mahdollisimman nopeasti." + }, + "section11": { + "heading1": "Vammaiset ja avustavien teknologioiden käyttäjät", + "content1": "Kaupunki tarjoaa neuvontaa ja tukea vammaisille ja avustavien teknologioiden käyttäjille. Tukea on saatavilla kaupungin sivuilla ilmoitetuilta neuvontasivuilta sekä puhelinneuvonnasta." + }, + "section12": { + "heading1": "Palaute ja yhteystiedot" + } + } } } diff --git a/frontend/benefit/applicant/src/components/layout/Layout.sc.ts b/frontend/benefit/applicant/src/components/layout/Layout.sc.ts index 1333b8454d..bcf6c1663f 100644 --- a/frontend/benefit/applicant/src/components/layout/Layout.sc.ts +++ b/frontend/benefit/applicant/src/components/layout/Layout.sc.ts @@ -1,6 +1,13 @@ -import styled from 'styled-components'; +import styled, { DefaultTheme } from 'styled-components'; -export const $Main = styled.main` +interface MainProps { + $backgroundColor: keyof DefaultTheme['colors']; +} + +export const $Main = styled.main` + ${(props) => ` + background-color: ${props.theme.colors[props.$backgroundColor]}; + `} display: flex; flex-direction: column; height: 100%; diff --git a/frontend/benefit/applicant/src/components/layout/Layout.tsx b/frontend/benefit/applicant/src/components/layout/Layout.tsx index 922bf59f6a..ca0c103f17 100644 --- a/frontend/benefit/applicant/src/components/layout/Layout.tsx +++ b/frontend/benefit/applicant/src/components/layout/Layout.tsx @@ -2,9 +2,11 @@ import Header from 'benefit/applicant/components/header/Header'; import TermsOfService from 'benefit/applicant/components/termsOfService/TermsOfService'; import { IS_CLIENT, LOCAL_STORAGE_KEYS } from 'benefit/applicant/constants'; import dynamic from 'next/dynamic'; +import { useRouter } from 'next/router'; import * as React from 'react'; import useAuth from 'shared/hooks/useAuth'; +import { ROUTES } from '../../constants'; import { $Main } from './Layout.sc'; const Footer = dynamic( @@ -18,6 +20,8 @@ const Layout: React.FC = ({ children, ...rest }) => { const { isAuthenticated } = useAuth(); const [isTermsOfServiceApproved, setIsTermsOfSerivceApproved] = React.useState(false); + const router = useRouter(); + const bgColor = router.pathname === ROUTES.LOGIN ? 'silverLight' : 'white'; React.useEffect(() => { if (IS_CLIENT) { @@ -31,7 +35,7 @@ const Layout: React.FC = ({ children, ...rest }) => { }, []); return ( - <$Main {...rest}> + <$Main $backgroundColor={bgColor} {...rest}>
{isAuthenticated && !isTermsOfServiceApproved ? ( props.theme.colors.black20}; + margin-top: ${(props) => props.theme.spacing.l}; + margin-bottom: ${(props) => props.theme.spacing.l}; + width: 100%; +`; + +export const $H1 = styled.h1` + font-size: ${(props) => props.theme.fontSize.heading.l}; + font-weight: 300; +`; + +export const $P = styled.p` + line-height: ${(props) => props.theme.lineHeight.l}; +`; + +export const $H2 = styled.h2` + font-size: ${(props) => props.theme.fontSize.heading.s}; + font-weight: 700; +`; diff --git a/frontend/benefit/applicant/src/pages/accessibility-statement.tsx b/frontend/benefit/applicant/src/pages/accessibility-statement.tsx index 1c1a9dab11..e5c865f45a 100644 --- a/frontend/benefit/applicant/src/pages/accessibility-statement.tsx +++ b/frontend/benefit/applicant/src/pages/accessibility-statement.tsx @@ -3,18 +3,12 @@ import { GetStaticProps, NextPage } from 'next'; import Head from 'next/head'; import * as React from 'react'; import Container from 'shared/components/container/Container'; +import { + $Grid, + $GridCell, +} from 'shared/components/forms/section/FormSection.sc'; import useLocale from 'shared/hooks/useLocale'; import getServerSideTranslations from 'shared/i18n/get-server-side-translations'; -import styled from 'styled-components'; - -const $TextWrapper = styled.div` - display: grid; - grid-template-columns: repeat(12, 1fr); -`; - -const $TextContainer = styled.main` - grid-column: span 8; -`; const AccessibilityStatement: NextPage = () => { const { t } = useTranslation(); @@ -38,8 +32,8 @@ const AccessibilityStatement: NextPage = () => { - <$TextWrapper> - <$TextContainer> + <$Grid> + <$GridCell $colSpan={8}>

{t(`${tBase}.h1`)}

{t(`${tBase}.sections.section1.heading1`)}

@@ -122,8 +116,8 @@ const AccessibilityStatement: NextPage = () => {

{t(`${tBase}.sections.section11.heading1`)}

{t(`${tBase}.sections.section11.content1`)}

- - + +
); diff --git a/frontend/benefit/applicant/src/pages/login.tsx b/frontend/benefit/applicant/src/pages/login.tsx index d7bb554c6e..ac5f3e7639 100644 --- a/frontend/benefit/applicant/src/pages/login.tsx +++ b/frontend/benefit/applicant/src/pages/login.tsx @@ -1,6 +1,13 @@ +import { + $H1, + $H2, + $Hr, + $P, +} from 'benefit/applicant/components/pages/Pages.sc'; import useLogin from 'benefit/applicant/hooks/useLogin'; import { Button, + IconLinkExternal, IconSignin, Notification, NotificationProps as HDSNotificationProps, @@ -11,14 +18,19 @@ import { useTranslation } from 'next-i18next'; import React, { useEffect } from 'react'; import { useQueryClient } from 'react-query'; import Container from 'shared/components/container/Container'; +import { + $Grid, + $GridCell, +} from 'shared/components/forms/section/FormSection.sc'; import getServerSideTranslations from 'shared/i18n/get-server-side-translations'; -import { useTheme } from 'styled-components'; import { IS_CLIENT, LOCAL_STORAGE_KEYS } from '../constants'; -type NotificationProps = Pick & { - content?: string; -}; +type NotificationProps = + | (Pick & { + content?: string; + }) + | null; const Login: NextPage = () => { const queryClient = useQueryClient(); @@ -28,8 +40,6 @@ const Login: NextPage = () => { } = useRouter(); const login = useLogin(); - const theme = useTheme(); - const notificationProps = React.useMemo((): NotificationProps => { if (error) { return { type: 'error', label: t('common:login.errorLabel') }; @@ -40,11 +50,7 @@ const Login: NextPage = () => { if (logout) { return { type: 'info', label: t('common:login.logoutMessageLabel') }; } - return { - type: 'info', - label: t('common:login.infoLabel'), - content: t('common:login.infoContent'), - }; + return null; }, [t, error, sessionExpired, logout]); useEffect(() => { @@ -61,24 +67,64 @@ const Login: NextPage = () => { return ( - - {notificationProps.content} - - + <$Grid> + <$GridCell $colSpan={6}> + {notificationProps && ( + + {notificationProps.content} + + )} + <$H1>{t('common:login.header')} + <$P>{t('common:login.infoText1')} + <$Hr /> + <$H2>{t('common:login.smallHeader1')} + <$P>{t('common:login.infoText2')} + <$Grid> + <$GridCell $colSpan={8}> + + + + <$Hr /> + <$H2>{t('common:login.smallHeader2')} + <$P>{t('common:login.infoText3')} + <$Grid> + <$GridCell $colSpan={8}> + + + + <$Hr /> + + {t('common:login.infoContent')} + + + ); }; From 4966ad1eccac626d46571d58b771f44328d076b9 Mon Sep 17 00:00:00 2001 From: Maks Turtiainen Date: Sat, 10 Jun 2023 01:03:50 +0300 Subject: [PATCH 030/102] HL-824: Fix PDF links --- backend/benefit/helsinkibenefit/settings.py | 7 ------- .../applicant/src/components/pdfViewer/PdfViewer.tsx | 2 ++ frontend/benefit/applicant/src/styles/globals.css | 7 ++++++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/benefit/helsinkibenefit/settings.py b/backend/benefit/helsinkibenefit/settings.py index 0e45bbee58..4193add1cd 100644 --- a/backend/benefit/helsinkibenefit/settings.py +++ b/backend/benefit/helsinkibenefit/settings.py @@ -1,5 +1,4 @@ import os -from corsheaders.defaults import default_headers import environ import sentry_sdk @@ -494,9 +493,3 @@ AWS_ACCESS_KEY_ID = env("S3_ACCESS_KEY_ID") AWS_SECRET_ACCESS_KEY = env("S3_SECRET_ACCESS_KEY") AWS_STORAGE_BUCKET_NAME = env("S3_STORAGE_BUCKET_NAME") - -CORS_ALLOW_HEADERS = ( - *default_headers, - "baggage", - "sentry-trace", -) diff --git a/frontend/benefit/applicant/src/components/pdfViewer/PdfViewer.tsx b/frontend/benefit/applicant/src/components/pdfViewer/PdfViewer.tsx index 9694c0fe8d..8281bcc6e6 100644 --- a/frontend/benefit/applicant/src/components/pdfViewer/PdfViewer.tsx +++ b/frontend/benefit/applicant/src/components/pdfViewer/PdfViewer.tsx @@ -1,3 +1,5 @@ +import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; + import { Button } from 'hds-react'; import React from 'react'; import { Document, Page, pdfjs } from 'react-pdf'; diff --git a/frontend/benefit/applicant/src/styles/globals.css b/frontend/benefit/applicant/src/styles/globals.css index b8fb01d147..be18885607 100644 --- a/frontend/benefit/applicant/src/styles/globals.css +++ b/frontend/benefit/applicant/src/styles/globals.css @@ -8,7 +8,8 @@ ul { list-style: none; } -ul, li { +ul, +li { margin: 0; padding: 0; } @@ -16,3 +17,7 @@ ul, li { .app-load-wrapper > div { display: none; } + +.annotationLayer > section { + border-style: none !important; +} From 313e7196ee61566a1c5ef662c73c9377f32efe34 Mon Sep 17 00:00:00 2001 From: Maks Turtiainen Date: Sat, 10 Jun 2023 01:55:08 +0300 Subject: [PATCH 031/102] Remove obsolete translations --- frontend/benefit/applicant/public/locales/en/common.json | 8 -------- frontend/benefit/applicant/public/locales/sv/common.json | 8 -------- 2 files changed, 16 deletions(-) diff --git a/frontend/benefit/applicant/public/locales/en/common.json b/frontend/benefit/applicant/public/locales/en/common.json index 4f15fc2ad7..6ee8296524 100644 --- a/frontend/benefit/applicant/public/locales/en/common.json +++ b/frontend/benefit/applicant/public/locales/en/common.json @@ -556,14 +556,6 @@ "noMessages": "No messages", "close": "Close" }, - "supportingContent": { - "contact": { - "title": "", - "text": "Do you need help? If anything is unclear, you can email us", - "emailAddress": "helsinkilisa@hel.fi", - "buttonText": "" - } - }, "application": { "tooltipShowInfo": "Show information" }, diff --git a/frontend/benefit/applicant/public/locales/sv/common.json b/frontend/benefit/applicant/public/locales/sv/common.json index f86e0c3be2..a03d583aa3 100644 --- a/frontend/benefit/applicant/public/locales/sv/common.json +++ b/frontend/benefit/applicant/public/locales/sv/common.json @@ -556,14 +556,6 @@ "noMessages": "Inga meddelanden", "close": "Stäng" }, - "supportingContent": { - "contact": { - "title": "", - "text": "Behöver du hjälp? Om något är oklart kan du mejla oss", - "emailAddress": "helsinkilisa@hel.fi", - "buttonText": "" - } - }, "application": { "tooltipShowInfo": "Visa information" }, From bc8c120ad858619949342fec7ad240ddf2dadb80 Mon Sep 17 00:00:00 2001 From: Maks Turtiainen Date: Sat, 10 Jun 2023 02:03:47 +0300 Subject: [PATCH 032/102] Remove unneeded css statement --- frontend/benefit/applicant/src/styles/globals.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/benefit/applicant/src/styles/globals.css b/frontend/benefit/applicant/src/styles/globals.css index be18885607..cf05c00a9d 100644 --- a/frontend/benefit/applicant/src/styles/globals.css +++ b/frontend/benefit/applicant/src/styles/globals.css @@ -17,7 +17,3 @@ li { .app-load-wrapper > div { display: none; } - -.annotationLayer > section { - border-style: none !important; -} From 97884faf926e46f9ad3faebd807df3a6ef6518b6 Mon Sep 17 00:00:00 2001 From: Maks Turtiainen Date: Mon, 12 Jun 2023 11:24:16 +0300 Subject: [PATCH 033/102] More consistent naming, useUserQuery refactor --- .../applicant/public/locales/en/common.json | 6 +++--- .../applicant/public/locales/fi/common.json | 6 +++--- .../applicant/public/locales/sv/common.json | 6 +++--- .../src/components/layout/Layout.sc.ts | 4 ++-- .../applicant/src/components/layout/Layout.tsx | 2 +- .../applicant/src/components/pages/Pages.sc.ts | 6 +++--- .../applicant/src/hooks/useUserQuery.ts | 17 ++++++----------- frontend/benefit/applicant/src/pages/login.tsx | 18 +++++++++--------- 8 files changed, 30 insertions(+), 35 deletions(-) diff --git a/frontend/benefit/applicant/public/locales/en/common.json b/frontend/benefit/applicant/public/locales/en/common.json index 6ee8296524..7651492159 100644 --- a/frontend/benefit/applicant/public/locales/en/common.json +++ b/frontend/benefit/applicant/public/locales/en/common.json @@ -476,11 +476,11 @@ "logoutInfoContent": "You have been logged out. If you want to continue to use the service, you have to log in again click the button here below.", "infoContent": "Send us an email to helsinkilisa@hel.fi.", "termsOfServiceHeader": "Information about the processing of the employer representatives’ personal data", - "header": "Log in to the Helsinki benefit service", + "heading": "Log in to the Helsinki benefit service", "infoText1": "Apply for Helsinki benefit, a discretionary support for a private or third-sector employer that hires an unemployed Helsinki resident. You need a Suomi.fi e-Authorization to do business on behalf of your organization.", - "smallHeader1": "Log in with you bank access codes or mobile certificate", + "subheading1": "Log in with you bank access codes or mobile certificate", "infoText2": "When you log in for the first time, your own Helsinki profile is automatically created for you.", - "smallHeader2": "Acquire Suomi.fi e-Authorization", + "subheading2": "Acquire Suomi.fi e-Authorization", "infoText3": "If you do not yet have the right to do business on behalf of your organization, you can get yourself a Suomi.fi e-Authorization.", "authorization": "See the instructions for obtaining authorization", "suomifiUrl": "https://www.suomi.fi/e-authorizations" diff --git a/frontend/benefit/applicant/public/locales/fi/common.json b/frontend/benefit/applicant/public/locales/fi/common.json index 713560a651..ea78e25347 100644 --- a/frontend/benefit/applicant/public/locales/fi/common.json +++ b/frontend/benefit/applicant/public/locales/fi/common.json @@ -476,11 +476,11 @@ "logoutInfoContent": "Sinut on kirjattu ulos. Jos haluat jatkaa asiointia palvelussa, kirjaudu uudelleen sisään klikkaamalla alla olevasta painikkeesta.", "infoContent": "Lähetä meille sähköposti osoitteeseen helsinkilisa@hel.fi.", "termsOfServiceHeader": "Tietoa työnantajan edustajien henkilötietojen käsittelystä", - "header": "Kirjaudu Helsinki-lisän asiointipalveluun", + "heading": "Kirjaudu Helsinki-lisän asiointipalveluun", "infoText1": "Hae organisaatiollesi taloudellista tukea, Helsinki-lisää, työttömän helsinkiläisen työllistämiseen. Tarvitset Suomi.fi-valtuutuksen asioimiseen organisaatiosi puolesta.", - "smallHeader1": "Kirjaudu pankkitunnuksilla tai mobiilivarmenteella", + "subheading1": "Kirjaudu pankkitunnuksilla tai mobiilivarmenteella", "infoText2": "Kun kirjaudut ensimmäisen kerran, sinulle luodaan automaattisesti oma Helsinki-profiili tunnus.", - "smallHeader2": "Hanki Suomi.fi -valtuutus", + "subheading2": "Hanki Suomi.fi -valtuutus", "infoText3": "Jos sinulla ei ole vielä oikeutta asioida organisaatiosi puolesta, voit hankkia itsellesi Suomi.fi -valtuuden.", "authorization": "Katso ohjeet valtuutuksen hankkimiseen", "suomifiUrl": "https://www.suomi.fi/valtuudet" diff --git a/frontend/benefit/applicant/public/locales/sv/common.json b/frontend/benefit/applicant/public/locales/sv/common.json index a03d583aa3..ac483e0613 100644 --- a/frontend/benefit/applicant/public/locales/sv/common.json +++ b/frontend/benefit/applicant/public/locales/sv/common.json @@ -476,11 +476,11 @@ "logoutInfoContent": "Du har nu loggats ut. Om du vill fortsätta använda tjänsten, logga in på nytt genom att klicka på knappen nedan.", "infoContent": "Skicka ett mejl till helsinkilisa@hel.fi.", "termsOfServiceHeader": "Information om behandlingen av arbetsgivarens representanters uppgifter", - "header": "Logga in i Helsingforstillägg tjänsten", + "heading": "Logga in i Helsingforstillägg tjänsten", "infoText1": "Sök ekonomiskt stöd, Helsingforstillägg, för att sysselsätta arbetslösa helsingforsbor. Du behöver Suomi.fi-fullmakter för att utföra ärenden för din organisation.", - "smallHeader1": "Logga in med bankkoder eller mobilt certifikat", + "subheading1": "Logga in med bankkoder eller mobilt certifikat", "infoText2": "När du loggar in för första gången skapas automatiskt en egen Helsingforsprofil för dig.", - "smallHeader2": "Skaffa Suomi.fi-fullmakter", + "subheading2": "Skaffa Suomi.fi-fullmakter", "infoText3": "Om du ännu inte har rättighet att handla på din organisations vägnar kan du skaffa Suomi.fi-fullmakter för dig själv.", "authorization": "Se instruktioner för att skaffa autorisering", "suomifiUrl": "https://www.suomi.fi/fullmakter" diff --git a/frontend/benefit/applicant/src/components/layout/Layout.sc.ts b/frontend/benefit/applicant/src/components/layout/Layout.sc.ts index bcf6c1663f..ee3f22b23d 100644 --- a/frontend/benefit/applicant/src/components/layout/Layout.sc.ts +++ b/frontend/benefit/applicant/src/components/layout/Layout.sc.ts @@ -1,12 +1,12 @@ import styled, { DefaultTheme } from 'styled-components'; interface MainProps { - $backgroundColor: keyof DefaultTheme['colors']; + backgroundColor: keyof DefaultTheme['colors']; } export const $Main = styled.main` ${(props) => ` - background-color: ${props.theme.colors[props.$backgroundColor]}; + background-color: ${props.theme.colors[props.backgroundColor]}; `} display: flex; flex-direction: column; diff --git a/frontend/benefit/applicant/src/components/layout/Layout.tsx b/frontend/benefit/applicant/src/components/layout/Layout.tsx index ca0c103f17..ed8012f59f 100644 --- a/frontend/benefit/applicant/src/components/layout/Layout.tsx +++ b/frontend/benefit/applicant/src/components/layout/Layout.tsx @@ -35,7 +35,7 @@ const Layout: React.FC = ({ children, ...rest }) => { }, []); return ( - <$Main $backgroundColor={bgColor} {...rest}> + <$Main backgroundColor={bgColor} {...rest}>
{isAuthenticated && !isTermsOfServiceApproved ? ( props.theme.fontSize.heading.l}; font-weight: 300; `; -export const $P = styled.p` +export const $Paragraph = styled.p` line-height: ${(props) => props.theme.lineHeight.l}; `; -export const $H2 = styled.h2` +export const $Subheading = styled.h2` font-size: ${(props) => props.theme.fontSize.heading.s}; font-weight: 700; `; diff --git a/frontend/benefit/applicant/src/hooks/useUserQuery.ts b/frontend/benefit/applicant/src/hooks/useUserQuery.ts index 646f03bed3..ace1549ef8 100644 --- a/frontend/benefit/applicant/src/hooks/useUserQuery.ts +++ b/frontend/benefit/applicant/src/hooks/useUserQuery.ts @@ -13,6 +13,8 @@ import { LOCAL_STORAGE_KEYS } from '../constants'; // check that authentication is still alive in every 5 minutes const FIVE_MINUTES = 5 * 60 * 1000; +const UNAUTHORIZER_ROUTES = new Set(['/login', '/accessibility-statement']); + const useUserQuery = ( queryKeys?: string | unknown[] ): UseQueryResult => { @@ -23,20 +25,13 @@ const useUserQuery = ( const locale = useLocale(); const { axios, handleResponse } = useBackendAPI(); - const isEnabled = (): boolean => { - if (logout) { - return false; - } - if (router.route === '/accessibility-statement') { - return false; - } - return true; - }; - const handleError = (error: Error): void => { if (logout) { void router.push(`${locale}/login?logout=true`); } else if (/40[13]/.test(error.message)) { + if (UNAUTHORIZER_ROUTES.has(router.route)) { + return; + } void router.push(`${locale}/login`); } else { showErrorToast( @@ -51,7 +46,7 @@ const useUserQuery = ( () => handleResponse(axios.get(BackendEndpoint.USER_ME)), { refetchInterval: FIVE_MINUTES, - enabled: isEnabled(), + enabled: !logout, retry: false, select: (data) => camelcaseKeys(data, { deep: true }), onError: (error) => handleError(error), diff --git a/frontend/benefit/applicant/src/pages/login.tsx b/frontend/benefit/applicant/src/pages/login.tsx index ac5f3e7639..d86b1e0012 100644 --- a/frontend/benefit/applicant/src/pages/login.tsx +++ b/frontend/benefit/applicant/src/pages/login.tsx @@ -1,8 +1,8 @@ import { - $H1, - $H2, + $Heading, $Hr, - $P, + $Paragraph, + $Subheading, } from 'benefit/applicant/components/pages/Pages.sc'; import useLogin from 'benefit/applicant/hooks/useLogin'; import { @@ -78,11 +78,11 @@ const Login: NextPage = () => { {notificationProps.content} )} - <$H1>{t('common:login.header')} - <$P>{t('common:login.infoText1')} + <$Heading>{t('common:login.heading')} + <$Paragraph>{t('common:login.infoText1')} <$Hr /> - <$H2>{t('common:login.smallHeader1')} - <$P>{t('common:login.infoText2')} + <$Subheading>{t('common:login.subheading1')} + <$Paragraph>{t('common:login.infoText2')} <$Grid> <$GridCell $colSpan={8}> diff --git a/frontend/benefit/handler/src/components/newApplication/ApplicationForm.sc.ts b/frontend/benefit/handler/src/components/newApplication/ApplicationForm.sc.ts new file mode 100644 index 0000000000..1fc6d4a8bc --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/ApplicationForm.sc.ts @@ -0,0 +1,48 @@ +import styled from 'styled-components'; + +type ViewFieldProps = { + isInline?: boolean; +}; + +export const $ViewField = styled.div` + &:not(:last-child) { + padding-bottom: ${(props) => + props.children ? props.theme.spacing.xs4 : 0}; + } + display: ${(props) => (props.isInline ? 'inline' : 'block')}; + font-weight: 400; +`; + +export const $ViewFieldBold = styled.span` + font-weight: 500; +`; + +export const $SummaryTableHeader = styled.div` + &:not(:last-child) { + padding-bottom: ${(props) => + props.children ? props.theme.spacing.xs2 : 0}; + } + font-size: ${(props) => props.theme.fontSize.body.m}; + font-weight: 500; +`; + +export const $SummaryTableValue = styled.span` + font-size: ${(props) => props.theme.fontSize.body.l}; +`; + +export const $MainHeading = styled.h1` + display: inline-block; + font-size: ${(props) => props.theme.fontSize.heading.xl}; + font-weight: normal; +`; + +export const $SpinnerContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100%; +`; + +export const $ButtonContainer = styled.span` + margin-right: ${(props) => props.theme.spacing.xs2}; +`; diff --git a/frontend/benefit/handler/src/components/newApplication/ApplicationForm.tsx b/frontend/benefit/handler/src/components/newApplication/ApplicationForm.tsx new file mode 100644 index 0000000000..9e63ebda6f --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/ApplicationForm.tsx @@ -0,0 +1,189 @@ +import { + Button, + Dialog, + IconAngleLeft, + LoadingSpinner, + Stepper, +} from 'hds-react'; +import React from 'react'; +import Container from 'shared/components/container/Container'; +import { + $Grid, + $GridCell, +} from 'shared/components/forms/section/FormSection.sc'; + +import ActionBar from './actionBar/ActionBar'; +import { $MainHeading, $SpinnerContainer } from './ApplicationForm.sc'; +import CompanySearch from './companySearch/CompanySearch'; +import FormContent from './formContent/FormContent'; +import Review from './review/Review'; +import { useApplicationForm } from './useApplicationForm'; + +const ApplicationForm: React.FC = () => { + const { + id, + t, + isConfirmationModalOpen, + setIsConfirmationModalOpen, + translationsBase, + router, + application, + formik, + fields, + handleSave, + handleQuietSave, + handleSubmit, + handleSaveDraft, + handleDelete, + showDeminimisSection, + minEndDate, + maxEndDate, + setEndDate, + getSelectValue, + subsidyOptions, + deMinimisAidSet, + attachments, + dispatchStep, + stepState, + isLoading, + checkedConsentArray, + getConsentErrorText, + handleConsentClick, + } = useApplicationForm(); + + // 'theme' prop didn't work for some reason + const colorBlack90 = 'var(--color-black-90)'; + const stepperCss = { + 'pointer-events': 'none', + p: { + 'text-decoration': 'none !important', + }, + '--hds-not-selected-step-label-color': colorBlack90, + '--hds-step-background-color': 'var(--color-white)', + '--hds-step-content-color': colorBlack90, + '--hds-stepper-background-color': 'var(--color-white)', + '--hds-stepper-color': colorBlack90, + '--hds-stepper-disabled-color': 'var(--color-black-30)', + '--hds-stepper-focus-border-color': colorBlack90, + }; + + if (isLoading) { + return ( + <$SpinnerContainer> + + + ); + } + + return ( + + <$Grid> + <$GridCell $colSpan={6} css="display: flex; align-items: center;"> + + + <$GridCell $colSpan={6}> + e.stopPropagation()} + css={stepperCss} + /> + + +
+ <$MainHeading>{t('common:mainIngress.heading')} +
+ {stepState.activeStepIndex === 0 && } + {stepState.activeStepIndex === 1 && ( + <> + + + + )} + {stepState.activeStepIndex === 2 && ( + <> + + dispatchStep({ type: 'setActive', payload: 1 })} + /> + + )} + {isConfirmationModalOpen && ( + setIsConfirmationModalOpen(false)} + closeButtonLabelText={t(`${translationsBase}.close`)} + variant="danger" + > + + + {t(`${translationsBase}.backWithoutSavingDescription`)} + + + + + + + )} +
+ ); +}; + +export default ApplicationForm; diff --git a/frontend/benefit/handler/src/components/newApplication/actionBar/ActionBar.tsx b/frontend/benefit/handler/src/components/newApplication/actionBar/ActionBar.tsx new file mode 100644 index 0000000000..43007c84e3 --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/actionBar/ActionBar.tsx @@ -0,0 +1,105 @@ +import { Button, IconArrowRight, IconTrash } from 'hds-react'; +import { useRouter } from 'next/router'; +import { useTranslation } from 'next-i18next'; +import React, { useState } from 'react'; +import { + $Grid, + $GridCell, +} from 'shared/components/forms/section/FormSection.sc'; +import Modal from 'shared/components/modal/Modal'; + +import { $ButtonContainer } from '../ApplicationForm.sc'; + +type ActionBarProps = { + handleDelete?: () => void; + handleSave?: () => void; + handleSubmit?: () => void; + handleBack?: () => void; + handleSaveDraft: () => void; + id: string | string[] | undefined; +}; + +const ActionBar: React.FC = ({ + handleDelete, + handleSave, + handleBack, + handleSubmit, + handleSaveDraft, + id, +}: ActionBarProps) => { + const { t } = useTranslation(); + const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false); + const translationsBase = 'common:applications.actions'; + const router = useRouter(); + + return ( + <> + <$Grid> + <$GridCell $colSpan={10}> + {handleBack && ( + <$ButtonContainer> + + + )} + <$ButtonContainer> + + + <$ButtonContainer> + + + + <$GridCell $colSpan={2} $colStart={11} justifySelf="end"> + {handleSave && ( + + )} + {handleSubmit && ( + + )} + + + {isConfirmationModalOpen && handleDelete && ( + setIsConfirmationModalOpen(false)} + handleSubmit={handleDelete} + variant="danger" + > + {t(`${translationsBase}.deleteApplicationDescription`)} + + )} + + ); +}; + +export default ActionBar; diff --git a/frontend/benefit/handler/src/components/newApplication/companySearch/CompanySearch.tsx b/frontend/benefit/handler/src/components/newApplication/companySearch/CompanySearch.tsx new file mode 100644 index 0000000000..3385205de8 --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/companySearch/CompanySearch.tsx @@ -0,0 +1,104 @@ +import { + Button, + LoadingSpinner, + Notification, + RadioButton, + SearchInput, + SelectionGroup, +} from 'hds-react'; +import React from 'react'; +import { + $Grid, + $GridCell, +} from 'shared/components/forms/section/FormSection.sc'; +import { useTheme } from 'styled-components'; + +import { useCompanySearch } from './useCompanySearch'; + +const CompanySearch: React.FC = () => { + const { + getCompany, + getSuggestions, + t, + translationsBase, + errorMessage, + noResults, + companies, + selectedCompany, + onCompanyChange, + onSelectCompany, + isLoading, + } = useCompanySearch(); + const theme = useTheme(); + + return ( + <> +
+

{t('common:applications.sections.companySearch.heading')}

+ <$GridCell as={$Grid} $colSpan={12}> + <$GridCell $colSpan={6}> + + + <$GridCell $colStart={1} $colSpan={6}> + {companies.length > 0 && ( + <> + + {companies.map((company) => ( + `} + checked={selectedCompany === company.business_id} + onChange={(event) => onCompanyChange(event.target.value)} + /> + ))} + + + + )} + + <$GridCell $colStart={1} $colSpan={6}> + {errorMessage && ( + + {errorMessage.text} + + )} + {noResults && ( + + {noResults.text} + + )} + + +
+ {isLoading && } + + ); +}; + +export default CompanySearch; diff --git a/frontend/benefit/handler/src/components/newApplication/companySearch/companyApi.ts b/frontend/benefit/handler/src/components/newApplication/companySearch/companyApi.ts new file mode 100644 index 0000000000..56476eac43 --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/companySearch/companyApi.ts @@ -0,0 +1,25 @@ +import axios from 'axios'; +import { CompanySearchResult } from 'benefit/handler/types/application'; +import { + BackendEndpoint, + getBackendUrl, +} from 'benefit-shared/backend-api/backend-api'; +import { CompanyData } from 'benefit-shared/types/company'; + +export const searchCompanies = async ( + name: string +): Promise => { + const res = await axios.get( + `${getBackendUrl(BackendEndpoint.SEARCH_ORGANISATION)}${name}/` + ); + return res.data; +}; + +export const getCompanyData = async ( + businessId: string +): Promise => { + const res = await axios.get( + `${getBackendUrl(BackendEndpoint.GET_ORGANISATION)}${businessId}/` + ); + return res.data; +}; diff --git a/frontend/benefit/handler/src/components/newApplication/companySearch/useCompanySearch.ts b/frontend/benefit/handler/src/components/newApplication/companySearch/useCompanySearch.ts new file mode 100644 index 0000000000..80ffd25e06 --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/companySearch/useCompanySearch.ts @@ -0,0 +1,177 @@ +import { AxiosError } from 'axios'; +import { APPLICATION_INITIAL_VALUES } from 'benefit/handler/constants'; +import useFormActions from 'benefit/handler/hooks/useFormActions'; +import { CompanySearchResult } from 'benefit/handler/types/application'; +import { FinnishBusinessIds as bId } from 'finnish-business-ids'; +import { TFunction, useTranslation } from 'next-i18next'; +import { useState } from 'react'; + +import { getCompanyData, searchCompanies } from './companyApi'; + +type NotificationText = { + label: string; + text: string; +}; + +type ExtendedComponentProps = { + getCompany: (searchTerm: string) => void; + getSuggestions: (searchTerm: string) => Promise; + t: TFunction; + translationsBase: string; + errorMessage: NotificationText | null; + noResults: NotificationText | null; + companies: CompanySearchResult[]; + selectedCompany: string | undefined; + onCompanyChange: (businessId: string) => void; + onSelectCompany: () => void; + isLoading: boolean; +}; + +const useCompanySearch = (): ExtendedComponentProps => { + const minSearchTermLength = 3; + const { t } = useTranslation(); + const translationsBase = 'common:applications.sections'; + const [noResults, setNoResults] = useState(null); + const [errorMessage, setErrorMessage] = useState( + null + ); + const [companies, setCompanies] = useState([]); + const [selectedCompany, setSelectedCompany] = useState(); + const [isLoading, setIsLoading] = useState(false); + const { onCompanySelected } = useFormActions({}); + + const onCompanyChange = (businessId: string): void => { + setSelectedCompany(businessId); + }; + + const createNotification = ( + notification: 'noResults' | 'error' + ): NotificationText => ({ + label: t( + `${translationsBase}.companySearch.notifications.${notification}.label` + ), + text: t( + `${translationsBase}.companySearch.notifications.${notification}.text` + ), + }); + + const filterCompanies = ( + name: string, + data: CompanySearchResult[] + ): CompanySearchResult[] => + data.filter((company) => + company.name.toLowerCase().includes(name.toLowerCase()) + ); + + const formatSuggestions = ( + results: CompanySearchResult[] + ): CompanySearchResult[] => + results.map((result) => + result.business_id + ? { + name: `${result.name} <${result.business_id}>`, + business_id: result.business_id, + } + : { name: result.name, business_id: '' } + ); + + const handleError = (error: AxiosError): void => { + if (error.message) { + setErrorMessage({ + label: t(`${translationsBase}.companySearch.notifications.error.label`), + text: error.message, + }); + } else { + setErrorMessage(createNotification('error')); + } + setIsLoading(false); + }; + + const getCompanyAndCreateDraft = async ( + businessId: string + ): Promise => { + const companyData = await getCompanyData(businessId); + if (companyData.id) { + onCompanySelected({ + ...APPLICATION_INITIAL_VALUES, + createApplicationForCompany: companyData.id, + }).catch((error: AxiosError) => { + handleError(error); + }); + } + }; + + const onSelectCompany = async (): Promise => { + setIsLoading(true); + await getCompanyAndCreateDraft(selectedCompany); + setCompanies([]); + }; + + const getSuggestions = (searchTerm: string): Promise => + new Promise((resolve) => { + if (searchTerm.length < minSearchTermLength) { + resolve([]); + } else if (bId.isValidBusinessId(searchTerm)) { + getCompanyData(searchTerm) + .then((data) => { + const dataInArray = [data]; + return resolve(formatSuggestions(dataInArray)); + }) + .catch(() => resolve([])); + } else { + searchCompanies(searchTerm) + .then((data) => { + const filteredItems = filterCompanies(searchTerm, data); + return filteredItems.length === 0 + ? resolve([]) + : resolve(formatSuggestions(filteredItems)); + }) + .catch(() => resolve([])); + } + }); + + const getCompany = async (searchTerm: string): Promise => { + if (searchTerm.length < minSearchTermLength) return; + setIsLoading(true); + setCompanies([]); + setNoResults(null); + setErrorMessage(null); + const newSearchTerm = searchTerm.trimStart().trimEnd(); + const match = /.*<([^<>]*)>$/.exec(newSearchTerm); + if (match) { + await getCompanyAndCreateDraft(match[1]); + } else if (bId.isValidBusinessId(newSearchTerm)) { + await getCompanyAndCreateDraft(newSearchTerm); + } else { + searchCompanies(newSearchTerm) + .then((data) => { + if (data.length > 0) { + setSelectedCompany(data[0].business_id); + setCompanies(data); + return setIsLoading(false); + } + setNoResults(createNotification('noResults')); + return setIsLoading(false); + }) + .catch((error: AxiosError) => { + handleError(error); + }); + } + }; + + return { + getCompany, + getSuggestions, + t, + translationsBase, + errorMessage, + noResults, + companies, + selectedCompany, + onCompanyChange, + onSelectCompany, + isLoading, + }; +}; + +export { useCompanySearch }; diff --git a/frontend/benefit/handler/src/components/newApplication/formContent/FormContent.sc.ts b/frontend/benefit/handler/src/components/newApplication/formContent/FormContent.sc.ts new file mode 100644 index 0000000000..f1100ba752 --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/formContent/FormContent.sc.ts @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +export const $Description = styled.p` + font-size: ${(props) => props.theme.fontSize.heading.xs}; + line-height: ${(props) => props.theme.lineHeight.l}; +`; + +export const $CompanyInfoField = styled.div` + line-height: ${(props) => props.theme.lineHeight.l}; + margin-bottom: ${(props) => props.theme.spacing.xs2}; +`; + +export const $CompanyInfoHeader = styled.div` + font-weight: bold; +`; diff --git a/frontend/benefit/handler/src/components/newApplication/formContent/FormContent.tsx b/frontend/benefit/handler/src/components/newApplication/formContent/FormContent.tsx new file mode 100644 index 0000000000..1b48faf0bc --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/formContent/FormContent.tsx @@ -0,0 +1,764 @@ +import { + APPLICATION_FIELD_KEYS, + APPLICATION_START_DATE, +} from 'benefit/handler/constants'; +import { useAlertBeforeLeaving } from 'benefit/handler/hooks/useAlertBeforeLeaving'; +import { useDependentFieldsEffect } from 'benefit/handler/hooks/useDependentFieldsEffect'; +import { + Application, + ApplicationFields, +} from 'benefit/handler/types/application'; +import { + ATTACHMENT_TYPES, + BENEFIT_TYPES, + ORGANIZATION_TYPES, +} from 'benefit-shared/constants'; +import { DeMinimisAid, TextProp } from 'benefit-shared/types/application'; +import { FormikProps } from 'formik'; +import { DateInput, Select, SelectionGroup, TextInput } from 'hds-react'; +import React from 'react'; +import FieldLabel from 'shared/components/forms/fields/fieldLabel/FieldLabel'; +import { + $Checkbox, + $RadioButton, +} from 'shared/components/forms/fields/Fields.sc'; +import { Option } from 'shared/components/forms/fields/types'; +import Heading from 'shared/components/forms/heading/Heading'; +import FormSection from 'shared/components/forms/section/FormSection'; +import { + $Grid, + $GridCell, +} from 'shared/components/forms/section/FormSection.sc'; +import { BenefitAttachment } from 'shared/types/attachment'; +import { OptionType } from 'shared/types/common'; +import { + formatStringFloatValue, + phoneToLocal, + stringFloatToFixed, +} from 'shared/utils/string.utils'; +import { useTheme } from 'styled-components'; + +import AttachmentsList from './attachmentsList/AttachmentsList'; +import CompanySection from './companySection/CompanySection'; +import { $Description } from './FormContent.sc'; +import { useFormContent } from './useFormContent'; + +type Props = { + application: Application; + formik: FormikProps>; + fields: ApplicationFields; + handleSave: () => void; + handleQuietSave: () => void; + showDeminimisSection: boolean; + minEndDate: Date; + maxEndDate: Date | undefined; + setEndDate: () => void; + getSelectValue: (fieldName: keyof Application) => OptionType | null; + subsidyOptions: OptionType[]; + deMinimisAidSet: DeMinimisAid[]; + attachments: BenefitAttachment[]; + checkedConsentArray: boolean[]; + getConsentErrorText: (consentIndex: number) => string; + handleConsentClick: (consentIndex: number) => void; +}; + +const FormContent: React.FC = ({ + application, + formik, + fields, + handleSave, + handleQuietSave, + showDeminimisSection, + minEndDate, + maxEndDate, + setEndDate, + getSelectValue, + subsidyOptions, + deMinimisAidSet, + attachments, + checkedConsentArray, + getConsentErrorText, + handleConsentClick, +}) => { + const { + t, + languageOptions, + translationsBase, + language, + cbPrefix, + textLocale, + clearDeminimisAids, + clearBenefitValues, + clearDatesValues, + clearCommissionValues, + clearContractValues, + clearPaySubsidyValues, + clearAlternativeAddressValues, + getErrorMessage, + } = useFormContent(formik, fields); + + const theme = useTheme(); + useAlertBeforeLeaving(formik.dirty); + + useDependentFieldsEffect( + { + associationHasBusinessActivities: + formik.values.associationHasBusinessActivities, + apprenticeshipProgram: formik.values.apprenticeshipProgram, + benefitType: formik.values.benefitType, + paySubsidyGranted: formik.values.paySubsidyGranted, + startDate: formik.values.startDate, + useAlternativeAddress: formik.values.useAlternativeAddress, + }, + { + isFormDirty: formik.dirty, + clearDeminimisAids, + clearBenefitValues, + clearDatesValues, + clearCommissionValues, + clearContractValues, + clearPaySubsidyValues, + clearAlternativeAddressValues, + setEndDate, + } + ); + + const selectLabel = t('common:select'); + + const isAbleToSelectEmploymentBenefit = + application?.company?.organizationType !== ORGANIZATION_TYPES.ASSOCIATION || + (application?.company?.organizationType === + ORGANIZATION_TYPES.ASSOCIATION && + application?.associationHasBusinessActivities); + const isAbleToSelectSalaryBenefit = formik.values.paySubsidyGranted === true; + + return ( +
+ + + <$GridCell $colSpan={4}> + + + <$GridCell $colSpan={4}> + + + <$GridCell $colStart={1} $colSpan={4}> + + + <$GridCell $colSpan={4}> + + + <$GridCell + $colStart={1} + $colSpan={6} + css={` + margin-top: ${theme.spacing.l}; + `} + > + + <$Checkbox + id={fields.employee.isLivingInHelsinki.name} + name={fields.employee.isLivingInHelsinki.name} + label={fields.employee.isLivingInHelsinki.placeholder} + onBlur={formik.handleBlur} + onChange={formik.handleChange} + aria-invalid={ + !!getErrorMessage(fields.employee.isLivingInHelsinki.name) + } + errorText={getErrorMessage(fields.employee.isLivingInHelsinki.name)} + required + checked={formik.values.employee?.isLivingInHelsinki === true} + /> + + + + <$GridCell $colSpan={8}> + + <$RadioButton + id={`${fields.paySubsidyGranted.name}False`} + name={fields.paySubsidyGranted.name} + value="false" + label={t( + `${translationsBase}.fields.${fields.paySubsidyGranted.name}.no` + )} + onBlur={formik.handleBlur} + onChange={() => { + formik.setFieldValue(fields.paySubsidyGranted.name, false); + formik.setFieldValue( + APPLICATION_FIELD_KEYS.APPRENTICESHIP_PROGRAM, + null + ); + }} + checked={formik.values.paySubsidyGranted === false} + /> + <$RadioButton + id={`${fields.paySubsidyGranted.name}True`} + name={fields.paySubsidyGranted.name} + value="true" + label={t( + `${translationsBase}.fields.${fields.paySubsidyGranted.name}.yes` + )} + onBlur={formik.handleBlur} + onChange={() => { + formik.setFieldValue(fields.paySubsidyGranted.name, true); + }} + checked={formik.values.paySubsidyGranted === true} + /> + + + {formik.values.paySubsidyGranted && ( + <$GridCell + as={$Grid} + $colSpan={12} + css={` + row-gap: ${theme.spacing.xl}; + `} + > + <$GridCell + $colSpan={4} + $colStart={1} + id={fields.paySubsidyPercent.name} + > + + formik.setFieldValue( + fields.additionalPaySubsidyPercent.name, + additionalPaySubsidyPercent.value + ) + } + options={[ + { + label: selectLabel, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore TODO: remove ts-ignore when HDS is fixed + value: null, + }, + ...subsidyOptions, + ]} + id={fields.additionalPaySubsidyPercent.name} + placeholder={selectLabel} + invalid={ + !!getErrorMessage(fields.additionalPaySubsidyPercent.name) + } + aria-invalid={ + !!getErrorMessage(fields.additionalPaySubsidyPercent.name) + } + error={getErrorMessage(fields.additionalPaySubsidyPercent.name)} + css={` + label { + white-space: pre; + } + `} + /> + + <$GridCell $colSpan={3} $colStart={1}> + + <$RadioButton + id={`${fields.apprenticeshipProgram.name}False`} + name={fields.apprenticeshipProgram.name} + value="false" + label={t( + `${translationsBase}.fields.${fields.apprenticeshipProgram.name}.no` + )} + onChange={() => { + formik.setFieldValue( + fields.apprenticeshipProgram.name, + false + ); + }} + checked={formik.values.apprenticeshipProgram === false} + /> + <$RadioButton + id={`${fields.apprenticeshipProgram.name}True`} + name={fields.apprenticeshipProgram.name} + value="true" + label={t( + `${translationsBase}.fields.${fields.apprenticeshipProgram.name}.yes` + )} + onChange={() => { + formik.setFieldValue( + fields.apprenticeshipProgram.name, + true + ); + }} + checked={formik.values.apprenticeshipProgram === true} + /> + + + + )} + + + <$GridCell $colSpan={6}> + + <$RadioButton + id={`${fields.benefitType.name}Employment`} + name={fields.benefitType.name} + value={BENEFIT_TYPES.EMPLOYMENT} + label={t( + `${translationsBase}.fields.${fields.benefitType.name}.employment` + )} + onBlur={formik.handleBlur} + onChange={formik.handleChange} + checked={formik.values.benefitType === BENEFIT_TYPES.EMPLOYMENT} + disabled={!isAbleToSelectEmploymentBenefit} + /> + <$RadioButton + id={`${fields.benefitType.name}Salary`} + name={fields.benefitType.name} + value={BENEFIT_TYPES.SALARY} + label={t( + `${translationsBase}.fields.${fields.benefitType.name}.salary` + )} + onBlur={formik.handleBlur} + onChange={formik.handleChange} + checked={formik.values.benefitType === BENEFIT_TYPES.SALARY} + disabled={!isAbleToSelectSalaryBenefit} + /> + + + + + + <$GridCell $colStart={1} $colSpan={4}> + + formik.setFieldValue(fields.startDate.name, value) + } + value={formik.values.startDate ?? ''} + invalid={!!getErrorMessage(fields.startDate.name)} + aria-invalid={!!getErrorMessage(fields.startDate.name)} + errorText={getErrorMessage(fields.startDate.name)} + minDate={APPLICATION_START_DATE} + required + /> + + <$GridCell $colSpan={4}> + + formik.setFieldValue(fields.endDate.name, value) + } + value={formik.values.endDate ?? ''} + invalid={!!getErrorMessage(fields.endDate.name)} + aria-invalid={!!getErrorMessage(fields.endDate.name)} + errorText={getErrorMessage(fields.endDate.name)} + initialMonth={!formik.values.endDate ? minEndDate : undefined} + minDate={minEndDate} + maxDate={maxEndDate} + required + /> + + + + {!formik.values.benefitType ? ( + <$GridCell $colSpan={8}> + {t(`${translationsBase}.messages.selectBenefitType`)} + + ) : ( + <> + <$GridCell $colSpan={4}> + + + <$GridCell $colSpan={3}> + + formik.setFieldValue( + fields.employee.workingHours.name, + stringFloatToFixed(e.target.value) + ) + } + value={formatStringFloatValue( + formik.values.employee?.workingHours + )} + invalid={!!getErrorMessage(fields.employee.workingHours.name)} + aria-invalid={ + !!getErrorMessage(fields.employee.workingHours.name) + } + errorText={getErrorMessage(fields.employee.workingHours.name)} + required + /> + + <$GridCell $colSpan={3}> + + + + <$GridCell $colSpan={12}> + + + + <$GridCell $colSpan={12}> + {t(`${translationsBase}.salaryExpensesExplanation`)} + + + <$GridCell $colSpan={2}> + + formik.setFieldValue( + fields.employee.monthlyPay.name, + stringFloatToFixed(e.target.value) + ) + } + value={formatStringFloatValue( + formik.values.employee?.monthlyPay + )} + invalid={!!getErrorMessage(fields.employee.monthlyPay.name)} + aria-invalid={ + !!getErrorMessage(fields.employee.monthlyPay.name) + } + errorText={getErrorMessage(fields.employee.monthlyPay.name)} + required + /> + + <$GridCell $colSpan={2}> + + formik.setFieldValue( + fields.employee.otherExpenses.name, + stringFloatToFixed(e.target.value) + ) + } + value={formatStringFloatValue( + formik.values.employee?.otherExpenses + )} + invalid={!!getErrorMessage(fields.employee.otherExpenses.name)} + aria-invalid={ + !!getErrorMessage(fields.employee.otherExpenses.name) + } + errorText={getErrorMessage(fields.employee.otherExpenses.name)} + required + /> + + <$GridCell $colSpan={2}> + + formik.setFieldValue( + fields.employee.vacationMoney.name, + stringFloatToFixed(e.target.value) + ) + } + value={formatStringFloatValue( + formik.values.employee?.vacationMoney + )} + invalid={!!getErrorMessage(fields.employee.vacationMoney.name)} + aria-invalid={ + !!getErrorMessage(fields.employee.vacationMoney.name) + } + errorText={getErrorMessage(fields.employee.vacationMoney.name)} + required + /> + + + )} + + + <$GridCell $colSpan={12}> + <$Description> + {t(`${translationsBase}.attachments.attachmentsIngress`)} + + + <$GridCell $colSpan={12}> + + + <$GridCell $colSpan={12}> + + + {formik.values.apprenticeshipProgram && ( + <$GridCell $colSpan={12}> + + + )} + {formik.values.paySubsidyGranted && ( + <$GridCell $colSpan={12}> + + + )} + <$GridCell $colSpan={12}> + + + <$GridCell $colSpan={12}> + + + + + {application?.applicantTermsInEffect?.applicantConsents.map( + (consent, i) => ( + <$GridCell + $colSpan={12} + key={consent.id} + css={` + label { + font-weight: 500; + } + `} + > + <$Checkbox + id={`${cbPrefix}_${consent.id}`} + name={`${cbPrefix}_${i}`} + label={`${consent[`text${textLocale}` as TextProp]} *`} + required + checked={checkedConsentArray[i]} + errorText={getConsentErrorText(i)} + aria-invalid={false} + onChange={() => handleConsentClick(i)} + /> + + ) + )} + + + ); +}; + +export default FormContent; diff --git a/frontend/benefit/handler/src/components/newApplication/formContent/attachmentsList/AttachmentsList.tsx b/frontend/benefit/handler/src/components/newApplication/formContent/attachmentsList/AttachmentsList.tsx new file mode 100644 index 0000000000..7cd14f7f26 --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/formContent/attachmentsList/AttachmentsList.tsx @@ -0,0 +1,51 @@ +import { ATTACHMENT_TYPES } from 'benefit-shared/constants'; +import camelCase from 'lodash/camelCase'; +import * as React from 'react'; +import AttachmentsListBase from 'shared/components/attachments/AttachmentsList'; +import { BenefitAttachment } from 'shared/types/attachment'; + +import { useAttachmentsList } from './useAttachmentsList'; + +export type AttachmentsListProps = { + attachmentType: ATTACHMENT_TYPES; + attachments?: BenefitAttachment[]; + handleQuietSave?: () => void; + required?: boolean; + as?: 'div' | 'li'; +}; + +const AttachmentsList: React.FC = ({ + attachmentType, + attachments, + handleQuietSave, + required, + as, +}) => { + const { + t, + handleRemove, + handleUpload, + handleOpenFile, + translationsBase, + isRemoving, + isUploading, + } = useAttachmentsList(handleQuietSave); + + return ( + + ); +}; + +export default AttachmentsList; diff --git a/frontend/benefit/handler/src/components/newApplication/formContent/attachmentsList/useAttachmentsList.ts b/frontend/benefit/handler/src/components/newApplication/formContent/attachmentsList/useAttachmentsList.ts new file mode 100644 index 0000000000..b4344f6491 --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/formContent/attachmentsList/useAttachmentsList.ts @@ -0,0 +1,87 @@ +import useRemoveAttachmentQuery from 'benefit/handler/hooks/useRemoveAttachmentQuery'; +import useUploadAttachmentQuery from 'benefit/handler/hooks/useUploadAttachmentQuery'; +import { useRouter } from 'next/router'; +import { TFunction, useTranslation } from 'next-i18next'; +import React from 'react'; +import showErrorToast from 'shared/components/toast/show-error-toast'; +import { BenefitAttachment } from 'shared/types/attachment'; + +type ExtendedComponentProps = { + t: TFunction; + translationsBase: string; + applicationId: string; + attachments: []; + isRemoving: boolean; + isUploading: boolean; + handleRemove: (attachmentId: string) => void; + handleUpload: (attachment: FormData) => void; + handleOpenFile: (attachment: BenefitAttachment) => void; +}; + +const useAttachmentsList = ( + handleQuietSave: () => void +): ExtendedComponentProps => { + const router = useRouter(); + const id = router?.query?.id; + const { t } = useTranslation(); + const translationsBase = 'common:applications.sections.attachments'; + + const applicationId = id?.toString() || ''; + + const { + mutate: removeAttachment, + isLoading: isRemoving, + isError: isRemovingError, + } = useRemoveAttachmentQuery(); + + const { + mutate: uploadAttachment, + isLoading: isUploading, + isError: isUploadingError, + } = useUploadAttachmentQuery(); + + React.useEffect(() => { + if (isRemovingError || isUploadingError) { + showErrorToast( + t(`common:remove.errorTitle`), + t(`common:remove.errorMessage`) + ); + } + }, [isRemovingError, isUploadingError, t]); + + const handleOpenFile = React.useCallback( + (file: BenefitAttachment) => + // eslint-disable-next-line security/detect-non-literal-fs-filename + window.open(file.attachmentFile, '_blank')?.focus(), + [] + ); + + const handleRemove = (attachmentId: string): void => { + removeAttachment({ + applicationId, + attachmentId, + }); + }; + + const handleUpload = (attachment: FormData): void => { + handleQuietSave(); + uploadAttachment({ + applicationId, + data: attachment, + }); + }; + + return { + t, + applicationId, + attachments: [], + translationsBase, + isRemoving, + isUploading, + handleRemove, + handleUpload, + handleOpenFile, + }; +}; + +export { useAttachmentsList }; diff --git a/frontend/benefit/handler/src/components/newApplication/formContent/companySection/CompanySection.tsx b/frontend/benefit/handler/src/components/newApplication/formContent/companySection/CompanySection.tsx new file mode 100644 index 0000000000..1ca3dd2335 --- /dev/null +++ b/frontend/benefit/handler/src/components/newApplication/formContent/companySection/CompanySection.tsx @@ -0,0 +1,542 @@ +import { + APPLICATION_FIELD_KEYS, +} from 'benefit/handler/constants'; +import DeMinimisContext from 'benefit/handler/context/DeMinimisContext'; +import { + Application, + ApplicationFields, +} from 'benefit/handler/types/application'; +import { + ORGANIZATION_TYPES +} from 'benefit-shared/constants'; +import { DeMinimisAid } from 'benefit-shared/types/application'; +import { FormikProps } from 'formik'; +import { Select, SelectionGroup, TextArea, TextInput } from 'hds-react'; +import { TFunction } from 'next-i18next'; +import React from 'react'; +import InputMask from 'react-input-mask'; +import FieldLabel from 'shared/components/forms/fields/fieldLabel/FieldLabel'; +import { + $Checkbox, + $RadioButton, +} from 'shared/components/forms/fields/Fields.sc'; +import { Option } from 'shared/components/forms/fields/types'; +import FormSection from 'shared/components/forms/section/FormSection'; +import { + $Grid, + $GridCell, +} from 'shared/components/forms/section/FormSection.sc'; +import { OptionType } from 'shared/types/common'; +import { phoneToLocal } from 'shared/utils/string.utils'; +import { useTheme } from 'styled-components'; + +import { $CompanyInfoField, $CompanyInfoHeader } from '../FormContent.sc'; +import DeMinimisAidForm from './deMinimisAid/DeMinimisAidForm'; +import DeMinimisAidsList from './deMinimisAid/list/DeMinimisAidsList'; + +type Props = { + t: TFunction; + translationsBase: string; + application: Application; + formik: FormikProps>; + fields: ApplicationFields; + getErrorMessage: (fieldName: string) => string | undefined; + languageOptions: OptionType[]; + showDeminimisSection: boolean; + deMinimisAidSet: DeMinimisAid[]; +}; +const CompanySection: React.FC = ({ + t, + application, + translationsBase, + formik, + fields, + getErrorMessage, + languageOptions, + showDeminimisSection, + deMinimisAidSet, +}) => { + const theme = useTheme(); + const { setDeMinimisAids } = React.useContext(DeMinimisContext); + + return ( + <> + + <$GridCell as={$Grid} $colSpan={12}> + <$GridCell $colSpan={3}> + <$CompanyInfoHeader> + {t(`${translationsBase}.companyName`)} * + + <$CompanyInfoField>{application?.company?.name} + + <$GridCell $colSpan={3}> + <$CompanyInfoHeader> + {t(`${translationsBase}.companyBusinessId`)} * + + <$CompanyInfoField> + {application?.company?.businessId} + + + + <$GridCell as={$Grid} $colSpan={12}> + <$GridCell $colSpan={3}> + <$CompanyInfoHeader> + {t(`${translationsBase}.companyStreetAddress`)} * + + <$CompanyInfoField> + {`${application?.company?.streetAddress}, ${application?.company?.postcode}, ${application?.company?.city}`} + + + + <$GridCell $colSpan={6}> + <$Checkbox + id={fields.useAlternativeAddress.name} + name={fields.useAlternativeAddress.name} + label={fields.useAlternativeAddress.label} + checked={formik.values.useAlternativeAddress === true} + errorText={getErrorMessage(fields.useAlternativeAddress.name)} + onChange={formik.handleChange} + onBlur={formik.handleBlur} + aria-invalid={!!getErrorMessage(fields.useAlternativeAddress.name)} + /> + + {formik.values.useAlternativeAddress && ( + <$GridCell + as={$Grid} + $colSpan={12} + css={` + margin-top: ${theme.spacing.l}; + `} + > + <$GridCell $colSpan={4}> + + + <$GridCell $colStart={1} $colSpan={4}> + + + <$GridCell $colSpan={4}> + + + <$GridCell $colSpan={4}> + + + + )} + <$GridCell $colSpan={4} $colStart={1}> + { + const initValue = e.target.value; + const value = + fields.companyBankAccountNumber.mask?.stripVal(initValue) ?? + initValue; + return formik.setFieldValue( + fields.companyBankAccountNumber.name, + value + ); + }} + > + {() => ( + + )} + + + {application?.company?.organizationType.toLowerCase() === + ORGANIZATION_TYPES.ASSOCIATION.toLowerCase() && ( + <> + <$GridCell $colSpan={8} $colStart={1}> + + <$RadioButton + id={`${fields.associationHasBusinessActivities.name}False`} + name={fields.associationHasBusinessActivities.name} + value="false" + label={t( + `${translationsBase}.fields.${fields.associationHasBusinessActivities.name}.no` + )} + onChange={() => { + formik.setFieldValue( + fields.associationHasBusinessActivities.name, + false + ); + }} + // 3 states: null (none is selected), true, false + checked={ + formik.values.associationHasBusinessActivities === false + } + /> + <$RadioButton + id={`${fields.associationHasBusinessActivities.name}True`} + name={fields.associationHasBusinessActivities.name} + value="true" + label={t( + `${translationsBase}.fields.${fields.associationHasBusinessActivities.name}.yes` + )} + onChange={() => + formik.setFieldValue( + fields.associationHasBusinessActivities.name, + true + ) + } + checked={ + formik.values.associationHasBusinessActivities === true + } + /> + + + <$GridCell $colSpan={8}> + + <$Checkbox + id={fields.associationImmediateManagerCheck.name} + name={fields.associationImmediateManagerCheck.name} + label={fields.associationImmediateManagerCheck.placeholder} + required + checked={ + formik.values.associationImmediateManagerCheck === true + } + errorText={getErrorMessage( + fields.associationImmediateManagerCheck.name + )} + onChange={formik.handleChange} + onBlur={formik.handleBlur} + aria-invalid={ + !!getErrorMessage( + fields.associationImmediateManagerCheck.name + ) + } + /> + + + )} + + + <$GridCell $colSpan={4}> + + + <$GridCell $colSpan={4}> + + + <$GridCell $colStart={1} $colSpan={4}> + + + <$GridCell $colSpan={4}> + + + <$GridCell $colStart={1} $colSpan={4}> +