From e07e94df01c5ef0f10cf5fa1b7be267df0034cd1 Mon Sep 17 00:00:00 2001 From: norda-gunni <161026627+norda-gunni@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:08:44 +0000 Subject: [PATCH 01/34] feat(health-insurance-declaration): Make it possible to select only child for health insurance declaration (#15818) * feat: Make it posible to select only child for health insurance declaration and fix a bug that stopped the list of applicants from including children * chore: nx format:write update dirty files * feat: Make it posible to select only child for health insurance declaration and fix a bug that stopped the list of applicants from including children * chore: nx format:write update dirty files * Fix default message * pr comment * Update service * chore: nx format:write update dirty files * simplify applicant handling --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../health-insurance-declaration.utils.ts | 71 ++++++++++--------- .../forms/HealthInsuranceDeclarationForm.ts | 19 +++-- .../src/lib/dataSchema.ts | 9 ++- .../src/lib/messages/application.ts | 42 ++++++----- .../src/utils/data.ts | 45 ++++++++++-- 5 files changed, 124 insertions(+), 62 deletions(-) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance-declaration/health-insurance-declaration.utils.ts b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance-declaration/health-insurance-declaration.utils.ts index 289537cf7f61..6d45b033e222 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance-declaration/health-insurance-declaration.utils.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance-declaration/health-insurance-declaration.utils.ts @@ -56,41 +56,48 @@ const getApplicants = ( const applicants: InsuranceStatementsApplicantDTO[] = [] // Applicant - if (answers.isHealthInsured) { - applicants.push({ - nationalId: answers.applicant.nationalId, - name: answers.applicant.name, - type: 0, - }) - } - - // Spouse - answers.selectedApplicants?.registerPersonsSpouseCheckboxField?.map((s) => { - const externalSpouse = - application.externalData.nationalRegistrySpouse.data.nationalId === s - ? application.externalData.nationalRegistrySpouse.data - : undefined - if (externalSpouse) { + answers.selectedApplicants?.registerPersonsApplicantCheckboxField?.forEach( + (a) => { applicants.push({ - nationalId: externalSpouse.nationalId, - name: externalSpouse.name, - type: 1, + nationalId: answers.applicant.nationalId, + name: answers.applicant.name, + type: 0, }) - } - }) + }, + ) + + // Spouse + answers.selectedApplicants?.registerPersonsSpouseCheckboxField?.forEach( + (s) => { + const externalSpouse = + application.externalData.nationalRegistrySpouse.data.nationalId === s + ? application.externalData.nationalRegistrySpouse.data + : undefined + if (externalSpouse) { + applicants.push({ + nationalId: externalSpouse.nationalId, + name: externalSpouse.name, + type: 1, + }) + } + }, + ) // Children - answers.selectedApplicants?.registerPersonsChildrenCheckboxField?.map((c) => { - const child = application.externalData.childrenCustodyInformation.data.find( - (externalChild) => externalChild.nationalId === c, - ) - if (child) { - applicants.push({ - nationalId: child.nationalId, - name: child.fullName, - type: 2, - }) - } - }) + answers.selectedApplicants?.registerPersonsChildrenCheckboxField?.forEach( + (c) => { + const child = + application.externalData.childrenCustodyInformation.data.find( + (externalChild) => externalChild.nationalId === c, + ) + if (child) { + applicants.push({ + nationalId: child.nationalId, + name: child.fullName, + type: 2, + }) + } + }, + ) return applicants } diff --git a/libs/application/templates/health-insurance-declaration/src/forms/HealthInsuranceDeclarationForm.ts b/libs/application/templates/health-insurance-declaration/src/forms/HealthInsuranceDeclarationForm.ts index 90d4a79d1590..ed46fa830f95 100644 --- a/libs/application/templates/health-insurance-declaration/src/forms/HealthInsuranceDeclarationForm.ts +++ b/libs/application/templates/health-insurance-declaration/src/forms/HealthInsuranceDeclarationForm.ts @@ -34,7 +34,8 @@ import { getCountryNameFromCode, getFullNameFromExternalData, getInsuranceStatus, - getSelectedFamily, + getSelectedApplicants, + getApplicantAsOption, getSpouseAsOptions, hasFamilySelected, } from '../utils' @@ -268,6 +269,14 @@ export const HealthInsuranceDeclarationForm: Form = buildForm({ id: 'registerPersonsMultiFiled', title: m.application.registerPersons.sectionDescription, children: [ + buildCheckboxField({ + id: 'selectedApplicants.registerPersonsApplicantCheckboxField', + title: m.application.registerPersons.applicantTitle, + defaultValue: (application: any) => [ + getApplicantAsOption(application.externalData)[0]?.value, + ], + options: ({ externalData }) => getApplicantAsOption(externalData), + }), buildCheckboxField({ id: 'selectedApplicants.registerPersonsSpouseCheckboxField', title: m.application.registerPersons.spousetitle, @@ -489,11 +498,11 @@ export const HealthInsuranceDeclarationForm: Form = buildForm({ ), }), buildDividerField({}), - // Family table + // Applicants table buildStaticTableField({ - title: m.application.overview.familyTableTitle, + title: m.application.overview.applicantsTableTitle, rows: ({ answers, externalData }) => - getSelectedFamily( + getSelectedApplicants( answers as HealthInsuranceDeclaration, externalData, ), @@ -502,8 +511,6 @@ export const HealthInsuranceDeclarationForm: Form = buildForm({ applicantInformationMessages.labels.nationalId, 'Tengsl', ], - condition: (answers) => - hasFamilySelected(answers as HealthInsuranceDeclaration), }), buildDividerField({ condition: (answers) => diff --git a/libs/application/templates/health-insurance-declaration/src/lib/dataSchema.ts b/libs/application/templates/health-insurance-declaration/src/lib/dataSchema.ts index afaa2c264e33..6acbf5a43996 100644 --- a/libs/application/templates/health-insurance-declaration/src/lib/dataSchema.ts +++ b/libs/application/templates/health-insurance-declaration/src/lib/dataSchema.ts @@ -23,16 +23,21 @@ export const HealthInsuranceDeclarationSchema = z.object({ .refine((v) => !!v), selectedApplicants: z .object({ + registerPersonsApplicantCheckboxField: z.string().array().optional(), registerPersonsSpouseCheckboxField: z.string().array().optional(), registerPersonsChildrenCheckboxField: z.string().array().optional(), - isHealthInsured: z.boolean(), }) .superRefine((v, ctx) => { if ( - !v.isHealthInsured && + !v.registerPersonsApplicantCheckboxField?.length && !v.registerPersonsSpouseCheckboxField?.length && !v.registerPersonsChildrenCheckboxField?.length ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['registerPersonsApplicantCheckboxField'], + params: errors.fields.noSelectedApplicant, + }) ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['registerPersonsChildrenCheckboxField'], diff --git a/libs/application/templates/health-insurance-declaration/src/lib/messages/application.ts b/libs/application/templates/health-insurance-declaration/src/lib/messages/application.ts index b1d5596b7170..155a0adffb01 100644 --- a/libs/application/templates/health-insurance-declaration/src/lib/messages/application.ts +++ b/libs/application/templates/health-insurance-declaration/src/lib/messages/application.ts @@ -43,12 +43,12 @@ export const application = { studentOrTourist: defineMessages({ sectionTitle: { id: 'hid.application:studentOrTourist.section.title', - defaultMessage: 'Námsmaður/ferðamaður', + defaultMessage: 'Nám/ferðir', description: 'Student or Tourist section title', }, sectionDescription: { id: 'hid.application:studentOrTourist.section.description', - defaultMessage: 'Ertu ferðamaður eða námsmaður?', + defaultMessage: 'Er sótt um vegna ferða eða náms?', description: 'Student or Tourist section description', }, touristRadioFieldText: { @@ -79,9 +79,14 @@ export const application = { }, sectionDescription: { id: 'hid.application:registerPersons.section.description', - defaultMessage: 'Ég er einnig að sækja um fyrir', + defaultMessage: 'Ég er að sækja um fyrir', description: 'Register persons section description', }, + applicantTitle: { + id: 'hid.application:registerPersons.section.applicantTitle', + defaultMessage: 'Umsækjandi', + description: 'Register persons title', + }, spousetitle: { id: 'hid.application:registerPersons.section.spouseTitle', defaultMessage: 'Maki', @@ -116,7 +121,7 @@ export const application = { }, studentSectionPlaceholderText: { id: 'hid.application:residency.section.student.placeholderSelectText', - defaultMessage: 'Veldur land sem þú ferðast til', + defaultMessage: 'Veldu land sem ferðast á til', description: 'Student residency selection placeholder text', }, }), @@ -200,17 +205,17 @@ export const application = { }, studentOrTouristTitle: { id: 'hid.application:overview.section.studentOrTouristTitle', - defaultMessage: 'Ertu ferðamaður eða námsmaður', + defaultMessage: 'Er sótt um vegna ferða eða náms', description: 'Overview section Student or Tourist title', }, studentOrTouristTouristText: { id: 'hid.application:overview.section.studentOrTouristTouristText', - defaultMessage: 'Ferðamaður', + defaultMessage: 'Ferða', description: 'Overview section Student or Tourist: Tourist text', }, studentOrTouristStudentText: { id: 'hid.application:overview.section.studentOrTouristStudentText', - defaultMessage: 'Námsmaður', + defaultMessage: 'Náms', description: 'Overview section Student or Tourist: Student text', }, applicantInfoTitle: { @@ -218,25 +223,30 @@ export const application = { defaultMessage: 'Persónu upplýsingar', description: 'Overview section applicant title', }, - familyTableTitle: { - id: 'hid.application:overview.section.familyTableHeader', - defaultMessage: 'Maki og börn', - description: 'Overview section family table title', + applicantsTableTitle: { + id: 'hid.application:overview.section.applicantsTableHeader', + defaultMessage: 'Sótt er um fyrir', + description: 'Overview section applicant table title', }, familyTableRelationHeader: { id: 'hid.application:overview.section.familyTableHeaderRelationText', defaultMessage: 'Tengsl', - description: 'Overview section family table title', + description: 'Overview section applicant table title', + }, + familyTableRelationApplicantText: { + id: 'hid.application:overview.section.applicantTableApplicantRelationText', + defaultMessage: 'Umsækjandi', + description: 'Overview section applicant table applicant text', }, familyTableRelationSpouseText: { - id: 'hid.application:overview.section.familyTableSpouseRelationText', + id: 'hid.application:overview.section.applicantTableSpouseRelationText', defaultMessage: 'Maki', - description: 'Overview section family table spouse relation text', + description: 'Overview section applicant table spouse relation text', }, familyTableRelationChildText: { - id: 'hid.application:overview.section.familyTableChildRelationText', + id: 'hid.application:overview.section.applicantTableChildRelationText', defaultMessage: 'Barn', - description: 'Overview section family table child relation text', + description: 'Overview section applicant table child relation text', }, dateTitle: { id: 'hid.application:overview.section.DateTitle', diff --git a/libs/application/templates/health-insurance-declaration/src/utils/data.ts b/libs/application/templates/health-insurance-declaration/src/utils/data.ts index 392d63665dec..3fa4ed06afc3 100644 --- a/libs/application/templates/health-insurance-declaration/src/utils/data.ts +++ b/libs/application/templates/health-insurance-declaration/src/utils/data.ts @@ -134,6 +134,23 @@ export const getChildrenAsOptions = (externalData: ExternalData): Option[] => { return [] } +export const getApplicantFromExternalData = ( + externalData: ExternalData, +): NationalRegistryIndividual => { + return externalData.nationalRegistry?.data as NationalRegistryIndividual +} + +export const getApplicantAsOption = (externalData: ExternalData): Option[] => { + const individual = getApplicantFromExternalData(externalData) + return [ + { + value: individual?.nationalId, + label: individual?.fullName, + subLabel: `${format(individual?.nationalId)}`, + }, + ] +} + export const getSpouseAsOptions = (externalData: ExternalData): Option[] => { const spouse = getSpouseFromExternalData(externalData) @@ -150,16 +167,33 @@ export const getSpouseAsOptions = (externalData: ExternalData): Option[] => { return [] } -export const getSelectedFamily = ( +export const getSelectedApplicants = ( answers: HealthInsuranceDeclaration, externalData: ExternalData, ) => { + const applicant = getApplicantFromExternalData(externalData) const spouse = getSpouseFromExternalData(externalData) const children = getChildrenFromExternalData(externalData) - let selectedFamily: StaticText[][] = [] + let selectedApplicants: StaticText[][] = [] + + selectedApplicants = selectedApplicants.concat( + answers.selectedApplicants?.registerPersonsApplicantCheckboxField + ? answers.selectedApplicants?.registerPersonsApplicantCheckboxField?.map( + (a) => { + if (a === applicant.nationalId) { + return [ + applicant.fullName, + applicant.nationalId, + m.overview.familyTableRelationApplicantText, + ] + } else return [] + }, + ) + : [], + ) if (spouse) { - selectedFamily = selectedFamily.concat( + selectedApplicants = selectedApplicants.concat( answers.selectedApplicants?.registerPersonsSpouseCheckboxField ? answers.selectedApplicants?.registerPersonsSpouseCheckboxField?.map( (s) => { @@ -187,11 +221,10 @@ export const getSelectedFamily = ( ] }, ) - if (selectedChildren) { - selectedFamily.concat(selectedChildren) + selectedApplicants = selectedApplicants.concat(selectedChildren) } - return selectedFamily + return selectedApplicants } export const getInsuranceStatementDataFromExternalData = ( From 7b6a1829e2a5047203f57bd465c4b269bc531ff0 Mon Sep 17 00:00:00 2001 From: norda-gunni <161026627+norda-gunni@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:37:22 +0000 Subject: [PATCH 02/34] chore(mocking): Relocate and update mocking readme as well as accident-notification mock data (#15841) * chore(mocking): Relocate and update mocking readme as well as accident-notification mock data * chore: nx format:write update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/application/templates/README.md | 46 +++++++++++++++++++ .../mockData}/accident-notification.json | 0 .../health-insurance-declaration.json | 2 +- mocks/README.md | 38 ++++++++++----- 4 files changed, 74 insertions(+), 12 deletions(-) rename {mocks/national-registry/v2 => libs/application/templates/accident-notification/mockData}/accident-notification.json (100%) diff --git a/libs/application/templates/README.md b/libs/application/templates/README.md index 684234ff491f..55b51ce87c0f 100644 --- a/libs/application/templates/README.md +++ b/libs/application/templates/README.md @@ -1 +1,47 @@ # Templates + +## Mocking XROAD endpoints with Mockoon for templates + +### Prerequisites + +Since the requests from the services we are running locally default to making their calls on port `8081`so the mock will be listening on port `8081`. This means the port forwarding for xroad needs to be listening on port `8082` (or some other port) and then we will set the mock server will forward requests it does not have mock responses for to that port. + +To set the port forwarding to listen on port `8082` you can pass a port argument to the proxies script like so `yarn proxies xroad --p 8082`. Alternatively if you use kubectl and socat just replace `8081:80` with `8082:80`. + +### How to + +The mockoon CLI is a dev dependency so it should be installed along with everything else when you `yarn`. When you want to use the mockoon-cli you simply call `mockoon-cli start --data `. The capture file can be one you made yourself (see below) or some applications have mock files already created for them, in which case they can be found under `libs/application//mockData`. + +Mockoon should now be listening on port `8081` and proxying non-mocked traffic to port `8082`. + +For more in-depth instructions, you can check out the [mockoon site](https://mockoon.com/cli/). + +### Mockoon app + +It is very much recommended to install the [Mockoon app](https://mockoon.com/download/) as that allows you to both capture new mock data, select which endppoints should be mocked or even modify the mocked payloads to name a few things. + +### Current mocks + +If mockdata is available for an application it should be in the mockData directory in the application in question (see above under how to). If you create mock data for an application that doesn't have any, consider adding it under the appropriate directory. + +## Q&A + +### What if I need to call an endpoint that isn't mocked + +No problem, mockoon will transparently proxy whatever requests it does not have mocks for. + +### What if I want to get an actual response from an endpoint being mocked + +Find the endpoint in question in the `Routes` panel, click on the three little dots in the upper right corner of the route entry and select `Toggle`. This will cause any incoming requests to be proxied rather than mocked. + +### What if I want to update the mocked data for an endpoint + +The simplest way is to delete the existing endpoint by finding it in the routes list as above but selecting `Delete` instead of `Toggle`, turning on the recording function by clicking the little dot in the `Logs` tab above the request list and then performing a call to the underlying endpoint. You can also toggle the endpoint mock off as described above, do a call to the endpoint, find the log for that call in the logs tab and simply copy over the returned data. + +### My calls aren't being mocked + +The mocks are currently set up for the Gervimaður Færeyjar fake person. If you need to mock other fake persons, you can download the [mockoon app](https://mockoon.com/download/) and either open the applicable collection or start your own with [automocking](https://mockoon.com/docs/latest/logging-and-recording/auto-mocking-and-recording/). + +### Does the mocking proxy only respond with mocks when the proxied service is down? + +No, one of the benefits of mocking locally is a significantly shorter response time, and to achieve that, it's necessary to use mocks even if the underlying service is operational. If you want to send calls to the proxied endpoint you can toggle the mock off in the `Routes` tab. diff --git a/mocks/national-registry/v2/accident-notification.json b/libs/application/templates/accident-notification/mockData/accident-notification.json similarity index 100% rename from mocks/national-registry/v2/accident-notification.json rename to libs/application/templates/accident-notification/mockData/accident-notification.json diff --git a/libs/application/templates/health-insurance-declaration/mockData/health-insurance-declaration.json b/libs/application/templates/health-insurance-declaration/mockData/health-insurance-declaration.json index feaf925be9f6..30cef8efeb8a 100644 --- a/libs/application/templates/health-insurance-declaration/mockData/health-insurance-declaration.json +++ b/libs/application/templates/health-insurance-declaration/mockData/health-insurance-declaration.json @@ -705,7 +705,7 @@ "responses": [ { "uuid": "a9384814-ffa3-4bdd-ae69-35d53bb264c7", - "body": "{\n \"canApply\": true,\n \"isInsured\": true,\n \"hasInsuranceStatement\": true,\n \"insuranceStatementValidUntil\": \"2024-12-05\",\n \"comment\": \"Umsækjandi á tryggingayfirlýsingu í gildi til 05.12.2024\"\n}", + "body": "{\n \"canApply\": true,\n \"isInsured\": false,\n \"hasInsuranceStatement\": false,\n \"insuranceStatementValidUntil\": \"2024-12-05\",\n \"comment\": \"Umsækjandi á tryggingayfirlýsingu í gildi til 05.12.2024\"\n}", "latency": 0, "statusCode": 200, "label": "", diff --git a/mocks/README.md b/mocks/README.md index dc1c442dbcf8..89602163d283 100644 --- a/mocks/README.md +++ b/mocks/README.md @@ -1,29 +1,45 @@ -# Mocking national registry XROAD endpoints +# Mocking REST endpoints with recorded data -## Prerequisites +It can be incredibly useful to be able to alter responses from a REST endpoint or even entirely replace an unavailable one. -Since the mock will be listening on port `8081`, the port forwarding for xroad needs to be listening on port `8082` as that is where the mock server will forward requests it does not have mock responses for. +This readme covers how to do that with Mockoon + +## Proxying XROAD + +Since the requests from the services we are running locally default to making their calls on port `8081`so the mock will be listening on port `8081`. This means the port forwarding for xroad needs to be listening on port `8082` (or some other port) and then we will set the mock server will forward requests it does not have mock responses for to that port. To set the port forwarding to listen on port `8082` you can pass a port argument to the proxies script like so `yarn proxies xroad --p 8082`. Alternatively if you use kubectl and socat just replace `8081:80` with `8082:80`. -## How to +## Mockoon-CLI -First you'll want to install [mockoon-cli](https://github.com/mockoon/mockoon/tree/main/packages/cli#installation), then you just call `mockoon-cli start --data `. The capture file can be one you made yourself (see below) or one that has been checked in such as `national-registryv2.json` Mockoon will now start listening on port `8081` and proxying non-mocked traffic to port `8082`. +If you only want to proxy and mock using existing mock data, you'll want to install [mockoon-cli](https://github.com/mockoon/mockoon/tree/main/packages/cli#installation). Then you just call `mockoon-cli start --data `. The capture file can be one you made yourself (see below) or one that has been checked in such as `national-registryv2.json` Mockoon will now start listening on port `8081` and proxying non-mocked traffic to port `8082`. For more in-depth instructions, you can check out the [mockoon site](https://mockoon.com/cli/). ## Current mocks -Currently, only a capture file for the accident notification form for Gervimaður Færeyjar on national registry V2 is included. +### Applications + +If mockdata is available for an application it should be in the mockData directory in the application in question (see above under how to). If you create mock data for an application that doesn't have any, consider adding it under the appropriate directory. + +## Q&A -## What if I need to call an endpoint that isn't mocked +### What if I need to call an endpoint that isn't mocked No problem, mockoon will transparently proxy whatever requests it does not have mocks for. -## My calls aren't being mocked +### What if I want to get an actual response from an endpoint being mocked + +Find the endpoint in question in the `Routes` panel, click on the three little dots in the upper right corner of the route entry and select `Toggle`. This will cause any incoming requests to be proxied rather than mocked. + +### What if I want to update the mocked data for an endpoint + +The simplest way is to delete the existing endpoint by finding it in the routes list as above but selecting `Delete` instead of `Toggle`, turning on the recording function by clicking the little dot in the `Logs` tab above the request list and then performing a call to the underlying endpoint. You can also toggle the endpoint mock off as described above, do a call to the endpoint, find the log for that call in the logs tab and simply copy over the returned data. + +### My calls aren't being mocked -The mocks are currently set up for the Gervimaður Færeyjar fake person. If you need to mock other fake persons, you can download the [mockoon app](https://mockoon.com/download/) and either open the `national-registry.json` collection or start your own with [automocking](https://mockoon.com/docs/latest/logging-and-recording/auto-mocking-and-recording/). +The mocks are currently set up for the Gervimaður Færeyjar fake person. If you need to mock other fake persons, you can download the [mockoon app](https://mockoon.com/download/) and either open the applicable collection or start your own with [automocking](https://mockoon.com/docs/latest/logging-and-recording/auto-mocking-and-recording/). -## Does the mocking proxy only respond with mocks when the proxied service is down? +### Does the mocking proxy only respond with mocks when the proxied service is down? -No, one of the benefits of mocking locally is a significantly shorter response time, and to achieve that, it's necessary to use mocks even if the underlying service is operational. +No, one of the benefits of mocking locally is a significantly shorter response time, and to achieve that, it's necessary to use mocks even if the underlying service is operational. If you want to send calls to the proxied endpoint you can toggle the mock off in the `Routes` tab. From 0fa0872dab2e0bc02ad1e5aa6aac5ded35b9fc7e Mon Sep 17 00:00:00 2001 From: unakb Date: Wed, 4 Sep 2024 11:45:01 +0000 Subject: [PATCH 03/34] feat(j-s): Add "acknowledged" to case and subpoena (#15860) * feat(j-s): Added acknowledged to case and subpoena response * fix(j-s): Add mock to response * Update subpoena.response.ts --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/app/modules/cases/models/case.response.ts | 4 ++++ .../src/app/modules/cases/models/subpoena.response.ts | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/models/case.response.ts b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/models/case.response.ts index b300b0074575..4f8159657381 100644 --- a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/models/case.response.ts +++ b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/models/case.response.ts @@ -11,6 +11,9 @@ class IndictmentCaseData { @ApiProperty({ type: String }) caseNumber!: string + @ApiProperty({ type: Boolean }) + acknowledged?: boolean + @ApiProperty({ type: [Groups] }) groups!: Groups[] } @@ -37,6 +40,7 @@ export class CaseResponse { caseId: res.id, data: { caseNumber: `${t.caseNumber} ${res.courtCaseNumber}`, + acknowledged: false, // TODO: Connect to real data groups: [ { label: t.defendant, diff --git a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/models/subpoena.response.ts b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/models/subpoena.response.ts index c6c022006edc..a39933df0f9f 100644 --- a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/models/subpoena.response.ts +++ b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/models/subpoena.response.ts @@ -25,6 +25,9 @@ class SubpoenaData { @ApiProperty({ type: () => String }) title!: string + @ApiProperty({ type: Boolean }) + acknowledged?: boolean + @ApiProperty({ type: () => [Groups] }) groups!: Groups[] } @@ -60,12 +63,13 @@ export class SubpoenaResponse { (dateLog) => dateLog.dateType === DateType.ARRAIGNMENT_DATE, ) const arraignmentDate = subpoenaDateLog?.date ?? '' - const subpoenaCreatedDate = subpoenaDateLog?.created ?? '' + const subpoenaCreatedDate = subpoenaDateLog?.created ?? '' //TODO: Change to subpoena created in RLS return { caseId: internalCase.id, data: { title: t.subpoena, + acknowledged: false, // TODO: Connect to real data groups: [ { label: `${t.caseNumber} ${internalCase.courtCaseNumber}`, From 92794074856c3ceef7332297ad605af69f5c0fca Mon Sep 17 00:00:00 2001 From: albinagu <47886428+albinagu@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:46:19 +0000 Subject: [PATCH 04/34] fix(driving-license): digital license, no pickup (#15881) * fix(driving-license): digital license, no pickup * chore: nx format:write update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/dataProviders/index.ts | 1 - .../driving-license/src/forms/draft/draft.ts | 2 - .../src/forms/draft/subSectionDelivery.ts | 51 ------------------- .../src/forms/prerequisites/getForm.ts | 4 +- .../sectionDigitalLicenseInfo.ts | 30 +++++++++++ .../prerequisites/sectionExternalData.ts | 5 -- .../src/lib/drivingLicenseTemplate.ts | 2 - .../driving-license/src/lib/messages.ts | 37 ++++++++++---- 8 files changed, 60 insertions(+), 72 deletions(-) delete mode 100644 libs/application/templates/driving-license/src/forms/draft/subSectionDelivery.ts create mode 100644 libs/application/templates/driving-license/src/forms/prerequisites/sectionDigitalLicenseInfo.ts diff --git a/libs/application/templates/driving-license/src/dataProviders/index.ts b/libs/application/templates/driving-license/src/dataProviders/index.ts index c6d5f3243fa1..fe90c3829b53 100644 --- a/libs/application/templates/driving-license/src/dataProviders/index.ts +++ b/libs/application/templates/driving-license/src/dataProviders/index.ts @@ -9,7 +9,6 @@ export { UserProfileApi, CurrentLicenseApi, DrivingAssessmentApi, - JurisdictionApi, QualityPhotoApi, ExistingApplicationApi, } from '@island.is/application/types' diff --git a/libs/application/templates/driving-license/src/forms/draft/draft.ts b/libs/application/templates/driving-license/src/forms/draft/draft.ts index d120dd2231c3..3757e636945a 100644 --- a/libs/application/templates/driving-license/src/forms/draft/draft.ts +++ b/libs/application/templates/driving-license/src/forms/draft/draft.ts @@ -6,7 +6,6 @@ import { subSectionTempInfo } from './subSectionTempInfo' import { subSectionOtherCountry } from './subSectionOtherCountry' import { subSectionOtherCountryDirections } from './subSectionOtherCountryDirections' import { subSectionQualityPhoto } from './subSectionQualityPhoto' -import { subSectionDelivery } from './subSectionDelivery' import { subSectionHealthDeclaration } from './subSectionHealthDeclaration' import { subSectionSummary } from './subSectionSummary' import { subSectionPhone } from './subSectionPhone' @@ -33,7 +32,6 @@ export const draft: Form = buildForm({ subSectionOtherCountry, subSectionOtherCountryDirections, subSectionQualityPhoto, - subSectionDelivery, subSectionHealthDeclaration, subSectionSummary, ], diff --git a/libs/application/templates/driving-license/src/forms/draft/subSectionDelivery.ts b/libs/application/templates/driving-license/src/forms/draft/subSectionDelivery.ts deleted file mode 100644 index a6295a5344c5..000000000000 --- a/libs/application/templates/driving-license/src/forms/draft/subSectionDelivery.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - buildDescriptionField, - buildMultiField, - buildSelectField, - buildSubSection, -} from '@island.is/application/core' -import { m } from '../../lib/messages' -import { - chooseDistrictCommissionerDescription, - hasNoDrivingLicenseInOtherCountry, -} from '../../lib/utils' - -import { Jurisdiction } from '@island.is/clients/driving-license' - -export const subSectionDelivery = buildSubSection({ - id: 'user', - title: m.informationSectionTitle, - condition: hasNoDrivingLicenseInOtherCountry, - children: [ - buildMultiField({ - id: 'info', - title: m.pickupLocationTitle, - space: 1, - children: [ - buildDescriptionField({ - id: 'afhending', - title: m.districtCommisionerTitle, - titleVariant: 'h4', - description: chooseDistrictCommissionerDescription, - }), - buildSelectField({ - id: 'jurisdiction', - title: m.districtCommisionerPickup, - disabled: false, - required: true, - options: ({ - externalData: { - jurisdictions: { data }, - }, - }) => { - return (data as Jurisdiction[]).map(({ id, name, zip }) => ({ - value: `${id}`, - label: name, - tooltip: `Póstnúmer ${zip}`, - })) - }, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/driving-license/src/forms/prerequisites/getForm.ts b/libs/application/templates/driving-license/src/forms/prerequisites/getForm.ts index a003e2366867..404cabe3d5fe 100644 --- a/libs/application/templates/driving-license/src/forms/prerequisites/getForm.ts +++ b/libs/application/templates/driving-license/src/forms/prerequisites/getForm.ts @@ -7,6 +7,7 @@ import { sectionExternalData } from './sectionExternalData' import { sectionApplicationFor } from './sectionApplicationFor' import { sectionRequirements } from './sectionRequirements' import { sectionExistingApplication } from './sectionExistingApplication' +import { sectionDigitalLicenseInfo } from './sectionDigitalLicenseInfo' export const getForm = ({ allowFakeData = false, @@ -29,6 +30,7 @@ export const getForm = ({ sectionExternalData, sectionExistingApplication, ...(allowPickLicense ? [sectionApplicationFor(allowBELicense)] : []), + sectionDigitalLicenseInfo, sectionRequirements, ], }), @@ -43,7 +45,7 @@ export const getForm = ({ children: [], }), buildSection({ - id: 'confim', + id: 'confirm', title: m.applicationDone, children: [], }), diff --git a/libs/application/templates/driving-license/src/forms/prerequisites/sectionDigitalLicenseInfo.ts b/libs/application/templates/driving-license/src/forms/prerequisites/sectionDigitalLicenseInfo.ts new file mode 100644 index 000000000000..279e17edd222 --- /dev/null +++ b/libs/application/templates/driving-license/src/forms/prerequisites/sectionDigitalLicenseInfo.ts @@ -0,0 +1,30 @@ +import { + buildAlertMessageField, + buildMultiField, + buildSubSection, +} from '@island.is/application/core' +import { m } from '../../lib/messages' +import { B_TEMP } from '../../lib/constants' + +export const sectionDigitalLicenseInfo = buildSubSection({ + id: 'digitalLicenseInfo', + title: m.digitalLicenseInfoTitle, + children: [ + buildMultiField({ + id: 'info', + title: m.digitalLicenseInfoTitle, + description: m.digitalLicenseInfoDescription, + children: [ + buildAlertMessageField({ + id: 'digitalLicenseInfo', + title: m.digitalLicenseInfoAlertTitle, + message: ({ answers }) => + answers.applicationFor === B_TEMP + ? m.digitalLicenseInfoAlertMessageBTemp + : m.digitalLicenseInfoAlertMessageBFull, + alertType: 'info', + }), + ], + }), + ], +}) diff --git a/libs/application/templates/driving-license/src/forms/prerequisites/sectionExternalData.ts b/libs/application/templates/driving-license/src/forms/prerequisites/sectionExternalData.ts index b920cf6deeca..5a8c4a131fdf 100644 --- a/libs/application/templates/driving-license/src/forms/prerequisites/sectionExternalData.ts +++ b/libs/application/templates/driving-license/src/forms/prerequisites/sectionExternalData.ts @@ -10,7 +10,6 @@ import { UserProfileApi, CurrentLicenseApi, DrivingAssessmentApi, - JurisdictionApi, QualityPhotoApi, ExistingApplicationApi, } from '@island.is/application/types' @@ -57,10 +56,6 @@ export const sectionExternalData = buildSubSection({ provider: DrivingAssessmentApi, title: '', }), - buildDataProviderItem({ - provider: JurisdictionApi, - title: '', - }), buildDataProviderItem({ provider: SyslumadurPaymentCatalogApi, title: '', diff --git a/libs/application/templates/driving-license/src/lib/drivingLicenseTemplate.ts b/libs/application/templates/driving-license/src/lib/drivingLicenseTemplate.ts index 210c180849c8..43d724e7f327 100644 --- a/libs/application/templates/driving-license/src/lib/drivingLicenseTemplate.ts +++ b/libs/application/templates/driving-license/src/lib/drivingLicenseTemplate.ts @@ -11,7 +11,6 @@ import { ApplicationStateSchema, DefaultEvents, defineTemplateApi, - JurisdictionApi, CurrentLicenseApi, DrivingAssessmentApi, NationalRegistryUserApi, @@ -133,7 +132,6 @@ const template: ApplicationTemplate< }, }), DrivingAssessmentApi, - JurisdictionApi, QualityPhotoApi, ExistingApplicationApi.configure({ params: { diff --git a/libs/application/templates/driving-license/src/lib/messages.ts b/libs/application/templates/driving-license/src/lib/messages.ts index 9ae2238b273b..8aabdf363f80 100644 --- a/libs/application/templates/driving-license/src/lib/messages.ts +++ b/libs/application/templates/driving-license/src/lib/messages.ts @@ -105,11 +105,6 @@ export const m = defineMessages({ defaultMessage: 'Sýslumannsembætti', description: 'Information', }, - pickupLocationTitle: { - id: 'dl.application:pickuplocation', - defaultMessage: 'Afhendingarstaður', - description: 'location for pickup', - }, informationApplicant: { id: 'dl.application:information.applicant', defaultMessage: 'Umsækjandi', @@ -432,6 +427,33 @@ export const m = defineMessages({ description: 'Your application for a full driving license has been received. Before a full driving license can be applied for, you must bring the following to the district commissioner.', }, + digitalLicenseInfoTitle: { + id: 'dl.application:digitalLicenseInfoTitle', + defaultMessage: 'Stafrænt ökuskírteini', + description: 'Digital driving license', + }, + digitalLicenseInfoDescription: { + id: 'dl.application:digitalLicenseInfoDescription', + defaultMessage: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + description: 'Digital driving license', + }, + digitalLicenseInfoAlertTitle: { + id: 'dl.application:digitalLicenseInfoAlertTitle', + defaultMessage: 'Athugið', + description: 'Digital driving license', + }, + digitalLicenseInfoAlertMessageBTemp: { + id: 'dl.application:digitalLicenseInfoAlertMessageBTemp#markdown', + defaultMessage: + 'Þú ert að sækja um bráðabirgðaökuskírteini. Ökuskírteini þitt verður einungis gefið út sem stafrænt ökuskírteini og verður aðgengilegt fyrir þig um leið og öll skilyrði fyrir bráðabirgðaökuskírteini eru uppfyllt.', + description: 'Digital driving license', + }, + digitalLicenseInfoAlertMessageBFull: { + id: 'dl.application:digitalLicenseInfoAlertMessageBFull#markdown', + defaultMessage: + 'Þú ert að sækja um fullnaðarökuskírteini. Ökuskírteini þitt verður núna einungis gefið út sem stafrænt ökuskírteini og verður aðgengilegt fyrir þig þegar þú hefur lokið þessari pöntun um fullnaðarökuskírteini. Fullnaðarökuskírteini þitt verður framleitt í plasti í byrjun febrúar 2025 og sent til þín með Póstinum, á skráð lögheimili þitt um leið og plastökuskírteinið er tilbúið.', + description: 'Digital driving license', + }, congratulationsTempHelpText: { id: 'dl.application:congratulationsTempHelpText', defaultMessage: @@ -559,11 +581,6 @@ export const m = defineMessages({ defaultMessage: 'Sýslumannsembætti', description: 'Title for district commissioner', }, - districtCommisionerPickup: { - id: 'dl.application:districtCommisionerPickup', - defaultMessage: 'Afhending', - description: 'Pickup for district commissioner', - }, chooseDistrictCommisionerForFullLicense: { id: 'dl.application:chooseDistrictCommisionerForFullLicense', defaultMessage: From 42fef4d3389c0310dc0aab5d1575d40abb733177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rarinn=20Gunnar=20=C3=81rnason?= Date: Wed, 4 Sep 2024 14:04:22 +0000 Subject: [PATCH 05/34] fix(applications-admin-portal): Remove procurer name from application (#15887) --- .../src/components/ApplicationDetails/ApplicationDetails.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/libs/portals/admin/application-system/src/components/ApplicationDetails/ApplicationDetails.tsx b/libs/portals/admin/application-system/src/components/ApplicationDetails/ApplicationDetails.tsx index 266b82f86dee..e68600af3121 100644 --- a/libs/portals/admin/application-system/src/components/ApplicationDetails/ApplicationDetails.tsx +++ b/libs/portals/admin/application-system/src/components/ApplicationDetails/ApplicationDetails.tsx @@ -101,9 +101,6 @@ export const ApplicationDetails = ({ - - {actor} - {actor} From 2af28011b1507bccb2002ee847220c20612416d2 Mon Sep 17 00:00:00 2001 From: unakb Date: Wed, 4 Sep 2024 15:30:38 +0000 Subject: [PATCH 06/34] chore(j-s): Add institution addresses to db (#15886) * chore(j-s): Add institution addresses to db * chore(j-s): Use institution from db for subpoena --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../20240904100012-update-institution.js | 48 +++++++++++++++++++ .../backend/src/app/formatters/subpoenaPdf.ts | 24 +--------- .../modules/institution/institution.model.ts | 4 ++ 3 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 apps/judicial-system/backend/migrations/20240904100012-update-institution.js diff --git a/apps/judicial-system/backend/migrations/20240904100012-update-institution.js b/apps/judicial-system/backend/migrations/20240904100012-update-institution.js new file mode 100644 index 000000000000..d107dfba7de4 --- /dev/null +++ b/apps/judicial-system/backend/migrations/20240904100012-update-institution.js @@ -0,0 +1,48 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('institution', 'address', { + type: Sequelize.STRING, + allowNull: true, + }) + + const institutionsToUpdate = [ + { + name: 'Héraðsdómur Reykjavíkur', + address: 'Dómhúsið við Lækjartorg, Reykjavík', + }, + { name: 'Héraðsdómur Reykjaness', address: 'Fjarðargata 9, Hafnarfirði' }, + { + name: 'Héraðsdómur Vesturlands', + address: 'Bjarnarbraut 8, Borgarnesi', + }, + { name: 'Héraðsdómur Vestfjarða', address: 'Hafnarstræti 9, Ísafirði' }, + { + name: 'Héraðsdómur Norðurlands vestra', + address: 'Skagfirðingabraut 21, Sauðárkróki', + }, + { + name: 'Héraðsdómur Norðurlands eystra', + address: 'Hafnarstræti 107, 4. hæð, Akureyri', + }, + { name: 'Héraðsdómur Austurlands', address: 'Lyngás 15, Egilsstöðum' }, + { name: 'Héraðsdómur Suðurlands', address: 'Austurvegur 4, Selfossi' }, + ] + + await queryInterface.sequelize.transaction(async (transaction) => { + for (const institution of institutionsToUpdate) { + await queryInterface.bulkUpdate( + 'institution', + { address: institution.address }, + { name: institution.name }, + { transaction }, + ) + } + }) + }, + + down: async (queryInterface) => { + await queryInterface.removeColumn('institution', 'address') + }, +} diff --git a/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts index 40af05fe4ba9..313088079040 100644 --- a/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts +++ b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts @@ -22,28 +22,6 @@ import { setTitle, } from './pdfHelpers' -type DistrictCourts = - | 'Héraðsdómur Reykjavíkur' - | 'Héraðsdómur Reykjaness' - | 'Héraðsdómur Vesturlands' - | 'Héraðsdómur Vestfjarða' - | 'Héraðsdómur Norðurlands vestra' - | 'Héraðsdómur Norðurlands eystra' - | 'Héraðsdómur Austurlands' - | 'Héraðsdómur Suðurlands' - -// TODO: Move to databas -const DistrictCourtLocation: Record = { - 'Héraðsdómur Reykjavíkur': 'Dómhúsið við Lækjartorg, Reykjavík', - 'Héraðsdómur Reykjaness': 'Fjarðargata 9, Hafnarfirði', - 'Héraðsdómur Vesturlands': 'Bjarnarbraut 8, Borgarnesi', - 'Héraðsdómur Vestfjarða': 'Hafnarstræti 9, Ísafirði', - 'Héraðsdómur Norðurlands vestra': 'Skagfirðingabraut 21, Sauðárkróki', - 'Héraðsdómur Norðurlands eystra': 'Hafnarstræti 107, 4. hæð, Akureyri', - 'Héraðsdómur Austurlands': 'Lyngás 15, Egilsstöðum', - 'Héraðsdómur Suðurlands': 'Austurvegur 4, Selfossi', -} - export const createSubpoena = ( theCase: Case, defendant: Defendant, @@ -86,7 +64,7 @@ export const createSubpoena = ( if (theCase.court?.name) { addNormalText( doc, - DistrictCourtLocation[theCase.court.name as DistrictCourts], + theCase.court.address || 'Ekki skráð', // the latter shouldn't happen, if it does we have an problem with the court data 'Times-Roman', ) } diff --git a/apps/judicial-system/backend/src/app/modules/institution/institution.model.ts b/apps/judicial-system/backend/src/app/modules/institution/institution.model.ts index be40cbdadab2..202c29f3f76d 100644 --- a/apps/judicial-system/backend/src/app/modules/institution/institution.model.ts +++ b/apps/judicial-system/backend/src/app/modules/institution/institution.model.ts @@ -62,4 +62,8 @@ export class Institution extends Model { @Column({ type: DataType.STRING, allowNull: true }) @ApiPropertyOptional({ type: String }) nationalId?: string + + @Column({ type: DataType.STRING, allowNull: true }) + @ApiPropertyOptional({ type: String }) + address?: string } From e61152a18b35ff66a553168c4d6ae599abc91c10 Mon Sep 17 00:00:00 2001 From: berglindoma13 Date: Wed, 4 Sep 2024 17:02:24 +0000 Subject: [PATCH 07/34] fix(universtity-gateway): fixing modeOfDelivery error (#15891) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../university/university.service.ts | 17 +++++++-- .../templates/university/src/shared/types.ts | 36 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts index 3405f7851e79..c6d9b0006ec3 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts @@ -4,7 +4,6 @@ import { TemplateApiModuleActionProps } from '../../../types' import { BaseTemplateApiService } from '../../base-template-api.service' import { ApplicationTypes, - ApplicationWithAttachments, NationalRegistryIndividual, } from '@island.is/application/types' @@ -23,7 +22,10 @@ import { CreateApplicationDtoEducationOptionEnum, } from '@island.is/clients/university-gateway-api' -import { UniversityAnswers } from '@island.is/application/templates/university' +import { + UniversityAnswers, + UniversityGatewayProgram, +} from '@island.is/application/templates/university' import { Auth, AuthMiddleware } from '@island.is/auth-nest-tools' import { InnaClientService } from '@island.is/clients/inna' @@ -120,6 +122,14 @@ export class UniversityService extends BaseTemplateApiService { email: userFromAnswers.email, phone: userFromAnswers.phone, } + const programs = externalData.programs + ?.data as Array + const modesOfDeliveryFromChosenProgram = programs.find( + (x) => x.id === answers.programInformation.program, + ) + const defaultModeOfDelivery = modesOfDeliveryFromChosenProgram + ?.modeOfDelivery[0] + .modeOfDelivery as CreateApplicationDtoModeOfDeliveryEnum //all possible types of education data from the application answers const educationOptionChosen = @@ -235,7 +245,8 @@ export class UniversityService extends BaseTemplateApiService { universityId: answers.programInformation.university, programId: answers.programInformation.program, modeOfDelivery: mapStringToEnum( - answers.modeOfDeliveryInformation.chosenMode, + answers.modeOfDeliveryInformation?.chosenMode || + defaultModeOfDelivery, CreateApplicationDtoModeOfDeliveryEnum, 'CreateApplicationDtoModeOfDeliveryEnum', ), diff --git a/libs/application/templates/university/src/shared/types.ts b/libs/application/templates/university/src/shared/types.ts index 3afa07a83d31..2d5eb6aba94b 100644 --- a/libs/application/templates/university/src/shared/types.ts +++ b/libs/application/templates/university/src/shared/types.ts @@ -20,3 +20,39 @@ export type CurrentApplication = { id: string nationalId: string } + +export type UniversityGatewayProgram = { + active: boolean + applicationEndDate: string + applicationInUniversityGateway: boolean + applicationPeriodOpen: boolean + applicationStartDate: string + costPerYear?: number + credits: number + degreeAbbreviation: string + degreeType: string + departmentNameEn: string + departmentNameIs: string + descriptionEn: string + descriptionIs: string + durationInYears: number + externalId: string + id: string + iscedCode: string + modeOfDelivery: Array + nameEn: string + nameIs: string + schoolAnswerDate?: string + specializationExternalId?: string + specializationNameEn?: string + specializationNameIs?: string + startingSemesterSeason: string + startingSemesterYear: number + studentAnswerDate?: string + universityContentfulKey: string + universityId: string +} + +type ModeOfDelivery = { + modeOfDelivery: string +} From e0f0050ac9c3757323ea30df39b82e3b7a471bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3r=C3=B0ur=20H?= Date: Thu, 5 Sep 2024 10:06:05 +0000 Subject: [PATCH 08/34] fix(regulations-admin): Increase body limit on regulations backend (#15884) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/services/regulations-admin-backend/src/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/services/regulations-admin-backend/src/main.ts b/apps/services/regulations-admin-backend/src/main.ts index 8d3c254eaaff..b0f1f43fea55 100644 --- a/apps/services/regulations-admin-backend/src/main.ts +++ b/apps/services/regulations-admin-backend/src/main.ts @@ -7,4 +7,5 @@ bootstrap({ appModule: AppModule, name: 'regulations-admin-backend', openApi, + jsonBodyLimit: '300kb', }) From 0815408903c87da64560a0bc7fe883ecc2c557e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Thu, 5 Sep 2024 13:43:08 +0000 Subject: [PATCH 09/34] chore(j-s): Prison users access to appeal case files (#15844) * Create CaseFileInterceptor * Filter case files in interceptor * Only send appeal ruling for completed appeals * Refactor * Remove unused code * Refactor * Allow defence users to see all files * Refactor * Fix tests --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/app/modules/case/case.controller.ts | 4 +- .../guards/limitedAccessCaseExists.guard.ts | 2 +- .../case/interceptors/caseFile.interceptor.ts | 53 +++++++++++++++++++ ...=> completedAppealAccessed.interceptor.ts} | 2 +- .../case/limitedAccessCase.controller.ts | 5 +- .../modules/file/guards/caseFileCategory.ts | 2 + .../guards/limitedAccessViewCaseFile.guard.ts | 19 ++++--- .../limitedAccessViewCaseFileGuard.spec.ts | 8 --- libs/judicial-system/types/src/index.ts | 2 + libs/judicial-system/types/src/lib/file.ts | 20 +++---- libs/judicial-system/types/src/lib/user.ts | 14 +++++ 11 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 apps/judicial-system/backend/src/app/modules/case/interceptors/caseFile.interceptor.ts rename apps/judicial-system/backend/src/app/modules/case/interceptors/{case.interceptor.ts => completedAppealAccessed.interceptor.ts} (94%) diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts index cca384ac525a..4978748add3a 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts @@ -99,8 +99,8 @@ import { prosecutorUpdateRule, publicProsecutorStaffUpdateRule, } from './guards/rolesRules' -import { CaseInterceptor } from './interceptors/case.interceptor' import { CaseListInterceptor } from './interceptors/caseList.interceptor' +import { CompletedAppealAccessedInterceptor } from './interceptors/completedAppealAccessed.interceptor' import { Case } from './models/case.model' import { SignatureConfirmationResponse } from './models/signatureConfirmation.response' import { transitionCase } from './state/case.state' @@ -465,7 +465,7 @@ export class CaseController { ) @Get('case/:caseId') @ApiOkResponse({ type: Case, description: 'Gets an existing case' }) - @UseInterceptors(CaseInterceptor) + @UseInterceptors(CompletedAppealAccessedInterceptor) getById(@Param('caseId') caseId: string, @CurrentCase() theCase: Case): Case { this.logger.debug(`Getting case ${caseId} by id`) diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/limitedAccessCaseExists.guard.ts b/apps/judicial-system/backend/src/app/modules/case/guards/limitedAccessCaseExists.guard.ts index 460480edf5f8..f92e78361b74 100644 --- a/apps/judicial-system/backend/src/app/modules/case/guards/limitedAccessCaseExists.guard.ts +++ b/apps/judicial-system/backend/src/app/modules/case/guards/limitedAccessCaseExists.guard.ts @@ -14,7 +14,7 @@ export class LimitedAccessCaseExistsGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest() - const caseId = request.params.caseId + const caseId: string = request.params.caseId if (!caseId) { throw new BadRequestException('Missing case id') diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseFile.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseFile.interceptor.ts new file mode 100644 index 000000000000..d7d74fb30a5c --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseFile.interceptor.ts @@ -0,0 +1,53 @@ +import { Observable } from 'rxjs' +import { map } from 'rxjs/operators' + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common' + +import { + CaseAppealState, + CaseFileCategory, + isDefenceUser, + isPrisonStaffUser, + isPrisonSystemUser, + User, +} from '@island.is/judicial-system/types' + +import { Case } from '../models/case.model' + +@Injectable() +export class CaseFileInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest() + const user: User = request.user + + return next.handle().pipe( + map((data: Case) => { + if (isDefenceUser(user)) { + return data + } + + if ( + isPrisonStaffUser(user) || + data.appealState !== CaseAppealState.COMPLETED + ) { + data.caseFiles?.splice(0, data.caseFiles.length) + } else if (isPrisonSystemUser(user)) { + data.caseFiles?.splice( + 0, + data.caseFiles.length, + ...data.caseFiles.filter( + (cf) => cf.category === CaseFileCategory.APPEAL_RULING, + ), + ) + } + + return data + }), + ) + } +} diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/completedAppealAccessed.interceptor.ts similarity index 94% rename from apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts rename to apps/judicial-system/backend/src/app/modules/case/interceptors/completedAppealAccessed.interceptor.ts index 6beab2bc3915..5ff8d84bff3f 100644 --- a/apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/completedAppealAccessed.interceptor.ts @@ -20,7 +20,7 @@ import { EventLogService } from '../../event-log' import { Case } from '../models/case.model' @Injectable() -export class CaseInterceptor implements NestInterceptor { +export class CompletedAppealAccessedInterceptor implements NestInterceptor { constructor(private readonly eventLogService: EventLogService) {} intercept(context: ExecutionContext, next: CallHandler): Observable { diff --git a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts index 7e17c55bab85..809dcfc390cc 100644 --- a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts @@ -53,7 +53,8 @@ import { CaseWriteGuard } from './guards/caseWrite.guard' import { LimitedAccessCaseExistsGuard } from './guards/limitedAccessCaseExists.guard' import { RequestSharedWithDefenderGuard } from './guards/requestSharedWithDefender.guard' import { defenderTransitionRule, defenderUpdateRule } from './guards/rolesRules' -import { CaseInterceptor } from './interceptors/case.interceptor' +import { CaseFileInterceptor } from './interceptors/caseFile.interceptor' +import { CompletedAppealAccessedInterceptor } from './interceptors/completedAppealAccessed.interceptor' import { Case } from './models/case.model' import { transitionCase } from './state/case.state' import { @@ -85,7 +86,7 @@ export class LimitedAccessCaseController { type: Case, description: 'Gets a limited set of properties of an existing case', }) - @UseInterceptors(CaseInterceptor) + @UseInterceptors(CompletedAppealAccessedInterceptor, CaseFileInterceptor) async getById( @Param('caseId') caseId: string, @CurrentCase() theCase: Case, diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts b/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts index 5455ad7976b0..2d8d88353f35 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts @@ -22,3 +22,5 @@ export const defenderCaseFileCategoriesForIndictmentCases = [ CaseFileCategory.PROSECUTOR_CASE_FILE, CaseFileCategory.DEFENDANT_CASE_FILE, ] + +export const prisonAdminCaseFileCategories = [CaseFileCategory.APPEAL_RULING] diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessViewCaseFile.guard.ts b/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessViewCaseFile.guard.ts index 3526675d6902..a8c2f8295ea7 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessViewCaseFile.guard.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessViewCaseFile.guard.ts @@ -7,11 +7,10 @@ import { } from '@nestjs/common' import { - CaseFileCategory, isCompletedCase, isDefenceUser, isIndictmentCase, - isPrisonSystemUser, + isPrisonAdminUser, isRequestCase, User, } from '@island.is/judicial-system/types' @@ -21,6 +20,7 @@ import { CaseFile } from '../models/file.model' import { defenderCaseFileCategoriesForIndictmentCases, defenderCaseFileCategoriesForRestrictionAndInvestigationCases, + prisonAdminCaseFileCategories, } from './caseFileCategory' @Injectable() @@ -65,14 +65,13 @@ export class LimitedAccessViewCaseFileGuard implements CanActivate { } } - if (isPrisonSystemUser(user)) { - if ( - isCompletedCase(theCase.state) && - caseFile.category && - caseFile.category === CaseFileCategory.APPEAL_RULING - ) { - return true - } + if ( + caseFile.category && + isCompletedCase(theCase.state) && + isPrisonAdminUser(user) && + prisonAdminCaseFileCategories.includes(caseFile.category) + ) { + return true } throw new ForbiddenException(`Forbidden for ${user.role}`) diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessViewCaseFileGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessViewCaseFileGuard.spec.ts index dd31ac1d7816..e4e7672dc2d6 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessViewCaseFileGuard.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessViewCaseFileGuard.spec.ts @@ -229,27 +229,19 @@ describe('Limited Access View Case File Guard', () => { describe.each(allowedCaseFileCategories)( 'prison system users can view %s', (category) => { - let thenPrison: Then let thenPrisonAdmin: Then beforeEach(() => { - mockRequest.mockImplementationOnce(() => ({ - user: prisonUser, - case: { type, state }, - caseFile: { category }, - })) mockRequest.mockImplementationOnce(() => ({ user: prisonAdminUser, case: { type, state }, caseFile: { category }, })) - thenPrison = givenWhenThen() thenPrisonAdmin = givenWhenThen() }) it('should activate', () => { - expect(thenPrison.result).toBe(true) expect(thenPrisonAdmin.result).toBe(true) }) }, diff --git a/libs/judicial-system/types/src/index.ts b/libs/judicial-system/types/src/index.ts index 27dcae8b586d..cf873e6f07cc 100644 --- a/libs/judicial-system/types/src/index.ts +++ b/libs/judicial-system/types/src/index.ts @@ -28,10 +28,12 @@ export { isCourtOfAppealsUser, prisonSystemRoles, isPrisonSystemUser, + isPrisonStaffUser, defenceRoles, isDefenceUser, isAdminUser, isCoreUser, + isPrisonAdminUser, isPublicProsecutor, } from './lib/user' export type { User } from './lib/user' diff --git a/libs/judicial-system/types/src/lib/file.ts b/libs/judicial-system/types/src/lib/file.ts index 44f65118a9a1..ae687d066896 100644 --- a/libs/judicial-system/types/src/lib/file.ts +++ b/libs/judicial-system/types/src/lib/file.ts @@ -15,16 +15,16 @@ export enum CaseFileCategory { CASE_FILE_RECORD = 'CASE_FILE_RECORD', PROSECUTOR_CASE_FILE = 'PROSECUTOR_CASE_FILE', DEFENDANT_CASE_FILE = 'DEFENDANT_CASE_FILE', - PROSECUTOR_APPEAL_BRIEF = 'PROSECUTOR_APPEAL_BRIEF', - DEFENDANT_APPEAL_BRIEF = 'DEFENDANT_APPEAL_BRIEF', - PROSECUTOR_APPEAL_BRIEF_CASE_FILE = 'PROSECUTOR_APPEAL_BRIEF_CASE_FILE', - DEFENDANT_APPEAL_BRIEF_CASE_FILE = 'DEFENDANT_APPEAL_BRIEF_CASE_FILE', - PROSECUTOR_APPEAL_STATEMENT = 'PROSECUTOR_APPEAL_STATEMENT', - DEFENDANT_APPEAL_STATEMENT = 'DEFENDANT_APPEAL_STATEMENT', - PROSECUTOR_APPEAL_STATEMENT_CASE_FILE = 'PROSECUTOR_APPEAL_STATEMENT_CASE_FILE', - DEFENDANT_APPEAL_STATEMENT_CASE_FILE = 'DEFENDANT_APPEAL_STATEMENT_CASE_FILE', - PROSECUTOR_APPEAL_CASE_FILE = 'PROSECUTOR_APPEAL_CASE_FILE', - DEFENDANT_APPEAL_CASE_FILE = 'DEFENDANT_APPEAL_CASE_FILE', + PROSECUTOR_APPEAL_BRIEF = 'PROSECUTOR_APPEAL_BRIEF', // Sækjandi: Kæruskjal til Landsréttar + DEFENDANT_APPEAL_BRIEF = 'DEFENDANT_APPEAL_BRIEF', // Verjandi: Kæruskjal til Landsréttar + PROSECUTOR_APPEAL_BRIEF_CASE_FILE = 'PROSECUTOR_APPEAL_BRIEF_CASE_FILE', // Sækjandi: Fylgigögn kæruskjals til Landsréttar + DEFENDANT_APPEAL_BRIEF_CASE_FILE = 'DEFENDANT_APPEAL_BRIEF_CASE_FILE', // Verjandi: Fylgigögn kæruskjals til Landsréttar + PROSECUTOR_APPEAL_STATEMENT = 'PROSECUTOR_APPEAL_STATEMENT', // Sækjandi: Greinargerð + DEFENDANT_APPEAL_STATEMENT = 'DEFENDANT_APPEAL_STATEMENT', // Verjandi: Greinargerð + PROSECUTOR_APPEAL_STATEMENT_CASE_FILE = 'PROSECUTOR_APPEAL_STATEMENT_CASE_FILE', // Sækjandi: Fylgigögn greinargerðar + DEFENDANT_APPEAL_STATEMENT_CASE_FILE = 'DEFENDANT_APPEAL_STATEMENT_CASE_FILE', // Verjandi: Fylgigögn greinargerðar + PROSECUTOR_APPEAL_CASE_FILE = 'PROSECUTOR_APPEAL_CASE_FILE', // Sækjandi: Viðbótargögn við kæru til Landsréttar + DEFENDANT_APPEAL_CASE_FILE = 'DEFENDANT_APPEAL_CASE_FILE', // Verjandi: Viðbótargögn við kæru til Landsréttar APPEAL_COURT_RECORD = 'APPEAL_COURT_RECORD', APPEAL_RULING = 'APPEAL_RULING', } diff --git a/libs/judicial-system/types/src/lib/user.ts b/libs/judicial-system/types/src/lib/user.ts index ba7a5a6ec417..8c0dcb478e20 100644 --- a/libs/judicial-system/types/src/lib/user.ts +++ b/libs/judicial-system/types/src/lib/user.ts @@ -114,6 +114,20 @@ export const isPrisonSystemUser = (user?: InstitutionUser): boolean => { ) } +export const isPrisonAdminUser = (user: InstitutionUser): boolean => + Boolean( + user.role && + prisonSystemRoles.includes(user.role) && + user.institution?.type === InstitutionType.PRISON_ADMIN, + ) + +export const isPrisonStaffUser = (user: InstitutionUser): boolean => + Boolean( + user.role && + prisonSystemRoles.includes(user.role) && + user.institution?.type === InstitutionType.PRISON, + ) + export const defenceRoles: string[] = [UserRole.DEFENDER] export const isDefenceUser = (user?: InstitutionUser): boolean => { From 5c1b2c20891923d79651d04529c65506942ef518 Mon Sep 17 00:00:00 2001 From: albinagu <47886428+albinagu@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:07:06 +0000 Subject: [PATCH 10/34] fix(inheritance-report): make sure total assets get into answers (#15903) * fix(inheritance-report): mappers fix * fix set total assets --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../CalculateTotalAssets/index.tsx | 37 ------------------- .../SetTotalAssets/index.tsx | 21 +++++++++++ .../inheritance-report/src/fields/index.ts | 2 +- .../src/forms/sections/assets.ts | 5 +++ 4 files changed, 27 insertions(+), 38 deletions(-) delete mode 100644 libs/application/templates/inheritance-report/src/fields/CalculationsOfTotal/CalculateTotalAssets/index.tsx create mode 100644 libs/application/templates/inheritance-report/src/fields/CalculationsOfTotal/SetTotalAssets/index.tsx diff --git a/libs/application/templates/inheritance-report/src/fields/CalculationsOfTotal/CalculateTotalAssets/index.tsx b/libs/application/templates/inheritance-report/src/fields/CalculationsOfTotal/CalculateTotalAssets/index.tsx deleted file mode 100644 index 2ac38968b7fc..000000000000 --- a/libs/application/templates/inheritance-report/src/fields/CalculationsOfTotal/CalculateTotalAssets/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { FieldBaseProps } from '@island.is/application/types' -import { formatCurrency } from '@island.is/application/ui-components' -import { Box, Text } from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { FC, useEffect, useState } from 'react' -import { useFormContext } from 'react-hook-form' -import { m } from '../../../lib/messages' -import { calculateTotalAssets } from '../../../lib/utils/calculateTotalAssets' - -export const CalculateTotalAssets: FC< - React.PropsWithChildren -> = ({ application }) => { - const { answers } = application - const { formatMessage } = useLocale() - const { setValue } = useFormContext() - - const acc = calculateTotalAssets(answers) - - const [total] = useState(acc) - - useEffect(() => { - setValue('assets.assetsTotal', total) - }, [total, setValue]) - - return ( - - {formatMessage(m.overviewTotal)} - {formatCurrency(String(total))} - - ) -} - -export default CalculateTotalAssets diff --git a/libs/application/templates/inheritance-report/src/fields/CalculationsOfTotal/SetTotalAssets/index.tsx b/libs/application/templates/inheritance-report/src/fields/CalculationsOfTotal/SetTotalAssets/index.tsx new file mode 100644 index 000000000000..6d77a6b27c85 --- /dev/null +++ b/libs/application/templates/inheritance-report/src/fields/CalculationsOfTotal/SetTotalAssets/index.tsx @@ -0,0 +1,21 @@ +import { FieldBaseProps } from '@island.is/application/types' +import { FC, useEffect } from 'react' +import { useFormContext } from 'react-hook-form' +import { calculateTotalAssets } from '../../../lib/utils/calculateTotalAssets' + +export const SetTotalAssets: FC> = ({ + application, +}) => { + const { answers } = application + const { setValue } = useFormContext() + + const total = calculateTotalAssets(answers) + + useEffect(() => { + setValue('assets.assetsTotal', total) + }, [total, setValue]) + + return null +} + +export default SetTotalAssets diff --git a/libs/application/templates/inheritance-report/src/fields/index.ts b/libs/application/templates/inheritance-report/src/fields/index.ts index 29c879ebe87e..61b6498cd211 100644 --- a/libs/application/templates/inheritance-report/src/fields/index.ts +++ b/libs/application/templates/inheritance-report/src/fields/index.ts @@ -7,7 +7,7 @@ export { OverviewHeirs } from './Overview/OverviewHeirs' export { FuneralCost } from './FuneralCost' export { OtherAssetsRepeater } from './OtherAssetsRepeater' export { DeceasedShareField } from './DeceasedShareField' -export { CalculateTotalAssets } from './CalculationsOfTotal/CalculateTotalAssets' +export { SetTotalAssets } from './CalculationsOfTotal/SetTotalAssets' export { CalculateTotalDebts } from './CalculationsOfTotal/CalculateTotalDebts' export { CalculateTotalBusiness } from './CalculationsOfTotal/CalculateTotalBusiness' export { CalculateFuneralCost } from './CalculationsOfTotal/CalculateFuneralCost' diff --git a/libs/application/templates/inheritance-report/src/forms/sections/assets.ts b/libs/application/templates/inheritance-report/src/forms/sections/assets.ts index 623d6042f2db..52e400fd7071 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/assets.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/assets.ts @@ -691,6 +691,11 @@ export const assets = buildSection({ doesNotRequireAnswer: true, component: 'OverviewAssets', }), + buildCustomField({ + title: '', + id: 'assets.assetsTotal', + component: 'SetTotalAssets', + }), buildDescriptionField({ id: 'space', title: '', From bd2eb0877679034454749b67f88c7a67f2907f47 Mon Sep 17 00:00:00 2001 From: albinagu <47886428+albinagu@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:35:31 +0000 Subject: [PATCH 11/34] fix(inheritance-report): calculate realEstate withOUT share (#15905) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../templates/inheritance-report/src/forms/sections/assets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/application/templates/inheritance-report/src/forms/sections/assets.ts b/libs/application/templates/inheritance-report/src/forms/sections/assets.ts index 52e400fd7071..7946cf6e67aa 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/assets.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/assets.ts @@ -99,7 +99,7 @@ export const assets = buildSection({ }, ], assetKey: 'assets', - calcWithShareValue: true, + calcWithShareValue: false, repeaterButtonText: m.addRealEstate, sumField: 'propertyValuation', }, From a4c7324d749ac049c9b7711d72612b85a7712aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:54:27 +0000 Subject: [PATCH 12/34] fix(island-ui): Filter Dialog - Scroll doesn't work on iOS (#15896) * Fix mobile scroll * Keep track of scroll position * Move hook into separate file * useRef instead of global variable --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/island-ui/core/src/lib/Filter/Filter.tsx | 10 +++-- .../src/lib/Filter/usePreventBodyScroll.ts | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 libs/island-ui/core/src/lib/Filter/usePreventBodyScroll.ts diff --git a/libs/island-ui/core/src/lib/Filter/Filter.tsx b/libs/island-ui/core/src/lib/Filter/Filter.tsx index ac7db7fba7bd..692b369ebabd 100644 --- a/libs/island-ui/core/src/lib/Filter/Filter.tsx +++ b/libs/island-ui/core/src/lib/Filter/Filter.tsx @@ -6,6 +6,8 @@ import { Button } from '../Button/Button' import { Inline } from '../Inline/Inline' import { Stack } from '../Stack/Stack' import { Text } from '../Text/Text' +import { usePreventBodyScroll } from './usePreventBodyScroll' + import * as styles from './Filter.css' export interface FilterProps { @@ -52,7 +54,7 @@ export interface FilterProps { /** * Datatype to use for Filter context. * Provides the Filter's childs access to shared values, - * like the `isDialog` state with out bloating the childs props. + * like the `isDialog` state without bloating the childs props. */ interface FilterContextValue { variant?: FilterProps['variant'] @@ -78,7 +80,7 @@ export const Filter: FC> = ({ children, popoverFlip = true, }) => { - const dialog = useDialogState() + const dialog = useDialogState({ modal: true }) const popover = usePopoverState({ placement: 'bottom-start', unstable_flip: popoverFlip, @@ -87,6 +89,8 @@ export const Filter: FC> = ({ const hasFilterInput = !!filterInput + usePreventBodyScroll(dialog.visible && variant === 'dialog') + return ( {variant === 'popover' && ( @@ -171,7 +175,7 @@ export const Filter: FC> = ({ /> - + { + const initialBodyPosition = useRef(null) + const initialScrollPosition = useRef(null) + + useEffect(() => { + const isBrowser = typeof window !== 'undefined' + if (!isBrowser || !preventBodyScroll) { + return + } + + if (initialBodyPosition.current === null) { + initialBodyPosition.current = + window.document.body.style.position || 'static' + } + if (initialScrollPosition.current === null) { + initialScrollPosition.current = window.scrollY + } + + // Prevent scrolling on the body element + window.document.body.style.position = 'fixed' + + return () => { + if (initialBodyPosition.current !== null) { + window.document.body.style.position = initialBodyPosition.current + initialBodyPosition.current = null + } + if (initialScrollPosition.current !== null) { + // When setting the body position to fixed, the scroll position resets to 0 + // Here we are restoring the scroll position + window.scrollTo(0, initialScrollPosition.current) + initialScrollPosition.current = null + } + } + }, [preventBodyScroll]) +} From aa49990d7b5412e70776142ce177f874e2dac6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Fri, 6 Sep 2024 09:32:40 +0000 Subject: [PATCH 13/34] fix(native-app): update inbox filter headers (#15890) * fix: headers of accordions should be bigger * fix: correct weight for unchekcked filters in inbox --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/native/app/src/ui/lib/accordion/accordion-item.tsx | 2 +- apps/native/app/src/ui/lib/checkbox/checkbox.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/native/app/src/ui/lib/accordion/accordion-item.tsx b/apps/native/app/src/ui/lib/accordion/accordion-item.tsx index a2792cdaa45f..4305671910e2 100644 --- a/apps/native/app/src/ui/lib/accordion/accordion-item.tsx +++ b/apps/native/app/src/ui/lib/accordion/accordion-item.tsx @@ -98,7 +98,7 @@ export function AccordionItem({ > {icon && {icon}} - {title} + {title} { return ( - + {label} From fd4618f41ac26a471d4166b8a23904b3e983ea84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Fri, 6 Sep 2024 09:37:01 +0000 Subject: [PATCH 14/34] feat(native-app): use unseen count for notifications as badge for app (#15893) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/native/app/src/screens/inbox/inbox.tsx | 2 -- apps/native/app/src/stores/notifications-store.ts | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/native/app/src/screens/inbox/inbox.tsx b/apps/native/app/src/screens/inbox/inbox.tsx index 6b97574ba2cc..6356e3f4c626 100644 --- a/apps/native/app/src/screens/inbox/inbox.tsx +++ b/apps/native/app/src/screens/inbox/inbox.tsx @@ -8,7 +8,6 @@ import { TopLine, InboxCard, } from '@ui' -import { setBadgeCountAsync } from 'expo-notifications' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -371,7 +370,6 @@ export const InboxScreen: NavigationFunctionComponent<{ badgeColor: theme.color.red400, }, }) - setBadgeCountAsync(unreadCount) }, [intl, theme, unreadCount]) const keyExtractor = useCallback((item: ListItem) => { diff --git a/apps/native/app/src/stores/notifications-store.ts b/apps/native/app/src/stores/notifications-store.ts index 768a537b1ab6..37c673adca2e 100644 --- a/apps/native/app/src/stores/notifications-store.ts +++ b/apps/native/app/src/stores/notifications-store.ts @@ -18,6 +18,7 @@ import { } from '../graphql/types/schema' import { ComponentRegistry } from '../utils/component-registry' import { getRightButtons } from '../utils/get-main-root' +import { setBadgeCountAsync } from 'expo-notifications' export interface Notification { id: string @@ -114,6 +115,7 @@ export const notificationsStore = create( }, updateNavigationUnseenCount(unseenCount: number) { set({ unseenCount }) + setBadgeCountAsync(unseenCount) Navigation.mergeOptions(ComponentRegistry.HomeScreen, { topBar: { From d4031f083977e3cc3bdc1b0079f252ef38b5a90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Fri, 6 Sep 2024 11:33:26 +0000 Subject: [PATCH 15/34] feat(web): Parental leave calculator (#15819) * Connected component initialized, UI halfway done * Add more translations * Add results screen skeleton * Add custom fields for other than parental leave statuses * Translation changes * Prevent user from clicking calculate button until form is filled * Sort union options * Add fallback error widget * Verify config using zod * Add calculations * Add parenthesis to calculation * Finalize parental leave calculation * Use correct translations for other than parental leave status * Fix bullet list rendering * Calculation logic added for other statuses * Change styles * Add spacing --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../MarkdownText/MarkdownText.tsx | 18 +- .../ParentalLeaveCalculator.css.ts | 7 + .../ParentalLeaveCalculator.tsx | 1089 +++++++++++++++++ .../translations.strings.ts | 496 ++++++++ apps/web/components/real.ts | 1 + apps/web/utils/currency.ts | 4 +- apps/web/utils/richText.tsx | 4 + 7 files changed, 1615 insertions(+), 4 deletions(-) create mode 100644 apps/web/components/connected/ParentalLeaveCalculator/ParentalLeaveCalculator.css.ts create mode 100644 apps/web/components/connected/ParentalLeaveCalculator/ParentalLeaveCalculator.tsx create mode 100644 apps/web/components/connected/ParentalLeaveCalculator/translations.strings.ts diff --git a/apps/web/components/Organization/MarkdownText/MarkdownText.tsx b/apps/web/components/Organization/MarkdownText/MarkdownText.tsx index d2266de2df6c..a28544a9f807 100644 --- a/apps/web/components/Organization/MarkdownText/MarkdownText.tsx +++ b/apps/web/components/Organization/MarkdownText/MarkdownText.tsx @@ -1,17 +1,29 @@ +import React from 'react' import Markdown from 'markdown-to-jsx' + import { Bullet, BulletList, Text, TextProps } from '@island.is/island-ui/core' -import React from 'react' + import * as styles from './MarkdownText.css' interface MarkdownTextProps { children: string color?: TextProps['color'] variant?: TextProps['variant'] + replaceNewLinesWithBreaks?: boolean } export const MarkdownText: React.FC< React.PropsWithChildren -> = ({ children, color = null, variant = 'default' }) => { +> = ({ + children, + color = null, + variant = 'default', + replaceNewLinesWithBreaks = true, +}) => { + const processedChildren = replaceNewLinesWithBreaks + ? (children as string).replace(/\n/gi, '
') + : children + return (
- {(children as string).replace(/\n/gi, '
')} + {processedChildren}
) diff --git a/apps/web/components/connected/ParentalLeaveCalculator/ParentalLeaveCalculator.css.ts b/apps/web/components/connected/ParentalLeaveCalculator/ParentalLeaveCalculator.css.ts new file mode 100644 index 000000000000..e7006fe6fb85 --- /dev/null +++ b/apps/web/components/connected/ParentalLeaveCalculator/ParentalLeaveCalculator.css.ts @@ -0,0 +1,7 @@ +import { style } from '@vanilla-extract/css' + +import { theme } from '@island.is/island-ui/theme' + +export const resultBorder = style({ + border: `1px dashed ${theme.color.blue300}`, +}) diff --git a/apps/web/components/connected/ParentalLeaveCalculator/ParentalLeaveCalculator.tsx b/apps/web/components/connected/ParentalLeaveCalculator/ParentalLeaveCalculator.tsx new file mode 100644 index 000000000000..9132ba9f85f6 --- /dev/null +++ b/apps/web/components/connected/ParentalLeaveCalculator/ParentalLeaveCalculator.tsx @@ -0,0 +1,1089 @@ +import { type PropsWithChildren, useMemo, useRef, useState } from 'react' +import { useIntl } from 'react-intl' +import NumberFormat from 'react-number-format' +import { + parseAsInteger, + parseAsString, + parseAsStringEnum, + useQueryState, +} from 'next-usequerystate' +import { z } from 'zod' + +import { + AlertMessage, + Box, + Button, + GridColumn, + GridRow, + Inline, + Input, + type Option, + RadioButton, + Select, + Stack, + Table, + Text, + Tooltip, +} from '@island.is/island-ui/core' +import { sortAlpha } from '@island.is/shared/utils' +import type { ConnectedComponent } from '@island.is/web/graphql/schema' +import { formatCurrency as formatCurrencyUtil } from '@island.is/web/utils/currency' + +import { MarkdownText } from '../../Organization' +import { translations as t } from './translations.strings' +import * as styles from './ParentalLeaveCalculator.css' + +interface FieldProps { + heading: string + headingTooltip?: string + description?: string + tooltip?: string +} + +const Field = ({ + heading, + headingTooltip, + description, + tooltip, + children, +}: PropsWithChildren) => { + return ( + + + {heading} + {headingTooltip && } + + {description && ( + + {description} + {tooltip && } + + )} + {children} + + ) +} + +enum Status { + PARENTAL_LEAVE = 'parentalLeave', + STUDENT = 'student', + OUTSIDE_WORKFORCE = 'outsideWorkForce', +} + +enum WorkPercentage { + OPTION_1 = 'option1', + OPTION_2 = 'option2', +} + +enum ParentalLeavePeriod { + MONTH = 'month', + THREE_WEEKS = 'threeWeeks', + TWO_WEEKS = 'twoWeeks', +} + +enum Screen { + FORM = 'form', + RESULTS = 'results', +} + +enum LegalDomicileInIceland { + YES = 'y', + NO = 'n', +} + +interface ParentalLeaveCalculatorProps { + slice: ConnectedComponent +} + +interface ScreenProps extends ParentalLeaveCalculatorProps { + changeScreen: () => void +} + +const FormScreen = ({ slice, changeScreen }: ScreenProps) => { + const { formatMessage } = useIntl() + + const statusOptions = useMemo[]>(() => { + return [ + { + label: formatMessage(t.status.parentalLeaveOption), + value: Status.PARENTAL_LEAVE, + }, + { + label: formatMessage(t.status.studentOption), + value: Status.STUDENT, + }, + { + label: formatMessage(t.status.outsideWorkforceOption), + value: Status.OUTSIDE_WORKFORCE, + }, + ] + }, [formatMessage]) + + const yearOptions = useMemo[]>(() => { + const keys = Object.keys(slice.configJson?.yearConfig || {}).map(Number) + keys.sort() + return keys.map((key) => ({ + label: String(key), + value: key, + })) + }, [slice.configJson?.yearConfig]) + + const additionalPensionFundingOptions = useMemo< + Option[] + >(() => { + const options: number[] = slice.configJson + ?.additionalPensionFundingOptions ?? [1, 2, 3, 4] + + return [ + { value: null, label: formatMessage(t.additionalPensionFunding.none) }, + ...options.map((option) => ({ + label: `${option} ${formatMessage( + t.additionalPensionFunding.optionSuffix, + )}`, + value: option, + })), + ] + }, [formatMessage, slice.configJson?.additionalPensionFundingOptions]) + + const unionOptions = useMemo[]>(() => { + const options: { label: string; percentage: number }[] = slice.configJson + ?.unionOptions + ? [...slice.configJson.unionOptions] + : [] + + options.sort(sortAlpha('label')) + + return [ + { + value: null, + label: formatMessage(t.union.none), + }, + ...options.map((option) => ({ + label: option.label, + value: option.label, + })), + ] + }, [formatMessage, slice.configJson?.unionOptions]) + + const parentalLeavePeriodOptions = useMemo[]>(() => { + return [ + { + label: formatMessage(t.parentalLeavePeriod.monthOption), + value: ParentalLeavePeriod.MONTH, + }, + { + label: formatMessage(t.parentalLeavePeriod.threeWeeksOption), + value: ParentalLeavePeriod.THREE_WEEKS, + }, + { + label: formatMessage(t.parentalLeavePeriod.twoWeeksOption), + value: ParentalLeavePeriod.TWO_WEEKS, + }, + ] + }, [formatMessage]) + + const [status, setStatus] = useQueryState( + 'status', + parseAsStringEnum(Object.values(Status)).withDefault(Status.PARENTAL_LEAVE), + ) + const [birthyear, setBirthyear] = useQueryState('birthyear', parseAsInteger) + const [workPercentage, setWorkPercentage] = useQueryState( + 'workPercentage', + parseAsStringEnum(Object.values(WorkPercentage)), + ) + const [income, setIncome] = useQueryState('income', parseAsInteger) + const [ + additionalPensionFundingPercentage, + setAdditionalPensionFundingPercentage, + ] = useQueryState('additionalPensionFunding', parseAsInteger) + const [union, setUnion] = useQueryState('union', parseAsString) + const [personalDiscount, setPersonalDiscount] = useQueryState( + 'personalDiscount', + parseAsInteger.withDefault(100), + ) + const [parentalLeavePeriod, setParentalLeavePeriod] = useQueryState( + 'parentalLeavePeriod', + parseAsStringEnum(Object.values(ParentalLeavePeriod)), + ) + const [parentalLeaveRatio, setParentalLeaveRatio] = useQueryState( + 'parentalLeaveRatio', + parseAsInteger.withDefault(100), + ) + const [legalDomicileInIceland, setLegalDomicileInIceland] = useQueryState( + 'legalDomicileInIceland', + parseAsStringEnum(Object.values(LegalDomicileInIceland)), + ) + + const canCalculate = () => { + let value = + Object.values(Status).includes(status) && + yearOptions.some((year) => year.value === birthyear) + + if (status === Status.OUTSIDE_WORKFORCE) { + value = value && legalDomicileInIceland === LegalDomicileInIceland.YES + } + + if (status === Status.PARENTAL_LEAVE) { + value = + value && + typeof income === 'number' && + income > 0 && + !!workPercentage && + Object.values(WorkPercentage).includes(workPercentage) && + parentalLeavePeriodOptions.some( + (option) => option.value === parentalLeavePeriod, + ) + } + + return value + } + + return ( + + + + { + setBirthyear(option?.value ?? null) + }} + value={yearOptions.find((option) => option.value === birthyear)} + label={formatMessage(t.childBirthYear.label)} + options={yearOptions} + /> + + + {status === Status.PARENTAL_LEAVE && ( + + + + { + setWorkPercentage(WorkPercentage.OPTION_1) + }} + checked={workPercentage === WorkPercentage.OPTION_1} + value={WorkPercentage.OPTION_1} + backgroundColor="white" + large={true} + label={formatMessage(t.workPercentage.option1)} + /> + + + { + setWorkPercentage(WorkPercentage.OPTION_2) + }} + checked={workPercentage === WorkPercentage.OPTION_2} + value={WorkPercentage.OPTION_2} + backgroundColor="white" + large={true} + label={formatMessage(t.workPercentage.option2)} + /> + + + + )} + + {status === Status.PARENTAL_LEAVE && ( + + { + setIncome(Number(value)) + }} + label={formatMessage(t.income.label)} + tooltip={formatMessage(t.income.tooltip)} + value={String(income || '')} + customInput={Input} + name="income" + id="income" + type="text" + inputMode="numeric" + thousandSeparator="." + decimalSeparator="," + suffix={formatMessage(t.income.inputSuffix)} + placeholder={formatMessage(t.income.inputPlaceholder)} + maxLength={ + formatMessage(t.income.inputSuffix).length + + (slice.configJson?.incomeInputMaxLength ?? 12) + } + /> + + )} + + {status === Status.PARENTAL_LEAVE && ( + + { + setUnion(option?.value ?? null) + }} + value={unionOptions.find((option) => option.value === union)} + label={formatMessage(t.union.label)} + options={unionOptions} + /> + + )} + + + { + setPersonalDiscount(Number(value)) + }} + label={formatMessage(t.personalDiscount.label)} + value={String(personalDiscount || '')} + customInput={Input} + name="personalDiscount" + id="personalDiscount" + type="text" + inputMode="numeric" + suffix={formatMessage(t.personalDiscount.suffix)} + placeholder={formatMessage(t.personalDiscount.placeholder)} + format={(value) => { + const maxPersonalDiscount = + slice.configJson?.maxPersonalDiscount ?? 100 + if (Number(value) > maxPersonalDiscount) { + value = String(maxPersonalDiscount) + } + return `${value}${formatMessage(t.personalDiscount.suffix)}` + }} + /> + + + {status === Status.PARENTAL_LEAVE && ( + + { - setDoctorId(val?.value) - }} - /> - - ) : null} - - - - -
- - - - - - - ) -} diff --git a/libs/service-portal/health/src/components/RegisterModal/index.ts b/libs/service-portal/health/src/components/RegisterModal/index.ts deleted file mode 100644 index 11dab747046f..000000000000 --- a/libs/service-portal/health/src/components/RegisterModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { RegisterModal } from './RegisterModal' diff --git a/libs/service-portal/health/src/screens/DentistRegistration/DentistRegistration.tsx b/libs/service-portal/health/src/screens/DentistRegistration/DentistRegistration.tsx index 8fc188331fd3..bcb7e79f7ed5 100644 --- a/libs/service-portal/health/src/screens/DentistRegistration/DentistRegistration.tsx +++ b/libs/service-portal/health/src/screens/DentistRegistration/DentistRegistration.tsx @@ -13,7 +13,7 @@ import { useGetPaginatedDentistsQuery, useRegisterDentistMutation, } from './DentistRegistration.generated' -import { m } from '@island.is/service-portal/core' +import { Modal, m } from '@island.is/service-portal/core' import { IntroHeader } from '@island.is/portals/core' import { useLocale, useNamespaces } from '@island.is/localization' import { messages } from '../../lib/messages' @@ -22,7 +22,6 @@ import { useDebounce } from 'react-use' import { useNavigate } from 'react-router-dom' import { HealthPaths } from '../../lib/paths' import { RightsPortalDentist } from '@island.is/api/schema' -import { RegisterModal } from '../../components/RegisterModal' import * as styles from './DentistRegistration.css' import { Problem } from '@island.is/react-spa/shared' @@ -40,6 +39,7 @@ export const DentistRegistration = () => { const [activeSearch, setActiveSearch] = useState('') const [selectedDentist, setSelectedDentist] = useState(null) + const [modalVisible, setModalVisible] = useState(false) const [hoverId, setHoverId] = useState(0) const [errorTransfering, setErrorTransfering] = useState(false) const errorBoxRef = useRef(null) @@ -158,30 +158,6 @@ export const DentistRegistration = () => { backgroundColor="blue" /> - - setSelectedDentist(null)} - onAccept={() => { - setErrorTransfering(false) - if (selectedDentist && selectedDentist.id) { - registerDentist({ - variables: { - input: { - id: `${selectedDentist.id}`, - }, - }, - }) - } - }} - id={'dentistRegisterModal'} - title={`${formatMessage(messages.dentistModalTitle)} ${ - selectedDentist?.name - }`} - description="" - isVisible={!!selectedDentist} - buttonLoading={loadingTranser} - /> - {loading ? ( ) : ( @@ -219,19 +195,69 @@ export const DentistRegistration = () => { visible: dentist.id === hoverId, })} > - + title={`${formatMessage( + messages.dentistModalTitle, + )} ${selectedDentist?.name}`} + buttons={[ + { + id: 'RegisterModalAccept', + type: 'primary' as const, + text: formatMessage( + messages.healthRegisterModalAccept, + ), + onClick: () => { + setErrorTransfering(false) + setModalVisible(false) + if (selectedDentist && selectedDentist.id) { + registerDentist({ + variables: { + input: { + id: `${selectedDentist.id}`, + }, + }, + }) + } + }, + }, + { + id: 'RegisterModalDecline', + type: 'ghost' as const, + text: formatMessage( + messages.healthRegisterModalDecline, + ), + onClick: () => { + setModalVisible(false) + }, + }, + ]} + disclosure={ + + } + /> ) : undefined} diff --git a/libs/service-portal/health/src/screens/HealthCenterRegistration/HealthCenterRegistration.tsx b/libs/service-portal/health/src/screens/HealthCenterRegistration/HealthCenterRegistration.tsx index 7e84491a1e2d..432eddfdccf4 100644 --- a/libs/service-portal/health/src/screens/HealthCenterRegistration/HealthCenterRegistration.tsx +++ b/libs/service-portal/health/src/screens/HealthCenterRegistration/HealthCenterRegistration.tsx @@ -16,6 +16,7 @@ import { EmptyState, ErrorScreen, ExcludesFalse, + Modal, } from '@island.is/service-portal/core' import { messages } from '../../lib/messages' import * as styles from './HealthRegistration.css' @@ -26,7 +27,6 @@ import { RightsPortalHealthCenter } from '@island.is/api/schema' import { useNavigate } from 'react-router-dom' import { HealthPaths } from '../../lib/paths' import { formatHealthCenterName } from '../../utils/format' -import { RegisterModal } from '../../components/RegisterModal' import { useGetHealthCenterDoctorsLazyQuery, useGetHealthCenterQuery, @@ -237,22 +237,6 @@ const HealthCenterRegistration = () => { )} - { - setSelectedHealthCenter(null) - setHealthCenterDoctors([]) - }} - onAccept={handleHealthCenterTransfer} - isVisible={!!selectedHealthCenter} - buttonLoading={loadingTransfer} - healthCenterDoctors={healthCenterDoctors} - /> - setFilter(val)} @@ -306,21 +290,61 @@ const HealthCenterRegistration = () => { visible: healthCenter.id === hoverId, })} > - + buttons={[ + { + id: 'RegisterHealthCenterModalAccept', + type: 'primary' as const, + text: formatMessage( + messages.healthRegisterModalAccept, + ), + onClick: handleHealthCenterTransfer, + }, + { + id: 'RegisterHealthCenterModalDecline', + type: 'ghost' as const, + text: formatMessage( + messages.healthRegisterModalDecline, + ), + onClick: () => { + setSelectedHealthCenter(null) + setHealthCenterDoctors([]) + }, + }, + ]} + disclosure={ + + } + /> From 2c1510937f9a1f4d37f28c4f551103e8c249fc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Fri, 6 Sep 2024 11:58:14 +0000 Subject: [PATCH 17/34] fix(native-app): add missing error messages for updating profile information (#15898) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/native/app/src/messages/en.ts | 12 ++++++++---- apps/native/app/src/messages/is.ts | 12 ++++++++---- .../app/src/screens/settings/edit-bank-info.tsx | 7 +++++-- .../native/app/src/screens/settings/edit-confirm.tsx | 7 +++++-- apps/native/app/src/screens/settings/edit-email.tsx | 1 - 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/native/app/src/messages/en.ts b/apps/native/app/src/messages/en.ts index 389df9de9346..57df2e066d8d 100644 --- a/apps/native/app/src/messages/en.ts +++ b/apps/native/app/src/messages/en.ts @@ -490,8 +490,8 @@ export const en: TranslatedMessages = { 'edit.phone.inputlabel': 'Phone number', 'edit.phone.button': 'Save', 'edit.phone.button.empty': 'Save empty', - 'edit.phone.button.error': 'Error', - 'edit.phone.button.errorMessage': 'Could not send verification code', + 'edit.phone.error': 'Error', + 'edit.phone.errorMessage': 'Could not send verification code', // edit email 'edit.email.screenTitle': 'Edit Email', @@ -499,8 +499,8 @@ export const en: TranslatedMessages = { 'edit.email.inputlabel': 'Email', 'edit.email.button': 'Save', 'edit.email.button.empty': 'Save empty', - 'edit.email.button.error': 'Error', - 'edit.email.button.errorMessage': 'Could not send verification code', + 'edit.email.error': 'Error', + 'edit.email.errorMessage': 'Could not send verification code', // edit bank info 'edit.bankinfo.screenTitle': 'Edit Bank Info', @@ -510,6 +510,8 @@ export const en: TranslatedMessages = { 'edit.bankinfo.inputlabel.book': 'Hb.', 'edit.bankinfo.inputlabel.number': 'Account number', 'edit.bankinfo.button': 'Save', + 'edit.bankinfo.error': 'Error', + 'edit.bankinfo.errorMessage': 'Could not save bank info', // edit confirm 'edit.confirm.screenTitle': 'Confirm edit', @@ -523,6 +525,8 @@ export const en: TranslatedMessages = { 'edit.confirm.inputlabel': 'Security number', 'edit.cancel.button': 'Cancel', 'edit.confirm.button': 'Confirm', + 'edit.confirm.error': 'Error', + 'edit.confirm.errorMessage': 'Could not update information', // air discount 'airDiscount.screenTitle': 'Air discount scheme', diff --git a/apps/native/app/src/messages/is.ts b/apps/native/app/src/messages/is.ts index 502f6f0ebc57..3af0414f90f1 100644 --- a/apps/native/app/src/messages/is.ts +++ b/apps/native/app/src/messages/is.ts @@ -490,8 +490,8 @@ export const is = { 'edit.phone.inputlabel': 'Símanúmer', 'edit.phone.button': 'Vista', 'edit.phone.button.empty': 'Vista tómt', - 'edit.phone.button.error': 'Villa', - 'edit.phone.button.errorMessage': 'Gat ekki sent staðfestingarkóða', + 'edit.phone.error': 'Villa', + 'edit.phone.errorMessage': 'Gat ekki sent staðfestingarkóða', // edit email 'edit.email.screenTitle': 'Breyta Netfangi', @@ -499,8 +499,8 @@ export const is = { 'edit.email.inputlabel': 'Netfang', 'edit.email.button': 'Vista', 'edit.email.button.empty': 'Vista tómt', - 'edit.email.button.error': 'Villa', - 'edit.email.button.errorMessage': 'Gat ekki sent staðfestingarkóða', + 'edit.email.error': 'Villa', + 'edit.email.errorMessage': 'Gat ekki sent staðfestingarkóða', // edit bank info 'edit.bankinfo.screenTitle': 'Breyta banka upplýsingum', @@ -510,6 +510,8 @@ export const is = { 'edit.bankinfo.inputlabel.book': 'Hb.', 'edit.bankinfo.inputlabel.number': 'Reikningsnúmer', 'edit.bankinfo.button': 'Vista', + 'edit.bankinfo.error': 'Villa', + 'edit.bankinfo.errorMessage': 'Gat ekki vistað reikningsupplýsingar', // edit confirm 'edit.confirm.screenTitle': 'Staðfesta aðgerð', @@ -523,6 +525,8 @@ export const is = { 'edit.confirm.inputlabel': 'Öryggisnúmer', 'edit.cancel.button': 'Hætta við', 'edit.confirm.button': 'Staðfesta', + 'edit.confirm.error': 'Villa', + 'edit.confirm.errorMessage': 'Gat ekki uppfært upplýsingar', // air discount 'airDiscount.screenTitle': 'Loftbrú', diff --git a/apps/native/app/src/screens/settings/edit-bank-info.tsx b/apps/native/app/src/screens/settings/edit-bank-info.tsx index ea92a495db8e..a6de5f0f03e8 100644 --- a/apps/native/app/src/screens/settings/edit-bank-info.tsx +++ b/apps/native/app/src/screens/settings/edit-bank-info.tsx @@ -119,7 +119,7 @@ export const EditBankInfoScreen: NavigationFunctionComponent = ({ }) if (!res.data) { - throw new Error('Faild to update') + throw new Error('Failed to update') } Navigation.dismissModal(componentId) @@ -128,7 +128,10 @@ export const EditBankInfoScreen: NavigationFunctionComponent = ({ throw new Error('Failed to update') } } catch (e) { - Alert.alert('Villa', 'Gat ekki vistað reikningsupplýsingar') + Alert.alert( + intl.formatMessage({ id: 'edit.bankinfo.error' }), + intl.formatMessage({ id: 'edit.bankinfo.errorMessage' }), + ) } }} /> diff --git a/apps/native/app/src/screens/settings/edit-confirm.tsx b/apps/native/app/src/screens/settings/edit-confirm.tsx index a7e3c22a3527..c624d83e259a 100644 --- a/apps/native/app/src/screens/settings/edit-confirm.tsx +++ b/apps/native/app/src/screens/settings/edit-confirm.tsx @@ -53,10 +53,13 @@ export const EditConfirmScreen: NavigationFunctionComponent = ({ Navigation.dismissModal(parentComponentId) } } else { - throw new Error('Failed') + throw new Error('Failed to update profile') } } catch (e) { - Alert.alert('Villa', 'Gat ekki uppfært upplýsingar') + Alert.alert( + intl.formatMessage({ id: 'edit.confirm.error' }), + intl.formatMessage({ id: 'edit.confirm.errorMessage' }), + ) } setLoading(false) } diff --git a/apps/native/app/src/screens/settings/edit-email.tsx b/apps/native/app/src/screens/settings/edit-email.tsx index b2d5a0df0020..12994c6d11f9 100644 --- a/apps/native/app/src/screens/settings/edit-email.tsx +++ b/apps/native/app/src/screens/settings/edit-email.tsx @@ -100,7 +100,6 @@ export const EditEmailScreen: NavigationFunctionComponent<{ }) if (res.data) { Navigation.dismissModal(componentId) - console.log(res.data, 'Uppfærði tómt netfang') } else { throw new Error('Failed to delete email') } From 535fd14449ea598e0b9a65806d8394f3545c4ee0 Mon Sep 17 00:00:00 2001 From: unakb Date: Fri, 6 Sep 2024 12:34:35 +0000 Subject: [PATCH 18/34] chore(j-s): Allow public prosecutor user to open case files (#15852) * chore(j-s): Allow public prosecutor user to open case files * Update getByIdRolesRules.spec.ts * Update getCaseFileSignedUrlRolesRules.spec.ts * fix(j-s): Only show public prosecutor cases in a complete state * Update getCaseFileSignedUrlRolesRules.spec.ts * fix(j-s):tests * fix(j-s): Add public prosecutor staff rule for indictment pdf --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/app/modules/case/case.controller.ts | 2 + .../getCaseFilesRecordPdfRolesRules.spec.ts | 4 +- .../getIndictmentPdfRolesRules.spec.ts | 4 +- .../src/app/modules/file/file.controller.ts | 2 + .../guards/test/viewCaseFileGuard.spec.ts | 54 +++++++++++++++++++ .../modules/file/guards/viewCaseFile.guard.ts | 5 ++ .../getCaseFileSignedUrlRolesRules.spec.ts | 4 +- 7 files changed, 72 insertions(+), 3 deletions(-) diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts index 4978748add3a..f6aaa0a14fd3 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts @@ -545,6 +545,7 @@ export class CaseController { @RolesRules( prosecutorRule, prosecutorRepresentativeRule, + publicProsecutorStaffRule, districtCourtJudgeRule, districtCourtRegistrarRule, districtCourtAssistantRule, @@ -700,6 +701,7 @@ export class CaseController { @RolesRules( prosecutorRule, prosecutorRepresentativeRule, + publicProsecutorStaffRule, districtCourtJudgeRule, districtCourtRegistrarRule, districtCourtAssistantRule, diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCaseFilesRecordPdfRolesRules.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCaseFilesRecordPdfRolesRules.spec.ts index 98e04d53d4f8..05822ae9a72c 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCaseFilesRecordPdfRolesRules.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getCaseFilesRecordPdfRolesRules.spec.ts @@ -4,6 +4,7 @@ import { districtCourtRegistrarRule, prosecutorRepresentativeRule, prosecutorRule, + publicProsecutorStaffRule, } from '../../../../guards' import { CaseController } from '../../case.controller' @@ -19,9 +20,10 @@ describe('CaseController - Get case files record pdf rules', () => { }) it('should give permission to roles', () => { - expect(rules).toHaveLength(5) + expect(rules).toHaveLength(6) expect(rules).toContain(prosecutorRule) expect(rules).toContain(prosecutorRepresentativeRule) + expect(rules).toContain(publicProsecutorStaffRule) expect(rules).toContain(districtCourtJudgeRule) expect(rules).toContain(districtCourtRegistrarRule) expect(rules).toContain(districtCourtAssistantRule) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getIndictmentPdfRolesRules.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getIndictmentPdfRolesRules.spec.ts index 170857ab4dca..6fee0d26b903 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/getIndictmentPdfRolesRules.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/getIndictmentPdfRolesRules.spec.ts @@ -4,6 +4,7 @@ import { districtCourtRegistrarRule, prosecutorRepresentativeRule, prosecutorRule, + publicProsecutorStaffRule, } from '../../../../guards' import { CaseController } from '../../case.controller' @@ -19,9 +20,10 @@ describe('CaseController - Get indictment pdf rules', () => { }) it('should give permission to roles', () => { - expect(rules).toHaveLength(5) + expect(rules).toHaveLength(6) expect(rules).toContain(prosecutorRule) expect(rules).toContain(prosecutorRepresentativeRule) + expect(rules).toContain(publicProsecutorStaffRule) expect(rules).toContain(districtCourtJudgeRule) expect(rules).toContain(districtCourtRegistrarRule) expect(rules).toContain(districtCourtAssistantRule) diff --git a/apps/judicial-system/backend/src/app/modules/file/file.controller.ts b/apps/judicial-system/backend/src/app/modules/file/file.controller.ts index f507d84863e2..340d782f767b 100644 --- a/apps/judicial-system/backend/src/app/modules/file/file.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/file/file.controller.ts @@ -37,6 +37,7 @@ import { prisonSystemStaffRule, prosecutorRepresentativeRule, prosecutorRule, + publicProsecutorStaffRule, } from '../../guards' import { Case, @@ -133,6 +134,7 @@ export class FileController { @RolesRules( prosecutorRule, prosecutorRepresentativeRule, + publicProsecutorStaffRule, districtCourtJudgeRule, districtCourtRegistrarRule, districtCourtAssistantRule, diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/test/viewCaseFileGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/file/guards/test/viewCaseFileGuard.spec.ts index d27014f8a5d1..e29e254f3c2d 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/test/viewCaseFileGuard.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/test/viewCaseFileGuard.spec.ts @@ -12,6 +12,7 @@ import { districtCourtRoles, InstitutionType, prosecutionRoles, + publicProsecutorRoles, User, UserRole, } from '@island.is/judicial-system/types' @@ -210,6 +211,59 @@ describe('View Case File Guard', () => { }) }) + describe.each(publicProsecutorRoles)('role %s', (role) => { + describe.each(completedCaseStates)('%s cases', (state) => { + let then: Then + + beforeEach(() => { + mockRequest.mockImplementationOnce(() => ({ + user: { + role, + institution: { + type: InstitutionType.PROSECUTORS_OFFICE, + id: '8f9e2f6d-6a00-4a5e-b39b-95fd110d762e', + }, + }, + case: { state }, + })) + + then = givenWhenThen() + }) + + it('should activate', () => { + expect(then.result).toBe(true) + }) + }) + + describe.each( + Object.values(CaseState).filter( + (state) => !completedCaseStates.includes(state), + ), + )('%s cases', (state) => { + let then: Then + + beforeEach(() => { + mockRequest.mockImplementationOnce(() => ({ + user: { + role, + institution: { + type: InstitutionType.PROSECUTORS_OFFICE, + id: '8f9e2f6d-6a00-4a5e-b39b-95fd110d762e', + }, + }, + case: { state }, + })) + + then = givenWhenThen() + }) + + it('should throw ForbiddenException', () => { + expect(then.error).toBeInstanceOf(ForbiddenException) + expect(then.error.message).toBe(`Forbidden for ${role}`) + }) + }) + }) + describe.each(Object.keys(CaseState))('in state %s', (state) => { describe.each( Object.keys(UserRole).filter( diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/viewCaseFile.guard.ts b/apps/judicial-system/backend/src/app/modules/file/guards/viewCaseFile.guard.ts index 466cfb67357e..dd00dfcdcbaa 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/viewCaseFile.guard.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/viewCaseFile.guard.ts @@ -14,6 +14,7 @@ import { isDistrictCourtUser, isPrisonSystemUser, isProsecutionUser, + isPublicProsecutorUser, User, } from '@island.is/judicial-system/types' @@ -44,6 +45,10 @@ export class ViewCaseFileGuard implements CanActivate { return true } + if (isPublicProsecutorUser(user) && isCompletedCase(theCase.state)) { + return true + } + if ( isDistrictCourtUser(user) && ([CaseState.SUBMITTED, CaseState.RECEIVED].includes(theCase.state) || diff --git a/apps/judicial-system/backend/src/app/modules/file/test/fileController/getCaseFileSignedUrlRolesRules.spec.ts b/apps/judicial-system/backend/src/app/modules/file/test/fileController/getCaseFileSignedUrlRolesRules.spec.ts index dd24bcd6d265..86d57470734e 100644 --- a/apps/judicial-system/backend/src/app/modules/file/test/fileController/getCaseFileSignedUrlRolesRules.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/file/test/fileController/getCaseFileSignedUrlRolesRules.spec.ts @@ -8,6 +8,7 @@ import { prisonSystemStaffRule, prosecutorRepresentativeRule, prosecutorRule, + publicProsecutorStaffRule, } from '../../../../guards' import { FileController } from '../../file.controller' @@ -23,9 +24,10 @@ describe('FileController - Get case file signed url rules', () => { }) it('should give permission to roles', () => { - expect(rules).toHaveLength(9) + expect(rules).toHaveLength(10) expect(rules).toContain(prosecutorRule) expect(rules).toContain(prosecutorRepresentativeRule) + expect(rules).toContain(publicProsecutorStaffRule) expect(rules).toContain(districtCourtJudgeRule) expect(rules).toContain(districtCourtRegistrarRule) expect(rules).toContain(districtCourtAssistantRule) From d8fa83a091b79f8768ebbe978eab32f825b1fdd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Fri, 6 Sep 2024 13:05:35 +0000 Subject: [PATCH 19/34] feat(j-s): Confirmed subpoena stamp (#15871) * Checkpoint * Cleanup * Rename * Refactor * Remove unused code * Fix lint * Make confirmation smaller * Use correct date --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../app/formatters/confirmedIndictmentPdf.ts | 4 +- .../backend/src/app/formatters/index.ts | 2 +- .../src/app/formatters/indictmentPdf.ts | 4 +- .../backend/src/app/formatters/pdfHelpers.ts | 106 +++++++++++++++++- .../backend/src/app/formatters/subpoenaPdf.ts | 15 +++ .../src/app/modules/case/case.controller.ts | 2 +- .../src/app/modules/case/case.service.ts | 2 +- .../app/modules/case/filters/case.filter.ts | 1 - .../modules/case/internalCase.controller.ts | 2 +- .../app/modules/case/internalCase.service.ts | 2 +- .../case/limitedAccessCase.controller.ts | 2 +- .../src/app/modules/case/pdf.service.ts | 4 +- .../createPresignedPostGuards.spec.ts | 6 +- .../internalNotification.service.ts | 3 +- .../notification/notification.service.ts | 2 +- .../IndictmentCaseFilesList.spec.tsx | 1 - .../Indictments/Overview/Overview.tsx | 1 - 17 files changed, 131 insertions(+), 28 deletions(-) diff --git a/apps/judicial-system/backend/src/app/formatters/confirmedIndictmentPdf.ts b/apps/judicial-system/backend/src/app/formatters/confirmedIndictmentPdf.ts index 1c663df0edfd..f5798cf30849 100644 --- a/apps/judicial-system/backend/src/app/formatters/confirmedIndictmentPdf.ts +++ b/apps/judicial-system/backend/src/app/formatters/confirmedIndictmentPdf.ts @@ -4,14 +4,14 @@ import { formatDate, lowercase } from '@island.is/judicial-system/formatters' import { calculatePt, + Confirmation, drawTextWithEllipsisPDFKit, - IndictmentConfirmation, smallFontSize, } from './pdfHelpers' import { PDFKitCoatOfArms } from './PDFKitCoatOfArms' export const createConfirmedIndictment = async ( - confirmation: IndictmentConfirmation, + confirmation: Confirmation, indictmentPDF: Buffer, ): Promise => { const pdfDoc = await PDFDocument.load(indictmentPDF) diff --git a/apps/judicial-system/backend/src/app/formatters/index.ts b/apps/judicial-system/backend/src/app/formatters/index.ts index 084da81ca6b5..120d7279c56e 100644 --- a/apps/judicial-system/backend/src/app/formatters/index.ts +++ b/apps/judicial-system/backend/src/app/formatters/index.ts @@ -29,7 +29,7 @@ export { formatPostponedCourtDateEmailNotification, stripHtmlTags, } from './formatters' -export { IndictmentConfirmation } from './pdfHelpers' +export { Confirmation } from './pdfHelpers' export { getRequestPdfAsBuffer, getRequestPdfAsString } from './requestPdf' export { getRulingPdfAsBuffer, getRulingPdfAsString } from './rulingPdf' export { createCaseFilesRecord } from './caseFilesRecordPdf' diff --git a/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts b/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts index c19da2711a7f..5ab70b81c81a 100644 --- a/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts +++ b/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts @@ -16,7 +16,7 @@ import { addNormalPlusJustifiedText, addNormalPlusText, addNormalText, - IndictmentConfirmation, + Confirmation, setTitle, } from './pdfHelpers' @@ -52,7 +52,7 @@ const roman = (num: number) => { export const createIndictment = async ( theCase: Case, formatMessage: FormatMessage, - confirmation?: IndictmentConfirmation, + confirmation?: Confirmation, ): Promise => { const doc = new PDFDocument({ size: 'A4', diff --git a/apps/judicial-system/backend/src/app/formatters/pdfHelpers.ts b/apps/judicial-system/backend/src/app/formatters/pdfHelpers.ts index d348032ebc35..4f44aa249e93 100644 --- a/apps/judicial-system/backend/src/app/formatters/pdfHelpers.ts +++ b/apps/judicial-system/backend/src/app/formatters/pdfHelpers.ts @@ -5,7 +5,7 @@ import { formatDate, lowercase } from '@island.is/judicial-system/formatters' import { coatOfArms } from './coatOfArms' import { policeStar } from './policeStar' -export interface IndictmentConfirmation { +export interface Confirmation { actor: string title?: string institution: string @@ -22,6 +22,10 @@ export const largeFontSize = 18 export const hugeFontSize = 26 export const giganticFontSize = 33 +const lightGray = '#FAFAFA' +const darkGray = '#CBCBCB' +const gold = '#ADA373' + const setFont = (doc: PDFKit.PDFDocument, font?: string) => { if (font) { doc.font(font) @@ -106,13 +110,105 @@ export const addPoliceStar = (doc: PDFKit.PDFDocument) => { doc.scale(25).translate(-270, -70) } +export const addConfirmation = ( + doc: PDFKit.PDFDocument, + confirmation: Confirmation, +) => { + const pageMargin = calculatePt(18) + const shaddowHeight = calculatePt(70) + const coatOfArmsWidth = calculatePt(105) + const coatOfArmsX = pageMargin + calculatePt(8) + const titleHeight = calculatePt(24) + const titleX = coatOfArmsX + coatOfArmsWidth + calculatePt(8) + const institutionWidth = calculatePt(160) + const confirmedByWidth = institutionWidth + calculatePt(48) + const shaddowWidth = institutionWidth + confirmedByWidth + coatOfArmsWidth + const titleWidth = institutionWidth + confirmedByWidth + + // Draw the shadow + doc + .rect(pageMargin, pageMargin + calculatePt(8), shaddowWidth, shaddowHeight) + .fill(lightGray) + .stroke() + + // Draw the coat of arms + doc + .rect(coatOfArmsX, pageMargin, coatOfArmsWidth, shaddowHeight) + .fillAndStroke('white', darkGray) + + addCoatOfArms(doc, calculatePt(49), calculatePt(24)) + + // Draw the title + doc + .rect(coatOfArmsX + coatOfArmsWidth, pageMargin, titleWidth, titleHeight) + .fillAndStroke(lightGray, darkGray) + doc.fill('black') + doc.font('Times-Bold') + doc + .fontSize(calculatePt(smallFontSize)) + .text('Réttarvörslugátt', titleX, pageMargin + calculatePt(9)) + doc.font('Times-Roman') + // The X value here is approx. 8px after the title + doc.text('Rafræn staðfesting', calculatePt(210), pageMargin + calculatePt(9)) + doc.text( + formatDate(confirmation.date) || '', + shaddowWidth - calculatePt(24), + pageMargin + calculatePt(9), + ) + + // Draw the institution + doc + .rect( + coatOfArmsX + coatOfArmsWidth, + pageMargin + titleHeight, + institutionWidth, + shaddowHeight - titleHeight, + ) + .fillAndStroke('white', darkGray) + doc.fill('black') + doc.font('Times-Bold') + doc.text('Dómstóll', titleX, pageMargin + titleHeight + calculatePt(10)) + doc.font('Times-Roman') + drawTextWithEllipsis( + doc, + confirmation.institution, + titleX, + pageMargin + titleHeight + calculatePt(22), + institutionWidth - calculatePt(16), + ) + + // Draw the actor + doc + .rect( + coatOfArmsX + coatOfArmsWidth + institutionWidth, + pageMargin + titleHeight, + confirmedByWidth, + shaddowHeight - titleHeight, + ) + .fillAndStroke('white', darkGray) + doc.fill('black') + doc.font('Times-Bold') + doc.text( + 'Samþykktaraðili', + titleX + institutionWidth, + pageMargin + titleHeight + calculatePt(10), + ) + doc.font('Times-Roman') + doc.text( + `${confirmation.actor}${ + confirmation.title ? `, ${lowercase(confirmation.title)}` : '' + }`, + titleX + institutionWidth, + pageMargin + titleHeight + calculatePt(22), + ) + + doc.fillColor('black') +} + export const addIndictmentConfirmation = ( doc: PDFKit.PDFDocument, - confirmation: IndictmentConfirmation, + confirmation: Confirmation, ) => { - const lightGray = '#FAFAFA' - const darkGray = '#CBCBCB' - const gold = '#ADA373' const pageMargin = calculatePt(18) const shaddowHeight = calculatePt(90) const coatOfArmsWidth = calculatePt(105) diff --git a/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts index 313088079040..4af8001053e8 100644 --- a/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts +++ b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts @@ -13,6 +13,7 @@ import { subpoena as strings } from '../messages' import { Case } from '../modules/case' import { Defendant } from '../modules/defendant' import { + addConfirmation, addEmptyLines, addFooter, addHugeHeading, @@ -49,6 +50,11 @@ export const createSubpoena = ( doc.on('data', (chunk) => sinc.push(chunk)) setTitle(doc, formatMessage(strings.title)) + + if (dateLog) { + addEmptyLines(doc, 5) + } + addNormalText(doc, `${theCase.court?.name}`, 'Times-Bold', true) addNormalRightAlignedText( @@ -148,6 +154,15 @@ export const createSubpoena = ( addFooter(doc) + if (dateLog) { + addConfirmation(doc, { + actor: theCase.judge?.name || '', + title: theCase.judge?.title, + institution: theCase.judge?.institution?.name || '', + date: dateLog.created, + }) + } + doc.end() return new Promise((resolve) => diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts index f6aaa0a14fd3..7b0da96f91d1 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts @@ -68,7 +68,7 @@ import { prosecutorRule, publicProsecutorStaffRule, } from '../../guards' -import { CaseEvent, EventService } from '../event' +import { EventService } from '../event' import { UserService } from '../user' import { CreateCaseDto } from './dto/createCase.dto' import { TransitionCaseDto } from './dto/transitionCase.dto' diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 65a7daca3a84..eabf3a6cee0b 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -56,7 +56,7 @@ import { import { AwsS3Service } from '../aws-s3' import { CourtService } from '../court' import { Defendant, DefendantService } from '../defendant' -import { CaseEvent, EventService } from '../event' +import { EventService } from '../event' import { EventLog, EventLogService } from '../event-log' import { CaseFile, FileService } from '../file' import { IndictmentCount } from '../indictment-count' diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts b/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts index d49a4ea4a964..7228bb2fda08 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts @@ -5,7 +5,6 @@ import { CaseDecision, CaseState, CaseType, - DateType, getIndictmentVerdictAppealDeadline, IndictmentCaseReviewDecision, InstitutionType, diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts index 9149585ce497..0d73789d2686 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts @@ -23,7 +23,7 @@ import { restrictionCases, } from '@island.is/judicial-system/types' -import { CaseEvent, EventService } from '../event' +import { EventService } from '../event' import { DeliverDto } from './dto/deliver.dto' import { DeliverCancellationNoticeDto } from './dto/deliverCancellationNotice.dto' import { InternalCasesDto } from './dto/internalCases.dto' diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts index ebee2422a741..235d74f860a0 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts @@ -53,7 +53,7 @@ import { AwsS3Service } from '../aws-s3' import { CourtDocumentFolder, CourtService } from '../court' import { courtSubtypes } from '../court/court.service' import { Defendant, DefendantService } from '../defendant' -import { CaseEvent, EventService } from '../event' +import { EventService } from '../event' import { CaseFile, FileService } from '../file' import { IndictmentCount, IndictmentCountService } from '../indictment-count' import { Institution } from '../institution' diff --git a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts index 809dcfc390cc..42c90d58ec5e 100644 --- a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts @@ -40,7 +40,7 @@ import { import { nowFactory } from '../../factories' import { defenderRule, prisonSystemStaffRule } from '../../guards' -import { CaseEvent, EventService } from '../event' +import { EventService } from '../event' import { User } from '../user' import { TransitionCaseDto } from './dto/transitionCase.dto' import { UpdateCaseDto } from './dto/updateCase.dto' diff --git a/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts b/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts index 8289b8d636ec..6b956856ebb1 100644 --- a/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts @@ -22,6 +22,7 @@ import { } from '@island.is/judicial-system/types' import { + Confirmation, createCaseFilesRecord, createIndictment, createSubpoena, @@ -29,7 +30,6 @@ import { getCustodyNoticePdfAsBuffer, getRequestPdfAsBuffer, getRulingPdfAsBuffer, - IndictmentConfirmation, } from '../../formatters' import { AwsS3Service } from '../aws-s3' import { Defendant } from '../defendant' @@ -206,7 +206,7 @@ export class PdfService { ) } - let confirmation: IndictmentConfirmation | undefined = undefined + let confirmation: Confirmation | undefined = undefined if (hasIndictmentCaseBeenSubmittedToCourt(theCase.state)) { if (theCase.indictmentHash) { diff --git a/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createPresignedPostGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createPresignedPostGuards.spec.ts index 310e6b9f70c2..fcb2e12d9efd 100644 --- a/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createPresignedPostGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createPresignedPostGuards.spec.ts @@ -4,11 +4,7 @@ import { restrictionCases, } from '@island.is/judicial-system/types' -import { - CaseCompletedGuard, - CaseTypeGuard, - CaseWriteGuard, -} from '../../../case' +import { CaseTypeGuard, CaseWriteGuard } from '../../../case' import { LimitedAccessFileController } from '../../limitedAccessFile.controller' describe('LimitedAccessFileController - Create presigned post guards', () => { diff --git a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts index 2c6aa5278d6d..ebce85337c80 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts @@ -73,10 +73,9 @@ import { } from '../../formatters' import { notifications } from '../../messages' import { type Case, DateLog } from '../case' -import { ExplanatoryComment } from '../case/models/explanatoryComment.model' import { CourtService } from '../court' import { type Defendant, DefendantService } from '../defendant' -import { CaseEvent, EventService } from '../event' +import { EventService } from '../event' import { DeliverResponse } from './models/deliver.response' import { Notification, Recipient } from './models/notification.model' import { BaseNotificationService } from './baseNotification.service' diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts index 8a9e78671c95..8103b1106f14 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts @@ -11,7 +11,7 @@ import { type User } from '@island.is/judicial-system/types' import { CaseState, NotificationType } from '@island.is/judicial-system/types' import { type Case } from '../case' -import { CaseEvent, EventService } from '../event' +import { EventService } from '../event' import { SendNotificationResponse } from './models/sendNotification.response' @Injectable() diff --git a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.spec.tsx b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.spec.tsx index fdb75baa1555..65ec62d224ed 100644 --- a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.spec.tsx +++ b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.spec.tsx @@ -1,7 +1,6 @@ import { render, screen } from '@testing-library/react' import { - CaseDecision, CaseFileCategory, CaseType, } from '@island.is/judicial-system-web/src/graphql/schema' diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx index 9908cf73787a..c554555f7593 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx @@ -5,7 +5,6 @@ import { useRouter } from 'next/router' import { Box, Option, Select, Text } from '@island.is/island-ui/core' import * as constants from '@island.is/judicial-system/consts' import { formatDate } from '@island.is/judicial-system/formatters' -import { isCompletedCase } from '@island.is/judicial-system/types' import { core, titles } from '@island.is/judicial-system-web/messages' import { BlueBox, From 4d3250ff87b1655f67fa09a0b0e5e95b33703ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Fri, 6 Sep 2024 14:38:34 +0000 Subject: [PATCH 20/34] fix(native-app): final fix for ios font (#15913) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/native/app/android/link-assets-manifest.json | 2 +- apps/native/app/ios/IslandApp/Info.plist | 2 +- apps/native/app/ios/link-assets-manifest.json | 2 +- apps/native/app/src/ui/lib/list/list-item.tsx | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/native/app/android/link-assets-manifest.json b/apps/native/app/android/link-assets-manifest.json index d9434d82b38c..96ae18202a1b 100644 --- a/apps/native/app/android/link-assets-manifest.json +++ b/apps/native/app/android/link-assets-manifest.json @@ -38,7 +38,7 @@ "sha1": "8bf01afac8fc3e072eb36667374393b8566a044d" }, { - "path": "assets/fonts/IBMPlexSans-Regular.ttf", + "path": "assets/fonts/IBMPlexSans.ttf", "sha1": "ad38f2d6870ca73533f36bdb958cd8083a49c1a8" }, { diff --git a/apps/native/app/ios/IslandApp/Info.plist b/apps/native/app/ios/IslandApp/Info.plist index b074b56c5c00..24d36d07c197 100644 --- a/apps/native/app/ios/IslandApp/Info.plist +++ b/apps/native/app/ios/IslandApp/Info.plist @@ -55,7 +55,7 @@ Used for license scanning capabilities UIAppFonts - IBMPlexSans-Regular.ttf + IBMPlexSans.ttf IBMPlexSans-Italic.ttf IBMPlexSans-Bold.ttf IBMPlexSans-BoldItalic.ttf diff --git a/apps/native/app/ios/link-assets-manifest.json b/apps/native/app/ios/link-assets-manifest.json index d9434d82b38c..96ae18202a1b 100644 --- a/apps/native/app/ios/link-assets-manifest.json +++ b/apps/native/app/ios/link-assets-manifest.json @@ -38,7 +38,7 @@ "sha1": "8bf01afac8fc3e072eb36667374393b8566a044d" }, { - "path": "assets/fonts/IBMPlexSans-Regular.ttf", + "path": "assets/fonts/IBMPlexSans.ttf", "sha1": "ad38f2d6870ca73533f36bdb958cd8083a49c1a8" }, { diff --git a/apps/native/app/src/ui/lib/list/list-item.tsx b/apps/native/app/src/ui/lib/list/list-item.tsx index 8f77cd93bc13..5454e1172994 100644 --- a/apps/native/app/src/ui/lib/list/list-item.tsx +++ b/apps/native/app/src/ui/lib/list/list-item.tsx @@ -117,7 +117,6 @@ export function ListItem({ variant="body3" numberOfLines={1} ellipsizeMode="tail" - style={{ fontWeight: '300' }} > {title} From d0f7c8b0c5954bc48fb08bd292048cd704f97885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A6var=20M=C3=A1r=20Atlason?= <54210288+saevarma@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:44:21 +0000 Subject: [PATCH 21/34] infra(services-auth-delegation-api): Grant user-notification-worker access to delegation-api (#15912) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/services/auth/delegation-api/infra/delegation-api.ts | 7 ++++++- charts/identity-server/values.dev.yaml | 1 + charts/identity-server/values.prod.yaml | 1 + charts/identity-server/values.staging.yaml | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/services/auth/delegation-api/infra/delegation-api.ts b/apps/services/auth/delegation-api/infra/delegation-api.ts index e68fc0951175..1ceff205f6d3 100644 --- a/apps/services/auth/delegation-api/infra/delegation-api.ts +++ b/apps/services/auth/delegation-api/infra/delegation-api.ts @@ -90,5 +90,10 @@ export const serviceSetup = (services: { public: false, }, }) - .grantNamespaces('nginx-ingress-internal', 'islandis', 'service-portal') + .grantNamespaces( + 'nginx-ingress-internal', + 'islandis', + 'service-portal', + 'user-notification-worker', + ) } diff --git a/charts/identity-server/values.dev.yaml b/charts/identity-server/values.dev.yaml index 5f2548139268..1de58ff732d4 100644 --- a/charts/identity-server/values.dev.yaml +++ b/charts/identity-server/values.dev.yaml @@ -308,6 +308,7 @@ services-auth-delegation-api: - 'nginx-ingress-internal' - 'islandis' - 'service-portal' + - 'user-notification-worker' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/identity-server/values.prod.yaml b/charts/identity-server/values.prod.yaml index 6d152fff6b1c..d05db0f9e15c 100644 --- a/charts/identity-server/values.prod.yaml +++ b/charts/identity-server/values.prod.yaml @@ -305,6 +305,7 @@ services-auth-delegation-api: - 'nginx-ingress-internal' - 'islandis' - 'service-portal' + - 'user-notification-worker' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/identity-server/values.staging.yaml b/charts/identity-server/values.staging.yaml index 235d50eef758..e8b29ca0e315 100644 --- a/charts/identity-server/values.staging.yaml +++ b/charts/identity-server/values.staging.yaml @@ -308,6 +308,7 @@ services-auth-delegation-api: - 'nginx-ingress-internal' - 'islandis' - 'service-portal' + - 'user-notification-worker' grantNamespacesEnabled: true healthCheck: liveness: From 9aaa6b138599c9d44de2c5b474be8cd6df1f1103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:56:56 +0000 Subject: [PATCH 22/34] feat(web): Search screen - Stop overriding previous filters (#15915) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/screens/Search/Search.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/web/screens/Search/Search.tsx b/apps/web/screens/Search/Search.tsx index 86018074625d..1ee9de5acab3 100644 --- a/apps/web/screens/Search/Search.tsx +++ b/apps/web/screens/Search/Search.tsx @@ -604,7 +604,13 @@ const Search: Screen = ({ active={!query?.type?.length} onClick={() => { dispatch({ - type: ActionType.RESET_SEARCH, + type: ActionType.SET_PARAMS, + payload: { + query: { + type: [], + processentry: false, + }, + }, }) }} > @@ -628,8 +634,6 @@ const Search: Screen = ({ query: { processentry: false, ...getSearchParams(key), - category: [], - organization: [], }, searchLocked: false, }, @@ -676,7 +680,6 @@ const Search: Screen = ({ type: ActionType.SET_PARAMS, payload: { query: { - ...getSearchParams('webArticle'), ...payload, }, }, From 38737b98239cca54304cf3d3cd008474be85e180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Fri, 6 Sep 2024 17:18:01 +0000 Subject: [PATCH 23/34] feat(native-app): app should use locale on user profile (#15888) * feat: locale on user should be used for locale in the app * feat: use getProfileQuery for locale as well --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/native/app/src/graphql/client.ts | 3 ++ apps/native/app/src/screens/home/home.tsx | 6 ++++ .../app/src/screens/settings/settings.tsx | 31 +++++++++++++++++-- .../app/src/stores/preferences-store.ts | 25 +++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/apps/native/app/src/graphql/client.ts b/apps/native/app/src/graphql/client.ts index 7ac9267a0ae0..fbac7b3719a1 100644 --- a/apps/native/app/src/graphql/client.ts +++ b/apps/native/app/src/graphql/client.ts @@ -159,6 +159,9 @@ const cache = new InMemoryCache({ userNotifications: { merge: true, }, + getUserProfile: { + merge: true, + }, }, }, DocumentV2: { diff --git a/apps/native/app/src/screens/home/home.tsx b/apps/native/app/src/screens/home/home.tsx index 40fa0f020189..c8ec245d3cd5 100644 --- a/apps/native/app/src/screens/home/home.tsx +++ b/apps/native/app/src/screens/home/home.tsx @@ -34,6 +34,7 @@ import { ApplicationsModule } from './applications-module' import { HelloModule } from './hello-module' import { InboxModule } from './inbox-module' import { OnboardingModule } from './onboarding-module' +import { usePreferencesStore } from '../../stores/preferences-store' interface ListItem { id: string @@ -99,6 +100,9 @@ export const MainHomeScreen: NavigationFunctionComponent = ({ useAndroidNotificationPermission() const syncToken = useNotificationsStore(({ syncToken }) => syncToken) const checkUnseen = useNotificationsStore(({ checkUnseen }) => checkUnseen) + const getAndSetLocale = usePreferencesStore( + ({ getAndSetLocale }) => getAndSetLocale, + ) const [refetching, setRefetching] = useState(false) const flatListRef = useRef(null) const ui = useUiStore() @@ -126,6 +130,8 @@ export const MainHomeScreen: NavigationFunctionComponent = ({ // Sync push tokens and unseen notifications syncToken() checkUnseen() + // Get user locale from server + getAndSetLocale() // Handle initial notification handleInitialNotification() diff --git a/apps/native/app/src/screens/settings/settings.tsx b/apps/native/app/src/screens/settings/settings.tsx index 5b54f1980738..5b4bf194d1ae 100644 --- a/apps/native/app/src/screens/settings/settings.tsx +++ b/apps/native/app/src/screens/settings/settings.tsx @@ -14,7 +14,6 @@ import { Image, Linking, Platform, - Pressable, ScrollView, Switch, TouchableOpacity, @@ -151,6 +150,8 @@ export const SettingsScreen: NavigationFunctionComponent = ({ }).then(({ selectedItem }: any) => { if (selectedItem) { setLocale(selectedItem.id) + const locale = selectedItem.id === 'is-IS' ? 'is' : 'en' + updateLocale(locale) } }) } @@ -166,7 +167,7 @@ export const SettingsScreen: NavigationFunctionComponent = ({ }, 330) }, []) - function updateDocumentNotifications(value: boolean) { + const updateDocumentNotifications = (value: boolean) => { client .mutate({ mutation: UpdateProfileDocument, @@ -198,7 +199,7 @@ export const SettingsScreen: NavigationFunctionComponent = ({ }) } - function updateEmailNotifications(value: boolean) { + const updateEmailNotifications = (value: boolean) => { client .mutate({ mutation: UpdateProfileDocument, @@ -230,6 +231,30 @@ export const SettingsScreen: NavigationFunctionComponent = ({ }) } + const updateLocale = (value: string) => { + client + .mutate({ + mutation: UpdateProfileDocument, + update(cache, { data }) { + cache.modify({ + fields: { + getUserProfile: (existing) => { + return { ...existing, ...data?.updateProfile } + }, + }, + }) + }, + variables: { + input: { + locale: value, + }, + }, + }) + .catch(() => { + // noop + }) + } + useEffect(() => { if (userProfile) { setDocumentNotifications( diff --git a/apps/native/app/src/stores/preferences-store.ts b/apps/native/app/src/stores/preferences-store.ts index a456d69faec2..c44a4d945850 100644 --- a/apps/native/app/src/stores/preferences-store.ts +++ b/apps/native/app/src/stores/preferences-store.ts @@ -5,6 +5,12 @@ import { persist } from 'zustand/middleware' import create, { State } from 'zustand/vanilla' import { getDefaultOptions } from '../utils/get-default-options' import { getThemeWithPreferences } from '../utils/get-theme-with-preferences' +import { getApolloClientAsync } from '../graphql/client' +import { + GetProfileDocument, + GetProfileQuery, + GetProfileQueryVariables, +} from '../graphql/types/schema' export type Locale = 'en-US' | 'is-IS' | 'en-IS' | 'is-US' export type ThemeMode = 'dark' | 'light' | 'efficient' @@ -29,6 +35,7 @@ export interface PreferencesStore extends State { appearanceMode: AppearanceMode appLockTimeout: number setLocale(locale: Locale): void + getAndSetLocale(): void setAppearanceMode(appearanceMode: AppearanceMode): void setUseBiometrics(useBiometrics: boolean): void dismiss(key: string, value?: boolean): void @@ -61,6 +68,24 @@ export const preferencesStore = create( persist( (set, get) => ({ ...(defaultPreferences as PreferencesStore), + async getAndSetLocale() { + const client = await getApolloClientAsync() + + try { + const res = await client.query< + GetProfileQuery, + GetProfileQueryVariables + >({ + query: GetProfileDocument, + }) + + const locale = res.data?.getUserProfile?.locale + const appLocale = locale === 'en' ? 'en-US' : 'is-IS' + set({ locale: appLocale }) + } catch (err) { + // noop + } + }, setLocale(locale: Locale) { if (!availableLocales.includes(locale)) { throw new Error('Not supported locale') From c894fc1d1dd99bd41ed3d62bf8b2b71e03147889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3n=20Bjarni=20=C3=93lafsson?= <92530555+jonbjarnio@users.noreply.github.com> Date: Fri, 6 Sep 2024 19:44:32 +0000 Subject: [PATCH 24/34] feat(ojoi): OJOI application improvements/refactor (#15663) * WIP: updating data schema, adding partial validation to fields. * Removed all old code. * new ojoi select controller that updates the application on change * useTypes hook * Html controller now ready. * WIP: signatures refactor * Refactored signatures * Regular signature are now ready,. * Adding debounced method to use application hoook * Signature html, markup and preview now ready. * Signature ready. * WIP updating attachments inputs * WIP: uploading files * WIP: upload files hook * Updating clients and fetching files currently attached to the applicaiton * Now users can delete files interacting with the file input * File upload hook ready * HTML controller now saves the values on the application, and preview screen ready * Original attachment screen ready * Publishing and summary screen now ready. * Removed unused types * Moved query logic to hooks * Disable the submit button if validation fails * Schema validation with error handling now ready * Finishing up error handling for preview screen * Refactored advert modal, added key to html editor to force rerender when users selected advert to use. * Fixed bug where warning was shownig when it was not supposed to * Fixing comments * Restoring validator file * Remove unnecessary graphql type definition for string and boolean values. * Added logggin where needed * Removed unsued queries and added larger pagesize for types query * Removed unused method from resolver --- .../src/lib/mappers.ts | 44 + .../src/lib/ojoiApplication.resolver.ts | 73 +- .../src/lib/ojoiApplication.service.ts | 109 ++- .../models/addApplicationAttachment.input.ts | 28 + .../addApplicationAttachment.response.ts | 9 + .../deleteApplicationAttachment.input.ts | 12 + .../models/getApplicationAttachment.input.ts | 10 + .../getApplicationAttachments.response.ts | 33 + .../src/models/getComments.input.ts | 2 +- .../src/models/getComments.response.ts | 14 +- .../src/models/getPdfResponse.ts | 7 - .../src/models/getPdfUrlResponse.ts | 2 +- .../src/models/getPresignedUrl.input.ts | 16 + .../src/models/getPresignedUrl.response.ts | 7 + .../src/models/postApplication.input.ts | 2 +- .../src/models/postComment.input.ts | 2 +- .../src/models/postComment.response.ts | 5 +- .../src/lib/models/advert.input.ts | 3 + .../src/components/comments/AddComment.tsx | 66 +- .../src/components/comments/CommentList.tsx | 24 +- .../communicationChannels/AddChannel.tsx | 61 +- .../communicationChannels/Channel.tsx | 31 - .../communicationChannels/ChannelList.tsx | 67 +- .../src/components/form/FormGroup.tsx | 27 +- .../htmlEditor/templates/signatures.ts | 135 --- .../components/input/OJOIDateController.tsx | 100 +++ .../components/input/OJOIHtmlController.tsx | 72 ++ .../components/input/OJOIInputController.tsx | 82 ++ .../components/input/OJOISelectController.tsx | 84 ++ .../src/components/property/Property.tsx | 34 +- .../signatures/AddCommitteeMember.tsx | 77 ++ .../signatures/AddRegularMember.tsx | 92 ++ .../signatures/AddRegularSignature.tsx | 81 ++ .../src/components/signatures/Additional.tsx | 31 +- .../src/components/signatures/Chairman.tsx | 106 +++ .../src/components/signatures/Committee.tsx | 313 ++----- .../components/signatures/CommitteeMember.tsx | 123 +++ .../src/components/signatures/Institution.tsx | 163 ++++ .../src/components/signatures/Member.tsx | 28 + .../src/components/signatures/Regular.tsx | 340 ++------ .../components/signatures/RegularMember.tsx | 153 ++++ .../signatures/RemoveComitteeMember.tsx | 55 ++ .../signatures/RemoveRegularMember.tsx | 65 ++ .../signatures/RemoveRegularSignature.tsx | 53 ++ .../components/signatures/Signatures.css.ts | 73 +- .../src/fields/Advert.tsx | 326 ++------ .../src/fields/AdvertModal.tsx | 222 +++-- .../src/fields/Attachments.tsx | 84 +- .../src/fields/Comments.tsx | 94 ++- .../src/fields/CommunicationChannels.tsx | 37 + .../src/fields/Message.tsx | 18 + .../src/fields/Original.tsx | 48 +- .../src/fields/Preview.tsx | 159 ++-- .../src/fields/Publishing.tsx | 372 +++------ .../src/fields/Signatures.tsx | 203 +---- .../src/fields/Summary.tsx | 248 ++++-- .../src/graphql/queries.ts | 81 +- .../src/hooks/useAdvert.ts | 37 + .../src/hooks/useAdverts.ts | 67 ++ .../src/hooks/useCategories.ts | 26 + .../src/hooks/useComments.ts | 60 +- .../src/hooks/useDepartment.ts | 30 + .../src/hooks/useDepartments.ts | 26 + .../src/hooks/useFileUpload.ts | 227 +++++ .../src/hooks/usePrice.ts | 24 + .../src/hooks/useType.ts | 30 + .../src/hooks/useTypes.ts | 45 + .../src/hooks/useUpdateApplication.ts | 79 ++ .../src/lib/OJOIApplication.ts | 16 +- .../src/lib/constants.ts | 95 +-- .../src/lib/dataSchema.ts | 487 +++++++---- .../src/lib/messages/advert.ts | 8 +- .../src/lib/messages/comments.ts | 32 +- .../src/lib/messages/error.ts | 141 ++++ .../src/lib/messages/index.ts | 2 + .../src/lib/messages/preview.ts | 7 + .../src/lib/messages/publishing.ts | 5 + .../src/lib/messages/signatures.ts | 7 +- .../src/lib/messages/summary.ts | 2 +- .../src/lib/types.ts | 126 +-- .../src/lib/utils.ts | 351 +++++--- .../src/screens/AdvertScreen.tsx | 20 +- .../src/screens/PublishingScreen.tsx | 4 + .../src/screens/RequirementsScreen.tsx | 92 +- .../application/src/clientConfig.json | 785 +++++++++++++++++- .../src/lib/ojoiApplicationClient.service.ts | 48 +- 86 files changed, 5288 insertions(+), 2397 deletions(-) create mode 100644 libs/api/domains/official-journal-of-iceland-application/src/lib/mappers.ts create mode 100644 libs/api/domains/official-journal-of-iceland-application/src/models/addApplicationAttachment.input.ts create mode 100644 libs/api/domains/official-journal-of-iceland-application/src/models/addApplicationAttachment.response.ts create mode 100644 libs/api/domains/official-journal-of-iceland-application/src/models/deleteApplicationAttachment.input.ts create mode 100644 libs/api/domains/official-journal-of-iceland-application/src/models/getApplicationAttachment.input.ts create mode 100644 libs/api/domains/official-journal-of-iceland-application/src/models/getApplicationAttachments.response.ts delete mode 100644 libs/api/domains/official-journal-of-iceland-application/src/models/getPdfResponse.ts create mode 100644 libs/api/domains/official-journal-of-iceland-application/src/models/getPresignedUrl.input.ts create mode 100644 libs/api/domains/official-journal-of-iceland-application/src/models/getPresignedUrl.response.ts delete mode 100644 libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/Channel.tsx delete mode 100644 libs/application/templates/official-journal-of-iceland/src/components/htmlEditor/templates/signatures.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/input/OJOIDateController.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/input/OJOIHtmlController.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/AddCommitteeMember.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/AddRegularMember.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/AddRegularSignature.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/Chairman.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/CommitteeMember.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/Institution.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/Member.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/RegularMember.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/RemoveComitteeMember.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/RemoveRegularMember.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/components/signatures/RemoveRegularSignature.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/fields/CommunicationChannels.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/fields/Message.tsx create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/useAdvert.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/useAdverts.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/useCategories.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/useDepartment.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/useDepartments.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/useFileUpload.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/usePrice.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/useType.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts create mode 100644 libs/application/templates/official-journal-of-iceland/src/hooks/useUpdateApplication.ts diff --git a/libs/api/domains/official-journal-of-iceland-application/src/lib/mappers.ts b/libs/api/domains/official-journal-of-iceland-application/src/lib/mappers.ts new file mode 100644 index 000000000000..e88a77424d14 --- /dev/null +++ b/libs/api/domains/official-journal-of-iceland-application/src/lib/mappers.ts @@ -0,0 +1,44 @@ +import { + AddApplicationAttachmentTypeEnum, + GetApplicationAttachmentsTypeEnum, + GetPresignedUrlTypeEnum, +} from '@island.is/clients/official-journal-of-iceland/application' + +export const mapAttachmentType = ( + val: any, +): AddApplicationAttachmentTypeEnum => { + switch (val) { + case AddApplicationAttachmentTypeEnum.Frumrit: + case GetPresignedUrlTypeEnum.Frumrit: + return AddApplicationAttachmentTypeEnum.Frumrit + case AddApplicationAttachmentTypeEnum.Fylgiskjol: + case GetPresignedUrlTypeEnum.Fylgiskjol: + return AddApplicationAttachmentTypeEnum.Fylgiskjol + default: + return AddApplicationAttachmentTypeEnum.Fylgiskjol + } +} + +export const mapPresignedUrlType = (val: any): GetPresignedUrlTypeEnum => { + switch (val) { + case GetPresignedUrlTypeEnum.Frumrit: + return GetPresignedUrlTypeEnum.Frumrit + case GetPresignedUrlTypeEnum.Fylgiskjol: + return GetPresignedUrlTypeEnum.Fylgiskjol + default: + return GetPresignedUrlTypeEnum.Fylgiskjol + } +} + +export const mapGetAttachmentType = ( + val: any, +): GetApplicationAttachmentsTypeEnum => { + switch (val) { + case GetApplicationAttachmentsTypeEnum.Frumrit: + return GetApplicationAttachmentsTypeEnum.Frumrit + case GetApplicationAttachmentsTypeEnum.Fylgiskjol: + return GetApplicationAttachmentsTypeEnum.Fylgiskjol + default: + return GetApplicationAttachmentsTypeEnum.Fylgiskjol + } +} diff --git a/libs/api/domains/official-journal-of-iceland-application/src/lib/ojoiApplication.resolver.ts b/libs/api/domains/official-journal-of-iceland-application/src/lib/ojoiApplication.resolver.ts index 4b681ff24ef4..8d7c5cee47f7 100644 --- a/libs/api/domains/official-journal-of-iceland-application/src/lib/ojoiApplication.resolver.ts +++ b/libs/api/domains/official-journal-of-iceland-application/src/lib/ojoiApplication.resolver.ts @@ -1,4 +1,4 @@ -import { Args, Query, Resolver } from '@nestjs/graphql' +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql' import { IdsUserGuard, Scopes, ScopesGuard } from '@island.is/auth-nest-tools' import { ApiScope } from '@island.is/auth/scopes' import { FeatureFlag, Features } from '@island.is/nest/feature-flags' @@ -11,7 +11,13 @@ import { PostApplicationInput } from '../models/postApplication.input' import { UseGuards } from '@nestjs/common' import { CaseGetPriceResponse } from '../models/getPrice.response' import { GetPdfUrlResponse } from '../models/getPdfUrlResponse' -import { GetPdfResponse } from '../models/getPdfResponse' +import { GetPresignedUrlInput } from '../models/getPresignedUrl.input' +import { GetPresignedUrlResponse } from '../models/getPresignedUrl.response' +import { AddApplicationAttachmentResponse } from '../models/addApplicationAttachment.response' +import { AddApplicationAttachmentInput } from '../models/addApplicationAttachment.input' +import { GetApplicationAttachmentInput } from '../models/getApplicationAttachment.input' +import { GetApplicationAttachmentsResponse } from '../models/getApplicationAttachments.response' +import { DeleteApplicationAttachmentInput } from '../models/deleteApplicationAttachment.input' @Scopes(ApiScope.internal) @UseGuards(IdsUserGuard, ScopesGuard) @@ -25,42 +31,75 @@ export class OfficialJournalOfIcelandApplicationResolver { @Query(() => GetCommentsResponse, { name: 'officialJournalOfIcelandApplicationGetComments', }) - async getComments(@Args('input') input: GetCommentsInput) { - return await this.ojoiApplicationService.getComments(input) + getComments(@Args('input') input: GetCommentsInput) { + return this.ojoiApplicationService.getComments(input) } - @Query(() => PostCommentResponse, { + @Mutation(() => PostCommentResponse, { name: 'officialJournalOfIcelandApplicationPostComment', }) - async postComment(@Args('input') input: PostCommentInput) { - return await this.ojoiApplicationService.postComment(input) + postComment(@Args('input') input: PostCommentInput) { + return this.ojoiApplicationService.postComment(input) } @Query(() => Boolean, { name: 'officialJournalOfIcelandApplicationPostApplication', }) - async postApplication(@Args('input') input: PostApplicationInput) { - return await this.ojoiApplicationService.postApplication(input) + postApplication(@Args('input') input: PostApplicationInput) { + return this.ojoiApplicationService.postApplication(input) } @Query(() => CaseGetPriceResponse, { name: 'officialJournalOfIcelandApplicationGetPrice', }) - async getPrice(@Args('id') id: string) { - return await this.ojoiApplicationService.getPrice(id) + getPrice(@Args('id') id: string) { + return this.ojoiApplicationService.getPrice(id) } @Query(() => GetPdfUrlResponse, { name: 'officialJournalOfIcelandApplicationGetPdfUrl', }) - async getPdfUrl(@Args('id') id: string) { - return await this.ojoiApplicationService.getPdfUrl(id) + getPdfUrl(@Args('id') id: string) { + return this.ojoiApplicationService.getPdfUrl(id) } - @Query(() => GetPdfResponse, { - name: 'officialJournalOfIcelandApplicationGetPdf', + @Mutation(() => GetPresignedUrlResponse, { + name: 'officialJournalOfIcelandApplicationGetPresignedUrl', }) - async getPdf(@Args('id') id: string) { - return (await this.ojoiApplicationService.getPdf(id)).toString('base64') + getPresignedUrl( + @Args('input', { type: () => GetPresignedUrlInput }) + input: GetPresignedUrlInput, + ) { + return this.ojoiApplicationService.getPresignedUrl(input) + } + + @Mutation(() => AddApplicationAttachmentResponse, { + name: 'officialJournalOfIcelandApplicationAddAttachment', + }) + addAttachment( + @Args('input', { type: () => AddApplicationAttachmentInput }) + input: AddApplicationAttachmentInput, + ) { + return this.ojoiApplicationService.addApplicationAttachment(input) + } + + @Query(() => GetApplicationAttachmentsResponse, { + name: 'officialJournalOfIcelandApplicationGetAttachments', + }) + getAttachments( + @Args('input', { type: () => GetApplicationAttachmentInput }) + input: AddApplicationAttachmentInput, + ) { + return this.ojoiApplicationService.getApplicationAttachments(input) + } + + @Mutation(() => AddApplicationAttachmentResponse, { + name: 'officialJournalOfIcelandApplicationDeleteAttachment', + }) + deleteAttachment( + @Args('input', { type: () => DeleteApplicationAttachmentInput }) + input: DeleteApplicationAttachmentInput, + ) { + return this.ojoiApplicationService.deleteApplicationAttachment(input) } } diff --git a/libs/api/domains/official-journal-of-iceland-application/src/lib/ojoiApplication.service.ts b/libs/api/domains/official-journal-of-iceland-application/src/lib/ojoiApplication.service.ts index a810f69c9345..e8b2adc91172 100644 --- a/libs/api/domains/official-journal-of-iceland-application/src/lib/ojoiApplication.service.ts +++ b/libs/api/domains/official-journal-of-iceland-application/src/lib/ojoiApplication.service.ts @@ -1,12 +1,29 @@ import { OfficialJournalOfIcelandApplicationClientService } from '@island.is/clients/official-journal-of-iceland/application' -import { Injectable } from '@nestjs/common' +import { Inject, Injectable } from '@nestjs/common' import { PostCommentInput } from '../models/postComment.input' import { PostApplicationInput } from '../models/postApplication.input' import { GetCommentsInput } from '../models/getComments.input' +import { GetPresignedUrlInput } from '../models/getPresignedUrl.input' +import { GetPresignedUrlResponse } from '../models/getPresignedUrl.response' +import { AddApplicationAttachmentInput } from '../models/addApplicationAttachment.input' +import { + mapAttachmentType, + mapGetAttachmentType, + mapPresignedUrlType, +} from './mappers' +import { AddApplicationAttachmentResponse } from '../models/addApplicationAttachment.response' +import { GetApplicationAttachmentInput } from '../models/getApplicationAttachment.input' +import { DeleteApplicationAttachmentInput } from '../models/deleteApplicationAttachment.input' +import { LOGGER_PROVIDER } from '@island.is/logging' +import type { Logger } from '@island.is/logging' + +const LOG_CATEGORY = 'official-journal-of-iceland-application' @Injectable() export class OfficialJournalOfIcelandApplicationService { constructor( + @Inject(LOGGER_PROVIDER) + private logger: Logger, private readonly ojoiApplicationService: OfficialJournalOfIcelandApplicationClientService, ) {} @@ -15,10 +32,16 @@ export class OfficialJournalOfIcelandApplicationService { } async postComment(input: PostCommentInput) { - return this.ojoiApplicationService.postComment({ + const success = this.ojoiApplicationService.postComment({ id: input.id, - // comment: input.comment, + postApplicationComment: { + comment: input.comment, + }, }) + + return { + success, + } } async getPdfUrl(id: string) { @@ -27,12 +50,6 @@ export class OfficialJournalOfIcelandApplicationService { }) } - async getPdf(id: string) { - return this.ojoiApplicationService.getPdf({ - id, - }) - } - async postApplication(input: PostApplicationInput): Promise { return this.ojoiApplicationService.postApplication(input) } @@ -42,4 +59,78 @@ export class OfficialJournalOfIcelandApplicationService { id, }) } + + async getPresignedUrl( + input: GetPresignedUrlInput, + ): Promise { + const attachmentType = mapPresignedUrlType(input.attachmentType) + + return this.ojoiApplicationService.getPresignedUrl({ + id: input.applicationId, + type: attachmentType, + getPresignedUrlBody: { + fileName: input.fileName, + fileType: input.fileType, + }, + }) + } + + async addApplicationAttachment( + input: AddApplicationAttachmentInput, + ): Promise { + try { + const attachmentType = mapAttachmentType(input.attachmentType) + + this.ojoiApplicationService.addApplicationAttachment({ + id: input.applicationId, + type: attachmentType, + postApplicationAttachmentBody: { + fileName: input.fileName, + originalFileName: input.originalFileName, + fileFormat: input.fileFormat, + fileExtension: input.fileExtension, + fileLocation: input.fileLocation, + fileSize: input.fileSize, + }, + }) + + return { + success: true, + } + } catch (error) { + this.logger.error('Failed to add application attachment', { + category: LOG_CATEGORY, + applicationId: input.applicationId, + error: error, + }) + return { + success: false, + } + } + } + + async getApplicationAttachments(input: GetApplicationAttachmentInput) { + return this.ojoiApplicationService.getApplicationAttachments({ + id: input.applicationId, + type: mapGetAttachmentType(input.attachmentType), + }) + } + + async deleteApplicationAttachment(input: DeleteApplicationAttachmentInput) { + try { + await this.ojoiApplicationService.deleteApplicationAttachment({ + id: input.applicationId, + key: input.key, + }) + + return { success: true } + } catch (error) { + this.logger.error('Failed to delete application attachment', { + category: LOG_CATEGORY, + applicationId: input.applicationId, + error: error, + }) + return { success: false } + } + } } diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/addApplicationAttachment.input.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/addApplicationAttachment.input.ts new file mode 100644 index 000000000000..f15358a3cb60 --- /dev/null +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/addApplicationAttachment.input.ts @@ -0,0 +1,28 @@ +import { InputType, Field, Int } from '@nestjs/graphql' + +@InputType('OfficialJournalOfIcelandApplicationAddApplicationAttachmentInput') +export class AddApplicationAttachmentInput { + @Field() + applicationId!: string + + @Field() + attachmentType!: string + + @Field() + fileName!: string + + @Field() + originalFileName!: string + + @Field() + fileFormat!: string + + @Field() + fileExtension!: string + + @Field() + fileLocation!: string + + @Field(() => Int) + fileSize!: number +} diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/addApplicationAttachment.response.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/addApplicationAttachment.response.ts new file mode 100644 index 000000000000..83fd6cac76cc --- /dev/null +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/addApplicationAttachment.response.ts @@ -0,0 +1,9 @@ +import { Field, ObjectType } from '@nestjs/graphql' + +@ObjectType( + 'OfficialJournalOfIcelandApplicationAddApplicationAttachmentResponse', +) +export class AddApplicationAttachmentResponse { + @Field() + success!: boolean +} diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/deleteApplicationAttachment.input.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/deleteApplicationAttachment.input.ts new file mode 100644 index 000000000000..0c3ae8857689 --- /dev/null +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/deleteApplicationAttachment.input.ts @@ -0,0 +1,12 @@ +import { Field, InputType } from '@nestjs/graphql' + +@InputType( + 'OfficialJournalOfIcelandApplicationDeleteApplicationAttachmentInput', +) +export class DeleteApplicationAttachmentInput { + @Field() + applicationId!: string + + @Field() + key!: string +} diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/getApplicationAttachment.input.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/getApplicationAttachment.input.ts new file mode 100644 index 000000000000..3a19532f5fbd --- /dev/null +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/getApplicationAttachment.input.ts @@ -0,0 +1,10 @@ +import { InputType, Field } from '@nestjs/graphql' + +@InputType('OfficialJournalOfIcelandApplicationGetApplicationAttachmentInput') +export class GetApplicationAttachmentInput { + @Field() + applicationId!: string + + @Field() + attachmentType!: string +} diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/getApplicationAttachments.response.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/getApplicationAttachments.response.ts new file mode 100644 index 000000000000..57938abbd749 --- /dev/null +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/getApplicationAttachments.response.ts @@ -0,0 +1,33 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql' + +@ObjectType('OfficialJournalOfIcelandApplicationGetApplicationAttachments') +export class GetApplicationAttachmentsResponse { + @Field(() => [GetApplicationAttachmentResponse]) + attachments!: GetApplicationAttachmentResponse[] +} + +@ObjectType( + 'OfficialJournalOfIcelandApplicationGetApplicationAttachmentResponse', +) +export class GetApplicationAttachmentResponse { + @Field() + id!: string + + @Field() + fileName!: string + + @Field() + originalFileName!: string + + @Field() + fileFormat!: string + + @Field() + fileExtension!: string + + @Field() + fileLocation!: string + + @Field(() => Int) + fileSize!: number +} diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/getComments.input.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/getComments.input.ts index dd550de8404d..fdbe6f6a03ab 100644 --- a/libs/api/domains/official-journal-of-iceland-application/src/models/getComments.input.ts +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/getComments.input.ts @@ -2,6 +2,6 @@ import { Field, InputType } from '@nestjs/graphql' @InputType('OfficialJournalOfIcelandApplicationGetCommentsInput') export class GetCommentsInput { - @Field(() => String) + @Field() id!: string } diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/getComments.response.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/getComments.response.ts index a5203aa6fcab..cf472295d940 100644 --- a/libs/api/domains/official-journal-of-iceland-application/src/models/getComments.response.ts +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/getComments.response.ts @@ -8,7 +8,7 @@ export class CaseCommentTask { @Field(() => String, { nullable: true }) to!: string | null - @Field(() => String) + @Field() title!: string @Field(() => String, { nullable: true }) comment!: string | null @@ -16,22 +16,22 @@ export class CaseCommentTask { @ObjectType('OfficialJournalOfIcelandApplicationComment') export class CaseComment { - @Field(() => String) + @Field() id!: string - @Field(() => String) + @Field() createdAt!: string - @Field(() => Boolean) + @Field() internal!: boolean - @Field(() => String) + @Field() type!: string - @Field(() => String) + @Field() caseStatus!: string - @Field(() => String) + @Field() state!: string @Field(() => CaseCommentTask) diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/getPdfResponse.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/getPdfResponse.ts deleted file mode 100644 index 3f9ecbdb49b5..000000000000 --- a/libs/api/domains/official-journal-of-iceland-application/src/models/getPdfResponse.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Field, ObjectType } from '@nestjs/graphql' - -@ObjectType('OfficialJournalOfIcelandApplicationGetPdfResponse') -export class GetPdfResponse { - @Field(() => String) - buffer!: Buffer -} diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/getPdfUrlResponse.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/getPdfUrlResponse.ts index 5e3062492d49..4ceb94e91350 100644 --- a/libs/api/domains/official-journal-of-iceland-application/src/models/getPdfUrlResponse.ts +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/getPdfUrlResponse.ts @@ -2,6 +2,6 @@ import { Field, ObjectType } from '@nestjs/graphql' @ObjectType('OfficialJournalOfIcelandApplicationGetPdfUrlResponse') export class GetPdfUrlResponse { - @Field(() => String) + @Field() url!: string } diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/getPresignedUrl.input.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/getPresignedUrl.input.ts new file mode 100644 index 000000000000..b944f64608cf --- /dev/null +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/getPresignedUrl.input.ts @@ -0,0 +1,16 @@ +import { Field, InputType } from '@nestjs/graphql' + +@InputType('OfficialJournalOfIcelandApplicationGetPresignedUrlInput') +export class GetPresignedUrlInput { + @Field() + applicationId!: string + + @Field() + fileName!: string + + @Field() + fileType!: string + + @Field() + attachmentType!: string +} diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/getPresignedUrl.response.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/getPresignedUrl.response.ts new file mode 100644 index 000000000000..e2c168dd4132 --- /dev/null +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/getPresignedUrl.response.ts @@ -0,0 +1,7 @@ +import { Field, ObjectType } from '@nestjs/graphql' + +@ObjectType('OfficialJournalOfIcelandApplicationGetPresignedUrlResponse') +export class GetPresignedUrlResponse { + @Field() + url!: string +} diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/postApplication.input.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/postApplication.input.ts index b89efcef4356..6c673065c049 100644 --- a/libs/api/domains/official-journal-of-iceland-application/src/models/postApplication.input.ts +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/postApplication.input.ts @@ -4,6 +4,6 @@ import { Field, InputType } from '@nestjs/graphql' description: 'Submit application input', }) export class PostApplicationInput { - @Field(() => String) + @Field() id!: string } diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/postComment.input.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/postComment.input.ts index de02467dcf61..8a9f6929199d 100644 --- a/libs/api/domains/official-journal-of-iceland-application/src/models/postComment.input.ts +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/postComment.input.ts @@ -5,6 +5,6 @@ export class PostCommentInput { @Field(() => String, { description: 'Application ID' }) id!: string - @Field(() => String) + @Field() comment!: string } diff --git a/libs/api/domains/official-journal-of-iceland-application/src/models/postComment.response.ts b/libs/api/domains/official-journal-of-iceland-application/src/models/postComment.response.ts index 4c83110bb5d3..eddcf30b8b83 100644 --- a/libs/api/domains/official-journal-of-iceland-application/src/models/postComment.response.ts +++ b/libs/api/domains/official-journal-of-iceland-application/src/models/postComment.response.ts @@ -1,8 +1,7 @@ import { Field, ObjectType } from '@nestjs/graphql' -import { CaseComment } from './getComments.response' @ObjectType('OfficialJournalOfIcelandApplicationPostCommentResponse') export class PostCommentResponse { - @Field(() => CaseComment) - comment!: CaseComment + @Field() + success!: boolean } diff --git a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts index ac167ed5ffcb..14d782567e88 100644 --- a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts +++ b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts @@ -45,6 +45,9 @@ export class TypeQueryParams { @Field(() => Number, { nullable: true }) page?: number + + @Field(() => Number, { nullable: true }) + pageSize?: number } @InputType('OfficialJournalOfIcelandAdvertSingleParams') diff --git a/libs/application/templates/official-journal-of-iceland/src/components/comments/AddComment.tsx b/libs/application/templates/official-journal-of-iceland/src/components/comments/AddComment.tsx index f173d7010eda..9676ed6fb1aa 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/comments/AddComment.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/comments/AddComment.tsx @@ -1,60 +1,60 @@ -import { Box, Button, Input } from '@island.is/island-ui/core' +import { + AlertMessage, + Box, + Button, + Input, + Stack, +} from '@island.is/island-ui/core' import { useState } from 'react' import { comments } from '../../lib/messages/comments' import { useLocale } from '@island.is/localization' +import { useComments } from '../../hooks/useComments' type Props = { - onCommentChange?: (comment: string) => void - onAddComment?: (comment: string) => void + applicationId: string } -export const AddComment = ({ onCommentChange, onAddComment }: Props) => { - const { formatMessage } = useLocale() +export const AddComment = ({ applicationId }: Props) => { + const { formatMessage: f } = useLocale() + const { addComment, addCommentLoading, addCommentSuccess } = useComments({ + applicationId, + }) const [comment, setComment] = useState('') - const handleChange = ( - event: React.ChangeEvent, - ) => { - setComment(event.target.value) - if (onCommentChange) { - onCommentChange(event.target.value) - } - } - - const handleAdd = () => { + const onAddComment = () => { + addComment({ + comment: comment, + }) setComment('') - if (onAddComment) { - onAddComment(comment) - } } return ( - + + {addCommentSuccess === false && ( + + )} setComment(e.target.value)} /> - - + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/components/comments/CommentList.tsx b/libs/application/templates/official-journal-of-iceland/src/components/comments/CommentList.tsx index 8e8a85500685..7da90fa08760 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/comments/CommentList.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/comments/CommentList.tsx @@ -1,23 +1,29 @@ +import { SkeletonLoader } from '@island.is/island-ui/core' import type { Props as CommentProps } from './Comment' -import { Text } from '@island.is/island-ui/core' import { Comment } from './Comment' import * as styles from './Comments.css' -import { useLocale } from '@island.is/localization' -import { comments as messages } from '../../lib/messages/comments' +import { OJOI_INPUT_HEIGHT } from '../../lib/constants' type Props = { - comments: CommentProps[] + comments?: CommentProps[] + loading?: boolean } -export const CommentsList = ({ comments }: Props) => { - const { formatMessage: f } = useLocale() - if (!comments.length) { - return {f(messages.errors.emptyComments)} +export const CommentsList = ({ comments, loading }: Props) => { + if (loading) { + return ( + + ) } return (
    - {comments.map((comment, index) => ( + {comments?.map((comment, index) => ( ))}
diff --git a/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/AddChannel.tsx b/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/AddChannel.tsx index 09073114c40e..5f5c7ecb3ebb 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/AddChannel.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/AddChannel.tsx @@ -1,22 +1,53 @@ import { Box, Button, Input } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' -import { useRef, useState } from 'react' +import { useEffect, useState } from 'react' import { general, publishing } from '../../lib/messages' import * as styles from './AddChannel.css' -import { Channel } from './Channel' import { FormGroup } from '../form/FormGroup' +import { useApplication } from '../../hooks/useUpdateApplication' +import set from 'lodash/set' +import { InputFields } from '../../lib/types' + type Props = { - onAdd: () => void - state: Channel - setState: React.Dispatch> + applicationId: string + defaultEmail?: string + defaultPhone?: string + defaultVisible?: boolean } -export const AddChannel = ({ onAdd, state, setState }: Props) => { +export const AddChannel = ({ + applicationId, + defaultEmail, + defaultPhone, + defaultVisible, +}: Props) => { + const { application, updateApplication } = useApplication({ + applicationId, + }) const { formatMessage: f } = useLocale() - const phoneRef = useRef(null) + const [email, setEmail] = useState('') + const [phone, setPhone] = useState('') + const [isVisible, setIsVisible] = useState(defaultVisible ?? false) + + useEffect(() => { + setEmail(defaultEmail ?? email) + setPhone(defaultPhone ?? phone) + setIsVisible(defaultVisible ?? false) + }, [defaultEmail, defaultPhone, defaultVisible]) + + const onAddChannel = () => { + const currentAnswers = structuredClone(application.answers) + const currentChannels = currentAnswers.advert?.channels ?? [] + const updatedAnswers = set(currentAnswers, InputFields.advert.channels, [ + ...currentChannels, + { email, phone }, + ]) - const [isVisible, setIsVisible] = useState(false) + updateApplication(updatedAnswers) + setEmail('') + setPhone('') + } return ( @@ -32,19 +63,18 @@ export const AddChannel = ({ onAdd, state, setState }: Props) => { size="xs" name="email" type="email" - value={state.email} + value={email} label={f(general.email)} - onChange={(e) => setState({ ...state, email: e.target.value })} + onChange={(e) => setEmail(e.target.value)} /> setState({ ...state, phone: e.target.value })} + onChange={(e) => setPhone(e.target.value)} /> @@ -54,12 +84,13 @@ export const AddChannel = ({ onAdd, state, setState }: Props) => { variant="ghost" onClick={() => { setIsVisible(!isVisible) - setState({ email: '', phone: '' }) + setEmail('') + setPhone('') }} > {f(general.cancel)} - diff --git a/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/Channel.tsx b/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/Channel.tsx deleted file mode 100644 index f87569b3e866..000000000000 --- a/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/Channel.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Icon, Table as T } from '@island.is/island-ui/core' - -export type Channel = { - email: string - phone: string -} - -type Props = { - channel: Channel - onEditChannel: (channel: Channel) => void - onRemoveChannel: (channel: Channel) => void -} - -export const Channel = ({ channel, onEditChannel, onRemoveChannel }: Props) => { - return ( - - {channel.email} - {channel.phone} - - - - - - - - ) -} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/ChannelList.tsx b/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/ChannelList.tsx index 09417e1c5519..76c5be4ddf79 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/ChannelList.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/communicationChannels/ChannelList.tsx @@ -1,21 +1,40 @@ -import { Table as T } from '@island.is/island-ui/core' -import { Channel } from './Channel' +import { Icon, Table as T } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' import { general } from '../../lib/messages' +import { useApplication } from '../../hooks/useUpdateApplication' +import { InputFields } from '../../lib/types' +import set from 'lodash/set' type Props = { - channels: Channel[] - onEditChannel: (channel: Channel) => void - onRemoveChannel: (channel: Channel) => void + applicationId: string + onEditChannel: (email?: string, phone?: string) => void } -export const ChannelList = ({ - channels, - onEditChannel, - onRemoveChannel, -}: Props) => { +export const ChannelList = ({ applicationId, onEditChannel }: Props) => { const { formatMessage } = useLocale() - if (channels.length === 0) return null + + const { application, updateApplication } = useApplication({ + applicationId, + }) + + const channels = application.answers.advert?.channels || [] + + const onRemoveChannel = (email?: string) => { + const currentAnswers = structuredClone(application.answers) + const currentChannels = currentAnswers.advert?.channels ?? [] + + const updatedAnswers = set( + currentAnswers, + InputFields.advert.channels, + currentChannels.filter((channel) => channel.email !== email), + ) + + updateApplication(updatedAnswers) + } + + if (channels.length === 0) { + return null + } return ( @@ -29,12 +48,26 @@ export const ChannelList = ({ {channels.map((channel, i) => ( - + + {channel.email} + {channel.phone} + + + + + + + ))} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/form/FormGroup.tsx b/libs/application/templates/official-journal-of-iceland/src/components/form/FormGroup.tsx index 7925a2fea463..aaff6f3fcf8d 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/form/FormGroup.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/form/FormGroup.tsx @@ -1,22 +1,37 @@ import { Box, Text } from '@island.is/island-ui/core' import * as styles from './FormGroup.css' +import { MessageDescriptor } from 'react-intl' +import { useLocale } from '@island.is/localization' type Props = { - title?: string - intro?: string + title?: string | MessageDescriptor + intro?: string | MessageDescriptor children?: React.ReactNode } export const FormGroup = ({ title, intro, children }: Props) => { + const { formatMessage: f } = useLocale() + + const titleText = title + ? typeof title !== 'string' + ? f(title) + : title + : undefined + const introText = intro + ? typeof intro !== 'string' + ? f(intro) + : intro + : undefined + return ( {(title || intro) && ( - {title && ( - - {title} + {titleText && ( + + {titleText} )} - {intro && {intro}} + {introText && {introText}} )} {children} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/htmlEditor/templates/signatures.ts b/libs/application/templates/official-journal-of-iceland/src/components/htmlEditor/templates/signatures.ts deleted file mode 100644 index b964b5c5dfe7..000000000000 --- a/libs/application/templates/official-journal-of-iceland/src/components/htmlEditor/templates/signatures.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { HTMLText } from '@island.is/regulations-tools/types' -import { - CommitteeSignatureState, - RegularSignatureState, -} from '../../../lib/types' -import is from 'date-fns/locale/is' -import en from 'date-fns/locale/en-US' -import format from 'date-fns/format' - -type RegularSignatureTemplateParams = { - signatureGroups?: RegularSignatureState - additionalSignature?: string - locale?: 'is' | 'en' -} - -type CommitteeSignatureTemplateParams = { - signature?: CommitteeSignatureState - additionalSignature?: string - locale?: 'is' | 'en' -} - -const signatureTemplate = ( - name?: string, - after?: string, - above?: string, - below?: string, -) => { - if (!name) return '' - return ` -
- ${above ? `

${above}

` : ''} -
-

${name} - ${after ? `${after}` : ''} -

- ${below ? `

${below}

` : ''} -
-
- ` -} - -const additionalTemplate = (additional?: string) => { - if (!additional) return '' - - return `

${additional}

` -} - -const titleTemplate = (title?: string, date?: string, locale = 'is') => { - if (!title && !date) return '' - - return ` -

${title}${title && date ? ', ' : ''}${ - date - ? format(new Date(date), 'd. MMMM yyyy', { - locale: locale === 'is' ? is : en, - }) - : '' - }

` -} - -export const regularSignatureTemplate = ({ - signatureGroups, - additionalSignature, - locale = 'is', -}: RegularSignatureTemplateParams): HTMLText => { - const results = signatureGroups - ?.map((signatureGroup) => { - const className = - signatureGroup?.members?.length === 1 - ? 'signatures single' - : signatureGroup?.members?.length === 2 - ? 'signatures double' - : signatureGroup?.members?.length === 3 - ? 'signatures triple' - : 'signatures' - - return ` -
- ${titleTemplate( - signatureGroup.institution, - signatureGroup.date, - locale, - )} -
- ${signatureGroup?.members - ?.map((s) => signatureTemplate(s.name, s.after, s.above, s.below)) - .join('')} -
-
` - }) - - .join('') - - return additionalSignature - ? ((results + additionalTemplate(additionalSignature)) as HTMLText) - : (results as HTMLText) -} - -export const committeeSignatureTemplate = ({ - signature, - additionalSignature, - locale = 'is', -}: CommitteeSignatureTemplateParams): HTMLText => { - const className = - signature?.members?.length === 1 - ? 'signatures single' - : signature?.members?.length === 2 - ? 'signatures double' - : signature?.members?.length === 3 - ? 'signatures triple' - : 'signatures' - - const html = ` -
- ${titleTemplate(signature?.institution, signature?.date, locale)} -
- ${signatureTemplate( - signature?.chairman.name, - signature?.chairman.after, - signature?.chairman.above, - signature?.chairman.below, - )} -
-
- ${signature?.members - ?.map((s) => signatureTemplate(s.name, '', '', s.below)) - .join('')} -
-
- ` - - return additionalSignature - ? ((html + additionalTemplate(additionalSignature)) as HTMLText) - : (html as HTMLText) -} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIDateController.tsx b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIDateController.tsx new file mode 100644 index 000000000000..4e75c4a5c70e --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIDateController.tsx @@ -0,0 +1,100 @@ +import { SkeletonLoader } from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' +import { DatePickerController } from '@island.is/shared/form-fields' +import { MessageDescriptor } from 'react-intl' +import { OJOI_INPUT_HEIGHT } from '../../lib/constants' +import { useApplication } from '../../hooks/useUpdateApplication' +import { useFormContext } from 'react-hook-form' +import set from 'lodash/set' + +type Props = { + name: string + label: string | MessageDescriptor + placeholder: string | MessageDescriptor + defaultValue?: string + loading?: boolean + applicationId: string + disabled?: boolean + excludeDates?: Date[] + minDate?: Date + maxDate?: Date + onChange?: (value: string) => void +} + +export const OJOIDateController = ({ + name, + label, + placeholder, + defaultValue, + loading, + applicationId, + disabled, + excludeDates, + minDate, + maxDate, + onChange, +}: Props) => { + const { formatMessage: f } = useLocale() + const { + debouncedOnUpdateApplicationHandler, + application, + updateApplication, + } = useApplication({ + applicationId, + }) + + const { setValue } = useFormContext() + + if (loading) { + return ( + + ) + } + + // if defaultValue is passed and there is no value set we must set it in the application state + if (defaultValue && !application.answers.advert?.requestedDate) { + setValue(name, defaultValue) + const currentAnswers = structuredClone(application.answers) + const updatedAnswers = set(currentAnswers, name, defaultValue) + updateApplication(updatedAnswers) + } + + const placeholderText = + typeof placeholder === 'string' ? placeholder : f(placeholder) + + const labelText = typeof label === 'string' ? label : f(label) + + const handleChange = (value: string) => { + const currentAnswers = structuredClone(application.answers) + const newAnswers = set(currentAnswers, name, value) + + return newAnswers + } + + return ( + + debouncedOnUpdateApplicationHandler( + handleChange(e), + onChange && (() => onChange(e)), + ) + } + /> + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIHtmlController.tsx b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIHtmlController.tsx new file mode 100644 index 000000000000..d3426936f725 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIHtmlController.tsx @@ -0,0 +1,72 @@ +import { HTMLText } from '@island.is/regulations' +import { Editor, EditorFileUploader } from '@island.is/regulations-tools/Editor' +import { useCallback, useEffect, useRef } from 'react' +import { classes, editorWrapper } from '../htmlEditor/HTMLEditor.css' +import { baseConfig } from '../htmlEditor/config/baseConfig' +import { Box } from '@island.is/island-ui/core' +import { useApplication } from '../../hooks/useUpdateApplication' +import set from 'lodash/set' + +type Props = { + applicationId: string + name: string + defaultValue?: string + onChange?: (value: HTMLText) => void + editorKey?: string +} + +export const OJOIHtmlController = ({ + applicationId, + name, + onChange, + defaultValue, + editorKey, +}: Props) => { + const { debouncedOnUpdateApplicationHandler, application } = useApplication({ + applicationId, + }) + + const valueRef = useRef(() => + defaultValue ? (defaultValue as HTMLText) : ('' as HTMLText), + ) + + const fileUploader = (): EditorFileUploader => async (blob) => { + throw new Error('Not implemented') + } + + const handleChange = (value: HTMLText) => { + const currentAnswers = structuredClone(application.answers) + const newAnswers = set(currentAnswers, name, value) + + onChange && onChange(value) + return newAnswers + } + + const onChangeHandler = () => { + return handleChange(valueRef.current()) + } + + return ( + + { + // add little bit of delay for valueRef to update + setTimeout( + () => debouncedOnUpdateApplicationHandler(onChangeHandler()), + 100, + ) + }} + onBlur={() => debouncedOnUpdateApplicationHandler(onChangeHandler())} + /> + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx new file mode 100644 index 000000000000..54455a555853 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx @@ -0,0 +1,82 @@ +import { SkeletonLoader } from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' +import { InputController } from '@island.is/shared/form-fields' +import { MessageDescriptor } from 'react-intl' +import { OJOI_INPUT_HEIGHT } from '../../lib/constants' +import { useApplication } from '../../hooks/useUpdateApplication' +import set from 'lodash/set' + +type Props = { + name: string + label: string | MessageDescriptor + placeholder?: string | MessageDescriptor + defaultValue?: string + loading?: boolean + applicationId: string + disabled?: boolean + textarea?: boolean + onChange?: (value: string) => void +} + +export const OJOIInputController = ({ + name, + label, + placeholder, + defaultValue, + loading, + applicationId, + disabled, + textarea, + onChange, +}: Props) => { + const { formatMessage: f } = useLocale() + const { debouncedOnUpdateApplicationHandler, application } = useApplication({ + applicationId, + }) + + const placeholderText = placeholder + ? typeof placeholder === 'string' + ? placeholder + : f(placeholder) + : '' + + const labelText = typeof label === 'string' ? label : f(label) + + const handleChange = (value: string) => { + const currentAnswers = structuredClone(application.answers) + const newAnswers = set(currentAnswers, name, value) + + return newAnswers + } + + if (loading) { + return ( + + ) + } + + return ( + + debouncedOnUpdateApplicationHandler( + handleChange(e.target.value), + onChange && (() => onChange(e.target.value)), + ) + } + /> + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx new file mode 100644 index 000000000000..3bb2ec98f523 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx @@ -0,0 +1,84 @@ +import { SkeletonLoader } from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' +import { SelectController } from '@island.is/shared/form-fields' +import { MessageDescriptor } from 'react-intl' +import { OJOI_INPUT_HEIGHT } from '../../lib/constants' +import { useApplication } from '../../hooks/useUpdateApplication' +import set from 'lodash/set' +import { InputFields } from '../../lib/types' + +type OJOISelectControllerOption = { + label: string + value: string +} + +type Props = { + name: string + label: string | MessageDescriptor + placeholder: string | MessageDescriptor + options?: OJOISelectControllerOption[] + defaultValue?: string + loading?: boolean + applicationId: string + disabled?: boolean + onChange?: (value: string) => void +} + +export const OJOISelectController = ({ + name, + label, + placeholder, + options, + defaultValue, + loading, + applicationId, + disabled, + onChange, +}: Props) => { + const { formatMessage: f } = useLocale() + const { updateApplication, application } = useApplication({ applicationId }) + + const placeholderText = + typeof placeholder === 'string' ? placeholder : f(placeholder) + + const labelText = typeof label === 'string' ? label : f(label) + + const handleChange = (value: string) => { + const currentAnswers = structuredClone(application.answers) + const newAnswers = set(currentAnswers, name, value) + + // we must reset the selected typeId if the department changes + if (name === InputFields.advert.departmentId) { + set(newAnswers, InputFields.advert.typeId, '') + } + + updateApplication(newAnswers) + + onChange && onChange(value) + } + + if (loading) { + return ( + + ) + } + + return ( + handleChange(opt.value)} + /> + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/property/Property.tsx b/libs/application/templates/official-journal-of-iceland/src/components/property/Property.tsx index f257fdf848cc..35d5a5915000 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/property/Property.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/property/Property.tsx @@ -1,17 +1,31 @@ -import { Box, Text } from '@island.is/island-ui/core' +import { Box, SkeletonLoader, Text } from '@island.is/island-ui/core' import * as styles from './Property.css' +import { OJOI_INPUT_HEIGHT } from '../../lib/constants' type Props = { name?: string value?: string + loading?: boolean } -export const Property = ({ name, value }: Props) => ( - - - {name} - - - {value} +export const Property = ({ name, value, loading = false }: Props) => { + if (!value && !loading) { + return null + } + + return ( + + {loading ? ( + + ) : ( + <> + + {name} + + + {value} + + + )} - -) + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/AddCommitteeMember.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/AddCommitteeMember.tsx new file mode 100644 index 000000000000..d094f6b8e98e --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/AddCommitteeMember.tsx @@ -0,0 +1,77 @@ +import { Box, Button } from '@island.is/island-ui/core' +import { signatures } from '../../lib/messages/signatures' +import { useLocale } from '@island.is/localization' +import { useApplication } from '../../hooks/useUpdateApplication' +import { InputFields } from '../../lib/types' +import { + MAXIMUM_COMMITTEE_SIGNATURE_MEMBER_COUNT, + MINIMUM_COMMITTEE_SIGNATURE_MEMBER_COUNT, +} from '../../lib/constants' +import { getCommitteeAnswers, getEmptyMember } from '../../lib/utils' +import set from 'lodash/set' + +type Props = { + applicationId: string +} + +export const AddCommitteeMember = ({ applicationId }: Props) => { + const { formatMessage: f } = useLocale() + const { updateApplication, application, isLoading } = useApplication({ + applicationId, + }) + + const onAddCommitteeMember = () => { + const { signature, currentAnswers } = getCommitteeAnswers( + structuredClone(application.answers), + ) + + if (signature) { + const withExtraMember = { + ...signature, + members: [...(signature.members ?? []), getEmptyMember()], + } + + const updatedAnswers = set( + currentAnswers, + InputFields.signature.committee, + withExtraMember, + ) + + updateApplication(updatedAnswers) + } + } + + const getCurrentCount = () => { + const { signature } = getCommitteeAnswers( + structuredClone(application.answers), + ) + if (signature) { + return signature.members?.length ?? 0 + } + + return 0 + } + + const count = getCurrentCount() + + const isGreaterThanMinimum = count >= MINIMUM_COMMITTEE_SIGNATURE_MEMBER_COUNT + const isLessThanMaximum = count < MAXIMUM_COMMITTEE_SIGNATURE_MEMBER_COUNT + + const isDisabled = !isGreaterThanMinimum || !isLessThanMaximum + + return ( + + + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/AddRegularMember.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/AddRegularMember.tsx new file mode 100644 index 000000000000..4d6aeb79bfc4 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/AddRegularMember.tsx @@ -0,0 +1,92 @@ +import { Box, Button } from '@island.is/island-ui/core' +import { signatures } from '../../lib/messages/signatures' +import { useLocale } from '@island.is/localization' +import { useApplication } from '../../hooks/useUpdateApplication' +import { InputFields } from '../../lib/types' +import { + MAXIMUM_REGULAR_SIGNATURE_MEMBER_COUNT, + MINIMUM_REGULAR_SIGNATURE_MEMBER_COUNT, +} from '../../lib/constants' +import { getEmptyMember, getRegularAnswers } from '../../lib/utils' +import set from 'lodash/set' + +type Props = { + applicationId: string + signatureIndex: number +} + +export const AddRegularMember = ({ applicationId, signatureIndex }: Props) => { + const { formatMessage: f } = useLocale() + const { updateApplication, application, isLoading } = useApplication({ + applicationId, + }) + + const onAddMember = () => { + const { signature, currentAnswers } = getRegularAnswers( + structuredClone(application.answers), + ) + + if (signature) { + const doesSignatureExist = signature.at(signatureIndex) + + if (doesSignatureExist !== undefined) { + const updatedRegularSignature = signature.map((signature, index) => { + if (index === signatureIndex) { + return { + ...signature, + members: [...(signature.members ?? []), getEmptyMember()], + } + } + + return signature + }) + + const updatedAnswers = set( + currentAnswers, + InputFields.signature.regular, + updatedRegularSignature, + ) + + updateApplication(updatedAnswers) + } + } + } + + const getCurrentCount = () => { + const { signature } = getRegularAnswers( + structuredClone(application.answers), + ) + if (signature) { + const doesSignatureExist = signature?.at(signatureIndex) + + if (doesSignatureExist !== undefined) { + return doesSignatureExist.members?.length ?? 0 + } + } + + return 0 + } + + const count = getCurrentCount() + + const isGreaterThanMinimum = count >= MINIMUM_REGULAR_SIGNATURE_MEMBER_COUNT + const isLessThanMaximum = count < MAXIMUM_REGULAR_SIGNATURE_MEMBER_COUNT + + const isDisabled = !isGreaterThanMinimum || !isLessThanMaximum + + return ( + + + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/AddRegularSignature.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/AddRegularSignature.tsx new file mode 100644 index 000000000000..1d3ce373b3ed --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/AddRegularSignature.tsx @@ -0,0 +1,81 @@ +import { useLocale } from '@island.is/localization' +import { signatures } from '../../lib/messages/signatures' +import { Box, Button } from '@island.is/island-ui/core' +import { useApplication } from '../../hooks/useUpdateApplication' +import { getValueViaPath } from '@island.is/application/core' +import { InputFields } from '../../lib/types' +import { + getRegularAnswers, + getRegularSignature, + isRegularSignature, +} from '../../lib/utils' +import set from 'lodash/set' +import { + DEFAULT_REGULAR_SIGNATURE_MEMBER_COUNT, + MAXIMUM_REGULAR_SIGNATURE_COUNT, + ONE, +} from '../../lib/constants' + +type Props = { + applicationId: string +} + +export const AddRegularSignature = ({ applicationId }: Props) => { + const { formatMessage: f } = useLocale() + const { updateApplication, application, isLoading } = useApplication({ + applicationId, + }) + + const onAddInstitution = () => { + const { signature, currentAnswers } = getRegularAnswers( + structuredClone(application.answers), + ) + + if (signature) { + const newSignature = getRegularSignature( + ONE, + DEFAULT_REGULAR_SIGNATURE_MEMBER_COUNT, + )?.pop() + + const updatedAnswers = set( + currentAnswers, + InputFields.signature.regular, + [...signature, newSignature], + ) + + updateApplication(updatedAnswers) + } + } + + const getCount = () => { + const currentAnswers = structuredClone(application.answers) + const signature = getValueViaPath( + currentAnswers, + InputFields.signature.regular, + ) + + if (isRegularSignature(signature)) { + return signature?.length ?? 0 + } + + return 0 + } + + const count = getCount() + + return ( + + + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Additional.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Additional.tsx index 9772604d5949..12148dc93a80 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Additional.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Additional.tsx @@ -1,22 +1,15 @@ import { Box, Text } from '@island.is/island-ui/core' import * as styles from './Signatures.css' -import { InputFields, OJOIFieldBaseProps } from '../../lib/types' -import { InputController } from '@island.is/shared/form-fields' -import { getErrorViaPath } from '@island.is/application/core' import { useLocale } from '@island.is/localization' import { signatures } from '../../lib/messages/signatures' +import { OJOIInputController } from '../input/OJOIInputController' -type Props = Pick & { - signature: string - setSignature: (state: string) => void +type Props = { + applicationId: string + name: string } -export const AdditionalSignature = ({ - application, - errors, - signature, - setSignature, -}: Props) => { +export const AdditionalSignature = ({ applicationId, name }: Props) => { const { formatMessage: f } = useLocale() return ( @@ -25,18 +18,10 @@ export const AdditionalSignature = ({ {f(signatures.headings.additionalSignature)} - setSignature(event.target.value)} + diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Chairman.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Chairman.tsx new file mode 100644 index 000000000000..91b23f17dcaf --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Chairman.tsx @@ -0,0 +1,106 @@ +import { Box, Text } from '@island.is/island-ui/core' +import { useApplication } from '../../hooks/useUpdateApplication' +import { useLocale } from '@island.is/localization' +import { signatures } from '../../lib/messages/signatures' +import { InputFields } from '../../lib/types' +import { getCommitteeAnswers, getEmptyMember } from '../../lib/utils' +import { memberItemSchema } from '../../lib/dataSchema' +import { SignatureMember } from './Member' +import set from 'lodash/set' +import * as styles from './Signatures.css' +import * as z from 'zod' + +type Props = { + applicationId: string + member?: z.infer +} + +type MemberProperties = ReturnType + +export const Chairman = ({ applicationId, member }: Props) => { + const { formatMessage: f } = useLocale() + const { application, debouncedOnUpdateApplicationHandler } = useApplication({ + applicationId, + }) + + const handleChairmanChange = (value: string, key: keyof MemberProperties) => { + const { signature, currentAnswers } = getCommitteeAnswers( + application.answers, + ) + + if (signature) { + const updatedCommitteeSignature = { + ...signature, + chairman: { ...signature.chairman, [key]: value }, + } + + const updatedSignatures = set( + currentAnswers, + InputFields.signature.committee, + updatedCommitteeSignature, + ) + + return updatedSignatures + } + + return currentAnswers + } + + if (!member) { + return null + } + + return ( + + + {f(signatures.headings.chairman)} + + + + + debouncedOnUpdateApplicationHandler( + handleChairmanChange(e.target.value, 'above'), + ) + } + /> + + debouncedOnUpdateApplicationHandler( + handleChairmanChange(e.target.value, 'after'), + ) + } + /> + + + + debouncedOnUpdateApplicationHandler( + handleChairmanChange(e.target.value, 'name'), + ) + } + /> + + debouncedOnUpdateApplicationHandler( + handleChairmanChange(e.target.value, 'below'), + ) + } + /> + + + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Committee.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Committee.tsx index a941b1e3d9f8..0f7163017690 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Committee.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Committee.tsx @@ -1,282 +1,75 @@ -import { Box, Text, Button } from '@island.is/island-ui/core' +import { Box, Text } from '@island.is/island-ui/core' import * as styles from './Signatures.css' +import { InstitutionSignature } from './Institution' +import { SignatureTypes } from '../../lib/constants' +import { CommitteeMember } from './CommitteeMember' +import { useApplication } from '../../hooks/useUpdateApplication' +import { isCommitteeSignature } from '../../lib/utils' +import { Chairman } from './Chairman' +import { getValueViaPath } from '@island.is/application/core' +import { InputFields } from '../../lib/types' +import { AdditionalSignature } from './Additional' import { useLocale } from '@island.is/localization' -import { - CommitteeSignatureState, - InputFields, - OJOIFieldBaseProps, -} from '../../lib/types' -import cloneDeep from 'lodash/cloneDeep' -import { - DatePickerController, - InputController, -} from '@island.is/shared/form-fields' -import { INITIAL_ANSWERS, MEMBER_INDEX } from '../../lib/constants' -import { getErrorViaPath } from '@island.is/application/core' import { signatures } from '../../lib/messages/signatures' +import { AddCommitteeMember } from './AddCommitteeMember' -type ChairmanKey = keyof NonNullable['chairman'] -type MemberKey = keyof NonNullable['members'][0] - -type LocalState = typeof INITIAL_ANSWERS['signature'] -type Props = Pick & { - state: LocalState - setState: (state: LocalState) => void - addSignature?: boolean +type Props = { + applicationId: string } -export const CommitteeSignature = ({ state, setState, errors }: Props) => { +export const CommitteeSignature = ({ applicationId }: Props) => { const { formatMessage: f } = useLocale() + const { application } = useApplication({ + applicationId, + }) - const onCommitteeChairmanChange = (key: ChairmanKey, value: string) => { - const newState = cloneDeep(state) - newState.committee.chairman[key] = value - setState(newState) - } + const getSignature = () => { + const currentAnswers = getValueViaPath( + application.answers, + InputFields.signature.committee, + ) - const onCommitteMemberChange = ( - index: number, - key: MemberKey, - value: string, - ) => { - const newState = cloneDeep(state) - if (!newState.committee.members) return - newState.committee.members[index][key] = value - setState(newState) + if (isCommitteeSignature(currentAnswers)) { + return currentAnswers + } } - const onCommitteeChange = ( - key: keyof Omit, - value: string, - ) => { - const newState = cloneDeep(state) - newState.committee[key] = value - setState(newState) - } - - const onAddCommitteeMember = () => { - const newState = cloneDeep(state) - if (!newState.committee.members) return - newState.committee.members.push({ - name: '', - below: '', - }) - setState(newState) - } + const signature = getSignature() - const onRemoveCommitteeMember = (index: number) => { - const newState = cloneDeep(state) - if (!newState.committee.members) return - newState.committee.members.splice(index, 1) - setState(newState) + if (!signature) { + return null } return ( - - - - onCommitteeChange('institution', e.target.value)} - size="sm" - /> - - - onCommitteeChange('date', date)} - /> - - - - - {f(signatures.headings.chairman)} - - - - - - onCommitteeChairmanChange('above', e.target.value) - } - error={ - errors && - getErrorViaPath( - errors, - InputFields.signature.committee.chairman.above, - ) - } - /> - - - - onCommitteeChairmanChange('name', e.target.value) - } - error={ - errors && - getErrorViaPath( - errors, - InputFields.signature.committee.chairman.name, - ) - } + <> + + + + + + {f(signatures.headings.committeeMembers)} + + + {signature?.members?.map((member, index) => ( + - + ))} + - - - - onCommitteeChairmanChange('after', e.target.value) - } - error={ - errors && - getErrorViaPath( - errors, - InputFields.signature.committee.chairman.after, - ) - } - /> - - - - onCommitteeChairmanChange('below', e.target.value) - } - error={ - errors && - getErrorViaPath( - errors, - InputFields.signature.committee.chairman.below, - ) - } - /> - - - - - - - {f(signatures.headings.committeeMembers)} - - - {state.committee.members?.map((member, index) => { - const namePath = - InputFields.signature.committee.members.name.replace( - MEMBER_INDEX, - `${index}`, - ) - - const belowPath = - InputFields.signature.committee.members.below.replace( - MEMBER_INDEX, - `${index}`, - ) - return ( - - - - - onCommitteMemberChange(index, 'name', e.target.value) - } - error={errors && getErrorViaPath(errors, namePath)} - /> - - - - onCommitteMemberChange(index, 'below', e.target.value) - } - error={errors && getErrorViaPath(errors, belowPath)} - /> - - - {index > 1 && ( - - + + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/CommitteeMember.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/CommitteeMember.tsx new file mode 100644 index 000000000000..c153a116f289 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/CommitteeMember.tsx @@ -0,0 +1,123 @@ +import { Box } from '@island.is/island-ui/core' +import * as styles from './Signatures.css' +import { useApplication } from '../../hooks/useUpdateApplication' +import { useLocale } from '@island.is/localization' +import { signatures } from '../../lib/messages/signatures' +import { InputFields } from '../../lib/types' +import set from 'lodash/set' +import { getCommitteeAnswers, getEmptyMember } from '../../lib/utils' +import { memberItemSchema } from '../../lib/dataSchema' +import { SignatureMember } from './Member' +import * as z from 'zod' +import { RemoveCommitteeMember } from './RemoveComitteeMember' +import { getValueViaPath } from '@island.is/application/core' + +type Props = { + applicationId: string + memberIndex: number + member?: z.infer +} + +type MemberProperties = ReturnType + +export const CommitteeMember = ({ + applicationId, + memberIndex, + member, +}: Props) => { + const { formatMessage: f } = useLocale() + const { debouncedOnUpdateApplicationHandler, application } = useApplication({ + applicationId, + }) + + const handleMemberChange = ( + value: string, + key: keyof MemberProperties, + memberIndex: number, + ) => { + const { signature, currentAnswers } = getCommitteeAnswers( + application.answers, + ) + + if (signature) { + const updatedCommitteeSignature = { + ...signature, + members: signature?.members?.map((m, i) => { + if (i === memberIndex) { + return { + ...m, + [key]: value, + } + } + + return m + }), + } + + const updatedSignatures = set( + currentAnswers, + InputFields.signature.committee, + updatedCommitteeSignature, + ) + + return updatedSignatures + } + + return currentAnswers + } + + if (!member) { + return null + } + + const getMemberCount = () => { + const { signature } = getCommitteeAnswers(application.answers) + + if (signature) { + return signature.members?.length ?? 0 + } + + return 0 + } + + const isLast = memberIndex === getMemberCount() - 1 + + return ( + + + + + debouncedOnUpdateApplicationHandler( + handleMemberChange(e.target.value, 'name', memberIndex), + ) + } + /> + + + + debouncedOnUpdateApplicationHandler( + handleMemberChange(e.target.value, 'below', memberIndex), + ) + } + /> + + + + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Institution.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Institution.tsx new file mode 100644 index 000000000000..06a94dbf7502 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Institution.tsx @@ -0,0 +1,163 @@ +import { + Box, + DatePicker, + Input, + SkeletonLoader, +} from '@island.is/island-ui/core' +import { + OJOI_INPUT_HEIGHT, + SignatureType, + SignatureTypes, +} from '../../lib/constants' +import { useLocale } from '@island.is/localization' +import { signatures } from '../../lib/messages/signatures' +import { useApplication } from '../../hooks/useUpdateApplication' +import set from 'lodash/set' +import { getValueViaPath } from '@island.is/application/core' +import { InputFields } from '../../lib/types' +import * as styles from './Signatures.css' +import { + getCommitteeAnswers, + getRegularAnswers, + getSignatureDefaultValues, + isCommitteeSignature, + isRegularSignature, +} from '../../lib/utils' +import { z } from 'zod' +import { signatureInstitutionSchema } from '../../lib/dataSchema' +import { RemoveRegularSignature } from './RemoveRegularSignature' +type Props = { + applicationId: string + type: SignatureType + signatureIndex?: number +} + +type SignatureInstitutionKeys = z.infer + +export const InstitutionSignature = ({ + applicationId, + type, + signatureIndex, +}: Props) => { + const { formatMessage: f } = useLocale() + const { + debouncedOnUpdateApplicationHandler, + application, + applicationLoading, + } = useApplication({ + applicationId, + }) + + const handleInstitutionChange = ( + value: string, + key: SignatureInstitutionKeys, + signatureIndex?: number, + ) => { + const { signature, currentAnswers } = + type === SignatureTypes.COMMITTEE + ? getCommitteeAnswers(application.answers) + : getRegularAnswers(application.answers) + + if (isRegularSignature(signature)) { + const updatedRegularSignature = signature?.map((signature, index) => { + if (index === signatureIndex) { + return { + ...signature, + [key]: value, + } + } + + return signature + }) + + const updatedSignatures = set( + currentAnswers, + InputFields.signature[type], + updatedRegularSignature, + ) + + return updatedSignatures + } + + if (isCommitteeSignature(signature)) { + const updatedCommitteeSignature = set( + currentAnswers, + InputFields.signature[type], + { + ...signature, + [key]: value, + }, + ) + + return updatedCommitteeSignature + } + + return currentAnswers + } + + if (applicationLoading) { + return + } + + const { institution, date } = getSignatureDefaultValues( + getValueViaPath(application.answers, InputFields.signature[type]), + signatureIndex, + ) + + return ( + + + + + debouncedOnUpdateApplicationHandler( + handleInstitutionChange( + e.target.value, + 'institution', + signatureIndex, + ), + ) + } + /> + + + + date && + debouncedOnUpdateApplicationHandler( + handleInstitutionChange( + date.toISOString(), + 'date', + signatureIndex, + ), + ) + } + /> + + {signatureIndex !== undefined && ( + + )} + + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Member.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Member.tsx new file mode 100644 index 000000000000..fe26e50abf9c --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Member.tsx @@ -0,0 +1,28 @@ +import { Input } from '@island.is/island-ui/core' + +type Props = { + name: string + label: string + defaultValue?: string + onChange: ( + e: React.ChangeEvent, + ) => void +} + +export const SignatureMember = ({ + name, + label, + defaultValue, + onChange, +}: Props) => { + return ( + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Regular.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Regular.tsx index 6619c1173d69..0b314fd77ff8 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Regular.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Regular.tsx @@ -1,296 +1,88 @@ -import { Box, Button, Text } from '@island.is/island-ui/core' +import { Box, Text } from '@island.is/island-ui/core' import * as styles from './Signatures.css' +import { InputFields } from '../../lib/types' +import { useApplication } from '../../hooks/useUpdateApplication' +import { getValueViaPath } from '@island.is/application/core' +import { InstitutionSignature } from './Institution' +import { RegularMember } from './RegularMember' +import { isRegularSignature } from '../../lib/utils' +import { AddRegularMember } from './AddRegularMember' +import { AddRegularSignature } from './AddRegularSignature' +import { AdditionalSignature } from './Additional' +import { signatures as messages } from '../../lib/messages/signatures' import { useLocale } from '@island.is/localization' -import { - InputFields, - OJOIFieldBaseProps, - RegularSignatureState, -} from '../../lib/types' -import cloneDeep from 'lodash/cloneDeep' -import { - INITIAL_ANSWERS, - INSTITUTION_INDEX, - MEMBER_INDEX, -} from '../../lib/constants' -import { getErrorViaPath } from '@island.is/application/core' -import { signatures } from '../../lib/messages/signatures' -import { - DatePickerController, - InputController, -} from '@island.is/shared/form-fields' -type LocalState = typeof INITIAL_ANSWERS['signature'] - -type Props = Pick & { - state: LocalState - setState: (state: LocalState) => void - addSignature?: boolean +type Props = { + applicationId: string } -type Institution = NonNullable[0] - -type InstitutionMember = NonNullable[0] - -type MemberKey = keyof InstitutionMember - -type InstitutionKey = keyof Omit - -export const RegularSignature = ({ state, setState, errors }: Props) => { +export const RegularSignature = ({ applicationId }: Props) => { const { formatMessage: f } = useLocale() - - const onChangeMember = ( - institutionIndex: number, - memberIndex: number, - key: MemberKey, - value: string, - ) => { - const clonedState = cloneDeep(state) - const institution = clonedState.regular.find( - (_, index) => index === institutionIndex, + const { application } = useApplication({ + applicationId, + }) + + const getSignature = () => { + const currentAnswers = getValueViaPath( + application.answers, + InputFields.signature.regular, ) - if (!institution) return - - const member = institution?.members.find( - (_, index) => index === memberIndex, - ) - - if (!member) return - - const updatedMember = { ...member, [key]: value } - institution.members.splice(memberIndex, 1, updatedMember) - clonedState.regular.splice(institutionIndex, 1, institution) - setState(clonedState) + if (isRegularSignature(currentAnswers)) { + return currentAnswers + } } - const onRemoveMember = (institutionIndex: number, memberIndex: number) => { - const clonedState = cloneDeep(state) - const institution = clonedState.regular.find( - (_, index) => index === institutionIndex, - ) + const signatures = getSignature() - if (!institution) return - - institution.members.splice(memberIndex, 1) - clonedState.regular.splice(institutionIndex, 1, institution) - setState(clonedState) - } - - const onAddMember = (institutionIndex: number) => { - const clonedState = cloneDeep(state) - const institution = clonedState.regular.find( - (_, index) => index === institutionIndex, - ) - - if (!institution) return - - institution.members.push({ - above: '', - name: '', - after: '', - below: '', - }) - clonedState.regular.splice(institutionIndex, 1, institution) - setState(clonedState) - } - - const onChangeInstitution = ( - institutionIndex: number, - key: InstitutionKey, - value: string, - ) => { - const clonedState = cloneDeep(state) - const institution = clonedState.regular.find( - (_, index) => index === institutionIndex, - ) - - if (!institution) return - - const updatedInstitution = { ...institution, [key]: value } - clonedState.regular.splice(institutionIndex, 1, updatedInstitution) - setState(clonedState) - } - - const onRemoveInstitution = (institutionIndex: number) => { - const clonedState = cloneDeep(state) - clonedState.regular.splice(institutionIndex, 1) - setState(clonedState) - } - - const onAddInstitution = () => { - const clonedState = cloneDeep(state) - clonedState.regular.push({ - institution: '', - date: '', - members: [ - { - above: '', - name: '', - after: '', - below: '', - }, - ], - }) - setState(clonedState) + if (!signatures) { + return null } return ( - - {state.regular.map((institution, index) => { - const institutionPath = - InputFields.signature.regular.institution.replace( - INSTITUTION_INDEX, - `${index}`, - ) - - const datePath = InputFields.signature.regular.date.replace( - INSTITUTION_INDEX, - `${index}`, - ) - - return ( - - - - - onChangeInstitution(index, 'institution', e.target.value) - } - error={errors && getErrorViaPath(errors, institutionPath)} - size="sm" - /> - - - onChangeInstitution(index, 'date', date)} - error={errors && getErrorViaPath(errors, datePath)} + <> + + {signatures?.map((signature, index) => { + return ( + + + - - - {index > 0 && ( - + ) + })} + + - - ) - })} - - + ) + })} - + + + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/RegularMember.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/RegularMember.tsx new file mode 100644 index 000000000000..01ce1ba0ed42 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/RegularMember.tsx @@ -0,0 +1,153 @@ +import { Box } from '@island.is/island-ui/core' +import * as styles from './Signatures.css' +import { useApplication } from '../../hooks/useUpdateApplication' +import { useLocale } from '@island.is/localization' +import { signatures } from '../../lib/messages/signatures' +import { InputFields } from '../../lib/types' +import set from 'lodash/set' +import { getEmptyMember, getRegularAnswers } from '../../lib/utils' +import { memberItemSchema } from '../../lib/dataSchema' +import { SignatureMember } from './Member' +import * as z from 'zod' +import { RemoveRegularMember } from './RemoveRegularMember' + +type Props = { + applicationId: string + signatureIndex: number + memberIndex: number + member?: z.infer +} + +type MemberProperties = ReturnType + +export const RegularMember = ({ + applicationId, + signatureIndex, + memberIndex, + member, +}: Props) => { + const { formatMessage: f } = useLocale() + const { debouncedOnUpdateApplicationHandler, application } = useApplication({ + applicationId, + }) + + const handleMemberChange = ( + value: string, + key: keyof MemberProperties, + si: number, + mi: number, + ) => { + const { signature, currentAnswers } = getRegularAnswers(application.answers) + + if (signature) { + const updatedRegularSignature = signature.map((s, index) => { + if (index === si) { + return { + ...s, + members: s.members?.map((member, memberIndex) => { + if (memberIndex === mi) { + return { + ...member, + [key]: value, + } + } + + return member + }), + } + } + + return s + }) + + const updatedSignatures = set( + currentAnswers, + InputFields.signature.regular, + updatedRegularSignature, + ) + + return updatedSignatures + } + + return currentAnswers + } + + if (!member) { + return null + } + + return ( + + + + debouncedOnUpdateApplicationHandler( + handleMemberChange( + e.target.value, + 'above', + signatureIndex, + memberIndex, + ), + ) + } + /> + + debouncedOnUpdateApplicationHandler( + handleMemberChange( + e.target.value, + 'after', + signatureIndex, + memberIndex, + ), + ) + } + /> + + + + debouncedOnUpdateApplicationHandler( + handleMemberChange( + e.target.value, + 'name', + signatureIndex, + memberIndex, + ), + ) + } + /> + + debouncedOnUpdateApplicationHandler( + handleMemberChange( + e.target.value, + 'below', + signatureIndex, + memberIndex, + ), + ) + } + /> + + + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/RemoveComitteeMember.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/RemoveComitteeMember.tsx new file mode 100644 index 000000000000..43d6f6ba4295 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/RemoveComitteeMember.tsx @@ -0,0 +1,55 @@ +import { Box, Button } from '@island.is/island-ui/core' +import { useApplication } from '../../hooks/useUpdateApplication' +import { InputFields } from '../../lib/types' +import { MINIMUM_COMMITTEE_SIGNATURE_MEMBER_COUNT } from '../../lib/constants' +import set from 'lodash/set' +import * as styles from './Signatures.css' +import { getCommitteeAnswers, isCommitteeSignature } from '../../lib/utils' + +type Props = { + applicationId: string + memberIndex: number +} + +export const RemoveCommitteeMember = ({ + applicationId, + memberIndex, +}: Props) => { + const { updateApplication, application, isLoading } = useApplication({ + applicationId, + }) + + const onRemoveMember = () => { + const { currentAnswers, signature } = getCommitteeAnswers( + application.answers, + ) + + if (isCommitteeSignature(signature)) { + const updatedCommitteeSignature = { + ...signature, + members: signature.members?.filter((_, mi) => mi !== memberIndex), + } + + const updatedAnswers = set( + currentAnswers, + InputFields.signature.committee, + updatedCommitteeSignature, + ) + + updateApplication(updatedAnswers) + } + } + + return ( + + diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Attachments.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Attachments.tsx index 864cf1b6d285..c2bd139df826 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Attachments.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Attachments.tsx @@ -1,65 +1,33 @@ -import { Box } from '@island.is/island-ui/core' -import { FormGroup } from '../components/form/FormGroup' -import { UPLOAD_ACCEPT, FILE_SIZE_LIMIT, FileNames } from '../lib/constants' -import { attachments } from '../lib/messages' -import { InputFields, OJOIFieldBaseProps } from '../lib/types' -import { FileUploadController } from '@island.is/application/ui-components' -import { Application } from '@island.is/application/types' -import { RadioController } from '@island.is/shared/form-fields' +import { OJOIFieldBaseProps } from '../lib/types' +import { Box, InputFileUpload } from '@island.is/island-ui/core' +import { useFileUpload } from '../hooks/useFileUpload' +import { ALLOWED_FILE_TYPES, ApplicationAttachmentType } from '../lib/constants' import { useLocale } from '@island.is/localization' -import { getErrorViaPath } from '@island.is/application/core' +import { attachments } from '../lib/messages/attachments' -export const Attachments = ({ application, errors }: OJOIFieldBaseProps) => { +export const Attachments = ({ application }: OJOIFieldBaseProps) => { const { formatMessage: f } = useLocale() - - // TODO: Create wrapper around file upload component to handle file upload + const { files, onChange, onRemove } = useFileUpload({ + applicationId: application.id, + attachmentType: ApplicationAttachmentType.ADDITIONS, + }) return ( - <> - - - - - - - - - - - - - + + + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Comments.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Comments.tsx index 2106b0d876b1..e5a25426b2fb 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Comments.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Comments.tsx @@ -1,56 +1,80 @@ -import { AlertMessage, Box, SkeletonLoader } from '@island.is/island-ui/core' +import { + AlertMessage, + Box, + SkeletonLoader, + Stack, +} from '@island.is/island-ui/core' import { OJOIFieldBaseProps } from '../lib/types' import { CommentsList } from '../components/comments/CommentList' import { FormGroup } from '../components/form/FormGroup' import { useComments } from '../hooks/useComments' import { useLocale } from '@island.is/localization' -import { comments as messages } from '../lib/messages/comments' +import { + error as errorMessages, + comments as commentMessages, +} from '../lib/messages' +import { OJOI_INPUT_HEIGHT } from '../lib/constants' +import { AddComment } from '../components/comments/AddComment' export const Comments = ({ application }: OJOIFieldBaseProps) => { const { formatMessage: f } = useLocale() - const { comments, error, loading } = useComments({ + const { comments, loading, error } = useComments({ applicationId: application.id, }) - if (error) { - return ( - - - - ) - } + const showCommentsList = comments && comments.length > 0 if (loading) { return ( - - - + ) } return ( - - - - {/* handleAddComment(c)} /> */} - + + + {error && ( + + )} + {!showCommentsList && ( + + )} + {showCommentsList && ( + + ({ + task: comment.task.title, + comment: comment.task.comment as string, + from: comment.task.from ?? undefined, + date: comment.createdAt, + type: 'received', // TODO: Implement sent comments + }))} + /> + + )} + + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/CommunicationChannels.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/CommunicationChannels.tsx new file mode 100644 index 000000000000..8cad52cf0c78 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/fields/CommunicationChannels.tsx @@ -0,0 +1,37 @@ +import { useLocale } from '@island.is/localization' +import { OJOIFieldBaseProps } from '../lib/types' +import { FormGroup } from '../components/form/FormGroup' +import { publishing } from '../lib/messages' +import { ChannelList } from '../components/communicationChannels/ChannelList' +import { AddChannel } from '../components/communicationChannels/AddChannel' +import { useState } from 'react' + +export const CommunicationChannels = ({ application }: OJOIFieldBaseProps) => { + const { formatMessage: f } = useLocale() + + const [email, setEmail] = useState('') + const [phone, setPhone] = useState('') + const [isVisible, setIsVisible] = useState(false) + + return ( + + { + if (email) setEmail(email) + if (phone) setPhone(phone ?? '') + setIsVisible(true) + }} + applicationId={application.id} + /> + + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Message.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Message.tsx new file mode 100644 index 000000000000..f7815955eb2e --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Message.tsx @@ -0,0 +1,18 @@ +import { FormGroup } from '../components/form/FormGroup' +import { OJOIInputController } from '../components/input/OJOIInputController' +import { publishing } from '../lib/messages' +import { InputFields, OJOIFieldBaseProps } from '../lib/types' + +export const Message = ({ application }: OJOIFieldBaseProps) => { + return ( + + + + ) +} diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Original.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Original.tsx index f2e12858f5e2..a3d8a9ac7dd9 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Original.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Original.tsx @@ -1,29 +1,33 @@ -import { FileUploadController } from '@island.is/application/ui-components' -import { FormGroup } from '../components/form/FormGroup' -import { InputFields, OJOIFieldBaseProps } from '../lib/types' -import { Application } from '@island.is/application/types' -import { FILE_SIZE_LIMIT, UPLOAD_ACCEPT } from '../lib/constants' -import { original } from '../lib/messages' +import { OJOIFieldBaseProps } from '../lib/types' +import { ALLOWED_FILE_TYPES, ApplicationAttachmentType } from '../lib/constants' +import { attachments } from '../lib/messages' import { useLocale } from '@island.is/localization' -import { getErrorViaPath } from '@island.is/application/core' +import { InputFileUpload, Box } from '@island.is/island-ui/core' -export const Original = (props: OJOIFieldBaseProps) => { +import { useFileUpload } from '../hooks/useFileUpload' + +export const Original = ({ application }: OJOIFieldBaseProps) => { const { formatMessage: f } = useLocale() + const { files, onChange, onRemove } = useFileUpload({ + applicationId: application.id, + attachmentType: ApplicationAttachmentType.ORIGINAL, + }) return ( - - + - + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Preview.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Preview.tsx index c018afad1301..7385687ab936 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Preview.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Preview.tsx @@ -1,45 +1,36 @@ -import { Box, Button, SkeletonLoader } from '@island.is/island-ui/core' +import { + AlertMessage, + Box, + Bullet, + BulletList, + SkeletonLoader, + Stack, + Text, +} from '@island.is/island-ui/core' import { HTMLEditor } from '../components/htmlEditor/HTMLEditor' import { signatureConfig } from '../components/htmlEditor/config/signatureConfig' -import { advertisementTemplate } from '../components/htmlEditor/templates/content' -import { - regularSignatureTemplate, - committeeSignatureTemplate, -} from '../components/htmlEditor/templates/signatures' -import { preview } from '../lib/messages' -import { - OJOIFieldBaseProps, - OfficialJournalOfIcelandGraphqlResponse, -} from '../lib/types' +import { OJOIFieldBaseProps } from '../lib/types' import { useLocale } from '@island.is/localization' -import { useQuery } from '@apollo/client' -import { PDF_QUERY, PDF_URL_QUERY, TYPE_QUERY } from '../graphql/queries' - -export const Preview = (props: OJOIFieldBaseProps) => { - const { formatMessage: f } = useLocale() - const { answers, id } = props.application - const { advert, signature } = answers +import { HTMLText } from '@island.is/regulations-tools/types' +import { getAdvertMarkup, getSignatureMarkup } from '../lib/utils' +import { SignatureTypes } from '../lib/constants' +import { useApplication } from '../hooks/useUpdateApplication' +import { advert, error, preview } from '../lib/messages' +import { useType } from '../hooks/useType' - const { data, loading } = useQuery(TYPE_QUERY, { - variables: { - params: { - id: advert?.type, - }, - }, +export const Preview = ({ application }: OJOIFieldBaseProps) => { + const { application: currentApplication } = useApplication({ + applicationId: application.id, }) - const type = data?.officialJournalOfIcelandType?.type?.title - - const { data: pdfUrlData } = useQuery(PDF_URL_QUERY, { - variables: { - id: id, - }, - }) + const { formatMessage: f } = useLocale() - const { data: pdfData } = useQuery(PDF_QUERY, { - variables: { - id: id, - }, + const { + type, + loading, + error: typeError, + } = useType({ + typeId: currentApplication.answers.advert?.typeId, }) if (loading) { @@ -48,75 +39,63 @@ export const Preview = (props: OJOIFieldBaseProps) => { ) } - const onCopyPreviewLink = () => { - if (!pdfData) { - return - } + const signatureMarkup = getSignatureMarkup({ + signatures: currentApplication.answers.signatures, + type: currentApplication.answers.misc?.signatureType as SignatureTypes, + }) - const url = pdfData.officialJournalOfIcelandApplicationGetPdfUrl.url + const advertMarkup = getAdvertMarkup({ + type: type?.title, + title: currentApplication.answers.advert?.title, + html: currentApplication.answers.advert?.html, + }) - navigator.clipboard.writeText(url) - } + const hasMarkup = + !!currentApplication.answers.advert?.html || + type?.title || + currentApplication.answers.advert?.title - const onOpenPdfPreview = () => { - if (!pdfData) { - return - } - - window.open( - `data:application/pdf,${pdfData.officialJournalOfIcelandApplicationGetPdf.pdf}`, - '_blank', - ) - } + const combinedHtml = hasMarkup + ? (`${advertMarkup}
${signatureMarkup}` as HTMLText) + : (`${signatureMarkup}` as HTMLText) return ( - <> - - {!!pdfUrlData && ( - + + + {typeError && ( + )} - {!!pdfData && ( - + {!hasMarkup && ( + + {f(error.missingHtmlMessage)} + + {f(advert.inputs.department.label)} + {f(advert.inputs.type.label)} + {f(advert.inputs.title.label)} + {f(advert.inputs.editor.label)} + + + } + /> )} - + - + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx index 5f8ae9da5521..859364996ebf 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx @@ -1,296 +1,136 @@ import { useLocale } from '@island.is/localization' import { FormGroup } from '../components/form/FormGroup' +import { InputFields, OJOIFieldBaseProps } from '../lib/types' +import { error, publishing } from '../lib/messages' +import { OJOIDateController } from '../components/input/OJOIDateController' +import { useApplication } from '../hooks/useUpdateApplication' import { - InputFields, - OfficialJournalOfIcelandGraphqlResponse, - OJOIFieldBaseProps, - Override, -} from '../lib/types' -import { publishing } from '../lib/messages' -import { - DEBOUNCE_INPUT_TIMER, - MINIMUM_WEEKDAYS, - INITIAL_ANSWERS, -} from '../lib/constants' -import { useCallback, useEffect, useState } from 'react' -import { - DatePickerController, - InputController, -} from '@island.is/shared/form-fields' -import { getErrorViaPath } from '@island.is/application/core' -import { Box, Icon, Select, Tag } from '@island.is/island-ui/core' + AlertMessage, + Box, + Select, + SkeletonLoader, + Tag, +} from '@island.is/island-ui/core' +import { useCategories } from '../hooks/useCategories' +import { MINIMUM_WEEKDAYS, OJOI_INPUT_HEIGHT } from '../lib/constants' +import set from 'lodash/set' import addYears from 'date-fns/addYears' import { addWeekdays, getWeekendDates } from '../lib/utils' -import { useMutation, useQuery } from '@apollo/client' -import { CATEGORIES_QUERY } from '../graphql/queries' -import { ChannelList } from '../components/communicationChannels/ChannelList' -import { AddChannel } from '../components/communicationChannels/AddChannel' -import { UPDATE_APPLICATION } from '@island.is/application/graphql' -import debounce from 'lodash/debounce' -import { useFormContext } from 'react-hook-form' - -type LocalState = Override< - typeof INITIAL_ANSWERS['publishing'], - { - contentCategories: CategoryOption[] - communicationChannels: Channel[] - } -> -type Channel = { - email: string - phone: string -} +export const Publishing = ({ application }: OJOIFieldBaseProps) => { + const { formatMessage: f } = useLocale() -type CategoryOption = { - label: string - value: string -} + const { application: currentApplication, updateApplication } = useApplication( + { + applicationId: application.id, + }, + ) -export const Publishing = (props: OJOIFieldBaseProps) => { - const { formatMessage: f, locale } = useLocale() - const { application } = props - const { answers } = application + const { + categories, + error: categoryError, + loading: categoryLoading, + } = useCategories() - const today = new Date() - const maxEndDate = addYears(today, 5) - const minDate = new Date() - if (minDate.getHours() >= 12) { - minDate.setDate(minDate.getDate() + 1) + if (categoryLoading) { + return } - const defaultDate = answers.publishing?.date - ? new Date(answers.publishing.date).toISOString().split('T')[0] - : addWeekdays(today, MINIMUM_WEEKDAYS).toISOString().split('T')[0] - - const { setValue, clearErrors } = useFormContext() - - const [channelState, setChannelState] = useState({ - email: '', - phone: '', - }) + if (categoryError) { + return ( + + ) + } - const [categories, setCategories] = useState([]) + const onCategoryChange = (value?: string) => { + if (!value) { + return + } - const [state, setState] = useState({ - date: answers.publishing?.date ?? '', - contentCategories: - answers.publishing?.contentCategories ?? ([] as CategoryOption[]), - communicationChannels: - answers.publishing?.communicationChannels ?? ([] as Channel[]), - message: answers.publishing?.message ?? '', - }) + const currentAnswers = structuredClone(currentApplication.answers) + const selectedCategories = currentAnswers.advert?.categories || [] - const [updateApplication] = useMutation(UPDATE_APPLICATION) + const newCategories = selectedCategories.includes(value) + ? selectedCategories.filter((c) => c !== value) + : [...selectedCategories, value] - useQuery>( - CATEGORIES_QUERY, - { - variables: { - params: { - pageSize: 1000, - }, - }, - onCompleted: (data) => { - setCategories( - data.officialJournalOfIcelandCategories.categories.map( - (category) => ({ - label: category.title, - value: category.id, - }), - ), - ) - }, - }, - ) - - const onSelect = (opt: CategoryOption) => { - if (!opt.value) return - - const shouldAdd = !state.contentCategories.some( - (category) => category.value === opt.value, + const updatedAnswers = set( + currentAnswers, + InputFields.advert.categories, + newCategories, ) - const updatedCategories = shouldAdd - ? [...state.contentCategories, { label: opt.label, value: opt.value }] - : state.contentCategories.filter( - (category) => category.value !== opt.value, - ) - - setState({ ...state, contentCategories: updatedCategories }) - setValue(InputFields.publishing.contentCategories, updatedCategories) - } - const onEditChannel = (channel: Channel) => { - onRemoveChannel(channel) - setChannelState(channel) + updateApplication(updatedAnswers) } - const onRemoveChannel = (channel: Channel) => { - setState({ - ...state, - communicationChannels: state.communicationChannels.filter( - (c) => c.email !== channel.email, - ), - }) + const defaultCategory = { + label: f(publishing.inputs.contentCategories.placeholder), + value: '', } - const onAddChannel = () => { - if (!channelState.email) return - setState({ - ...state, - communicationChannels: [ - ...state.communicationChannels, - { - email: channelState.email, - phone: channelState.phone, - }, - ], - }) - setChannelState({ email: '', phone: '' }) - } - - const updateHandler = useCallback(async () => { - await updateApplication({ - variables: { - locale, - input: { - skipValidation: true, - id: application.id, - answers: { - ...application.answers, - publishing: state, - }, - }, - }, - }) + const mappedCategories = categories?.map((c) => ({ + label: c.title, + value: c.id, + })) - setValue(InputFields.publishing.date, state.date) - setValue(InputFields.publishing.contentCategories, state.contentCategories) - setValue( - InputFields.publishing.communicationChannels, - state.communicationChannels, - ) - setValue(InputFields.publishing.message, state.message) - }, [ - application.answers, - application.id, - locale, - setValue, - state, - updateApplication, - ]) + const selectedCategories = categories?.filter((c) => + currentApplication.answers.advert?.categories?.includes(c.id), + ) - const updateState = useCallback((newState: typeof state) => { - setState((prev) => ({ ...prev, ...newState })) - }, []) + const today = new Date() + const maxEndDate = addYears(today, 5) + const minDate = new Date() + if (minDate.getHours() >= 12) { + minDate.setDate(minDate.getDate() + 1) + } - useEffect(() => { - updateHandler() - }, [updateHandler]) - const debouncedStateUpdate = debounce(updateState, DEBOUNCE_INPUT_TIMER) + const defaultDate = currentApplication.answers.advert?.requestedDate + ? new Date(currentApplication.answers.advert.requestedDate) + .toISOString() + .split('T')[0] + : addWeekdays(today, MINIMUM_WEEKDAYS).toISOString().split('T')[0] return ( - <> - - - setState({ ...state, date })} - error={ - props.errors && - getErrorViaPath(props.errors, InputFields.publishing.date) - } - /> - - - onCategoryChange(opt?.value)} /> - - - - debouncedStateUpdate({ ...state, message: e.target.value }) - } - textarea - rows={4} - /> - - + + {selectedCategories?.map((c) => ( + onCategoryChange(c.id)} outlined key={c.id}> + {c.title} + + ))} + +
+
) } diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Signatures.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Signatures.tsx index 2174c65700a8..881ad65f48ef 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Signatures.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Signatures.tsx @@ -2,173 +2,57 @@ import { useLocale } from '@island.is/localization' import { FormGroup } from '../components/form/FormGroup' import { InputFields, OJOIFieldBaseProps } from '../lib/types' import { signatures } from '../lib/messages/signatures' +import { useState } from 'react' +import { SignatureType, SignatureTypes } from '../lib/constants' import { Tabs } from '@island.is/island-ui/core' import { CommitteeSignature } from '../components/signatures/Committee' import { RegularSignature } from '../components/signatures/Regular' -import { useCallback, useEffect, useState } from 'react' -import { useMutation } from '@apollo/client' -import { UPDATE_APPLICATION } from '@island.is/application/graphql' -import { DEBOUNCE_INPUT_TIMER, INITIAL_ANSWERS } from '../lib/constants' -import debounce from 'lodash/debounce' +import { useApplication } from '../hooks/useUpdateApplication' +import set from 'lodash/set' import { HTMLEditor } from '../components/htmlEditor/HTMLEditor' -import { - committeeSignatureTemplate, - regularSignatureTemplate, -} from '../components/htmlEditor/templates/signatures' -import { signatureConfig } from '../components/htmlEditor/config/signatureConfig' -import { useFormContext } from 'react-hook-form' -import { AdditionalSignature } from '../components/signatures/Additional' +import { getSignatureMarkup } from '../lib/utils' -type LocalState = typeof INITIAL_ANSWERS['signature'] - -export const Signatures = ({ application, errors }: OJOIFieldBaseProps) => { - const { formatMessage: f, locale } = useLocale() - - const { answers } = application - - const { setValue } = useFormContext() - - const [updateApplication] = useMutation(UPDATE_APPLICATION) - - const [selectedTab, setSelectedTab] = useState( - answers?.signature?.type ?? 'regular', - ) - - const [state, setState] = useState({ - type: answers?.signature?.type ?? 'regular', - signature: answers?.signature?.signature ?? '', - regular: answers?.signature?.regular ?? [ - { - institution: '', - date: '', - members: [ - { - name: '', - above: '', - after: '', - below: '', - }, - ], - }, - ], - committee: answers?.signature?.committee ?? { - institution: '', - date: '', - chairman: { - name: '', - above: '', - after: '', - below: '', - }, - members: [ - { - below: '', - name: '', - }, - ], +export const Signatures = ({ application }: OJOIFieldBaseProps) => { + const { formatMessage: f } = useLocale() + const { updateApplication, application: currentApplication } = useApplication( + { + applicationId: application.id, }, - additional: answers?.signature?.additional ?? '', - }) - - setValue('signature', state) - - const updateHandler = useCallback(async () => { - await updateApplication({ - variables: { - locale, - input: { - skipValidation: true, - id: application.id, - answers: { - ...application.answers, - signature: { - type: state.type, - signature: state.signature, - regular: state.regular, - committee: state.committee, - additional: state.additional, - }, - }, - }, - }, - }) - }, [application.answers, application.id, locale, state, updateApplication]) - - const updateAdditionalSignature = useCallback((newSignature: string) => { - setState((prev) => { - return { - ...prev, - additional: newSignature, - } - }) - }, []) - - const debouncedAdditionalSignatureUpdate = debounce( - updateAdditionalSignature, - DEBOUNCE_INPUT_TIMER, ) - const updateState = useCallback((newState: typeof state) => { - setState((prev) => { - return { - ...prev, - ...newState, - signature: - newState.type === 'regular' - ? regularSignatureTemplate({ - signatureGroups: newState.regular, - additionalSignature: newState.additional, - }) - : committeeSignatureTemplate({ - signature: newState.committee, - additionalSignature: newState.additional, - }), - } - }) - }, []) - - const debouncedStateUpdate = debounce(updateState, DEBOUNCE_INPUT_TIMER) - - const preview = - selectedTab === 'regular' - ? regularSignatureTemplate({ - signatureGroups: state.regular, - additionalSignature: state.additional, - }) - : committeeSignatureTemplate({ - signature: state.committee, - additionalSignature: state.additional, - }) - - useEffect(() => { - updateHandler() - }, [updateHandler]) + const [selectedTab, setSelectedTab] = useState( + (application.answers?.misc?.signatureType as SignatureType) ?? + SignatureTypes.REGULAR, + ) const tabs = [ { - id: 'regular', + id: SignatureTypes.REGULAR, label: f(signatures.tabs.regular), - content: ( - - ), + content: , }, { - id: 'committee', + id: SignatureTypes.COMMITTEE, label: f(signatures.tabs.committee), - content: ( - - ), + content: , }, ] + const onTabChangeHandler = (tabId: string) => { + if (Object.values(SignatureTypes).includes(tabId as SignatureTypes)) { + setSelectedTab(tabId as SignatureType) + + const currentAnswers = structuredClone(application.answers) + const newAnswers = set( + currentAnswers, + InputFields.misc.signatureType, + tabId, + ) + + updateApplication(newAnswers) + } + } + return ( <> { > { - updateState({ ...state, type: id }) - setValue(InputFields.signature.type, id) - setSelectedTab(id) - }} tabs={tabs} label={f(signatures.general.title)} - /> - diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Summary.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Summary.tsx index 8cd186a319f5..975ddc0f87e6 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Summary.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Summary.tsx @@ -1,81 +1,213 @@ import { useUserInfo } from '@island.is/auth/react' -import { Stack } from '@island.is/island-ui/core' -import { useQuery } from '@apollo/client' -import { Property } from '../components/property/Property' import { - DEPARTMENT_QUERY, - GET_PRICE_QUERY, - TYPE_QUERY, -} from '../graphql/queries' -import { summary } from '../lib/messages' + AlertMessage, + Box, + Bullet, + BulletList, + Stack, + Text, +} from '@island.is/island-ui/core' +import { Property } from '../components/property/Property' +import { advert, error, publishing, summary } from '../lib/messages' import { OJOIFieldBaseProps } from '../lib/types' import { useLocale } from '@island.is/localization' import { MINIMUM_WEEKDAYS } from '../lib/constants' -import { addWeekdays } from '../lib/utils' +import { addWeekdays, parseZodIssue } from '../lib/utils' +import { useCategories } from '../hooks/useCategories' +import { + advertValidationSchema, + publishingValidationSchema, + signatureValidationSchema, +} from '../lib/dataSchema' +import { useApplication } from '../hooks/useUpdateApplication' +import { ZodCustomIssue } from 'zod' +import { useType } from '../hooks/useType' +import { useDepartment } from '../hooks/useDepartment' +import { usePrice } from '../hooks/usePrice' +import { useEffect } from 'react' +import { signatures } from '../lib/messages/signatures' -export const Summary = ({ application }: OJOIFieldBaseProps) => { - const { formatMessage: f, formatDate } = useLocale() +export const Summary = ({ + application, + setSubmitButtonDisabled, +}: OJOIFieldBaseProps) => { + const { formatMessage: f, formatDate, formatNumber } = useLocale() + const { application: currentApplication } = useApplication({ + applicationId: application.id, + }) const user = useUserInfo() - const { answers } = application - - const { data, loading } = useQuery(TYPE_QUERY, { - variables: { - params: { - id: application?.answers?.advert?.type, - }, - }, + const { type, loading: loadingType } = useType({ + typeId: currentApplication.answers.advert?.typeId, }) - const { data: priceData } = useQuery(GET_PRICE_QUERY, { - variables: { id: application.id }, + const { price, loading: loadingPrice } = usePrice({ + applicationId: application.id, }) - const price = - priceData?.officialJournalOfIcelandApplicationGetPrice?.price ?? 0 + const { department, loading: loadingDepartment } = useDepartment({ + departmentId: currentApplication.answers.advert?.departmentId, + }) - const type = data?.officialJournalOfIcelandType?.type?.title + const { categories, loading: loadingCategories } = useCategories() - const { data: department } = useQuery(DEPARTMENT_QUERY, { - variables: { - params: { - id: answers?.advert?.department, - }, - }, - }) + const selectedCategories = categories?.filter((c) => + currentApplication.answers?.advert?.categories?.includes(c.id), + ) const today = new Date() const estimatedDate = addWeekdays(today, MINIMUM_WEEKDAYS) + const advertValidationCheck = advertValidationSchema.safeParse( + currentApplication.answers, + ) + + const signatureValidationCheck = signatureValidationSchema.safeParse({ + signatures: currentApplication.answers.signatures, + misc: currentApplication.answers.misc, + }) + + const publishingCheck = publishingValidationSchema.safeParse( + currentApplication.answers.advert, + ) + + useEffect(() => { + if ( + advertValidationCheck.success && + signatureValidationCheck.success && + publishingCheck.success + ) { + setSubmitButtonDisabled && setSubmitButtonDisabled(false) + } else { + setSubmitButtonDisabled && setSubmitButtonDisabled(true) + } + }, [ + advertValidationCheck, + signatureValidationCheck, + publishingCheck, + setSubmitButtonDisabled, + ]) + return ( - - - - - - - - - c.label) - .join(', ')} - /> - + <> + + + + + + + + + + + c.title).join(', ')} + /> + + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/graphql/queries.ts b/libs/application/templates/official-journal-of-iceland/src/graphql/queries.ts index 92555efea10d..49fb20dcc52b 100644 --- a/libs/application/templates/official-journal-of-iceland/src/graphql/queries.ts +++ b/libs/application/templates/official-journal-of-iceland/src/graphql/queries.ts @@ -242,10 +242,83 @@ export const PDF_URL_QUERY = gql` } ` -export const PDF_QUERY = gql` - query PdfDocument($id: String!) { - officialJournalOfIcelandApplicationGetPdf(id: $id) { - pdf +export const GET_PRESIGNED_URL_MUTATION = gql` + mutation GetPresignedUrl( + $input: OfficialJournalOfIcelandApplicationGetPresignedUrlInput! + ) { + officialJournalOfIcelandApplicationGetPresignedUrl(input: $input) { + url + } + } +` + +export const ADD_APPLICATION_ATTACHMENT_MUTATION = gql` + mutation AddApplicationAttachment( + $input: OfficialJournalOfIcelandApplicationAddApplicationAttachmentInput! + ) { + officialJournalOfIcelandApplicationAddAttachment(input: $input) { + success + } + } +` + +export const GET_APPLICATION_ATTACHMENTS_QUERY = gql` + query OfficialJournalOfIcelandApplicationGetAttachments( + $input: OfficialJournalOfIcelandApplicationGetApplicationAttachmentInput! + ) { + officialJournalOfIcelandApplicationGetAttachments(input: $input) { + attachments { + id + originalFileName + fileName + fileFormat + fileExtension + fileLocation + fileSize + } + } + } +` + +export const DELETE_APPLICATION_ATTACHMENT_MUTATION = gql` + mutation DeleteApplicationAttachment( + $input: OfficialJournalOfIcelandApplicationDeleteApplicationAttachmentInput! + ) { + officialJournalOfIcelandApplicationDeleteAttachment(input: $input) { + success + } + } +` + +export const GET_COMMENTS_QUERY = gql` + query GetComments( + $input: OfficialJournalOfIcelandApplicationGetCommentsInput! + ) { + officialJournalOfIcelandApplicationGetComments(input: $input) { + comments { + id + createdAt + internal + type + caseStatus + state + task { + from + to + title + comment + } + } + } + } +` + +export const POST_COMMENT_MUTATION = gql` + mutation AddComment( + $input: OfficialJournalOfIcelandApplicationPostCommentInput! + ) { + officialJournalOfIcelandApplicationPostComment(input: $input) { + success } } ` diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useAdvert.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useAdvert.ts new file mode 100644 index 000000000000..a2882dab7a46 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useAdvert.ts @@ -0,0 +1,37 @@ +import { useQuery } from '@apollo/client' +import { ADVERT_QUERY } from '../graphql/queries' +import { + OfficialJournalOfIcelandAdvert, + OfficialJournalOfIcelandAdvertResponse, +} from '@island.is/api/schema' + +type AdvertResponse = { + officialJournalOfIcelandAdvert: OfficialJournalOfIcelandAdvertResponse +} + +type Props = { + advertId: string | undefined | null + onCompleted?: (data: OfficialJournalOfIcelandAdvert) => void +} + +export const useAdvert = ({ advertId, onCompleted }: Props) => { + const { data, error, loading } = useQuery(ADVERT_QUERY, { + skip: !advertId, + variables: { + params: { + id: advertId, + }, + }, + onCompleted: (data) => { + if (onCompleted) { + onCompleted(data.officialJournalOfIcelandAdvert.advert) + } + }, + }) + + return { + advert: data?.officialJournalOfIcelandAdvert, + error, + loading, + } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useAdverts.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useAdverts.ts new file mode 100644 index 000000000000..561491768aaf --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useAdverts.ts @@ -0,0 +1,67 @@ +import { useQuery } from '@apollo/client' +import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE } from '../lib/constants' +import { ADVERTS_QUERY } from '../graphql/queries' +import { OfficialJournalOfIcelandAdvertsResponse } from '@island.is/api/schema' + +/** + * Fetches adverts from the API + * @param page - The page number + * @param pageSize - The number of items per page + * @param search - The search query + * @param department - The slug of the deparments to filter by + * @param type - The slug of the types to filter by + * @param category - The slug of the categories to filter by + * @param involvedParty - The slug of the involved parties to filter by + * @param dateFrom - The date to filter from + * @param dateTo - The date to filter to + */ +type Props = { + search?: string + page?: number + pageSize?: number + department?: string[] + type?: string[] + category?: string[] + involvedParty?: string[] + dateFrom?: Date + dateTo?: Date +} + +type AdvertsResponse = { + officialJournalOfIcelandAdverts: OfficialJournalOfIcelandAdvertsResponse +} + +export const useAdverts = ({ + page = DEFAULT_PAGE, + pageSize = DEFAULT_PAGE_SIZE, + search, + department, + type, + category, + involvedParty, + dateFrom, + dateTo, +}: Props) => { + const { data, loading, error } = useQuery(ADVERTS_QUERY, { + variables: { + input: { + page, + search, + pageSize, + department, + type, + category, + involvedParty, + dateFrom, + dateTo, + }, + }, + }) + + return { + adverts: data?.officialJournalOfIcelandAdverts.adverts, + paging: data?.officialJournalOfIcelandAdverts.paging, + loading, + error, + } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useCategories.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useCategories.ts new file mode 100644 index 000000000000..bb454d63eb5d --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useCategories.ts @@ -0,0 +1,26 @@ +import { useQuery } from '@apollo/client' +import { CATEGORIES_QUERY } from '../graphql/queries' +import { OfficialJournalOfIcelandAdvertsCategoryResponse } from '@island.is/api/schema' + +type CategoriesResponse = { + officialJournalOfIcelandCategories: OfficialJournalOfIcelandAdvertsCategoryResponse +} + +export const useCategories = () => { + const { data, loading, error } = useQuery( + CATEGORIES_QUERY, + { + variables: { + params: { + pageSize: 1000, + }, + }, + }, + ) + + return { + categories: data?.officialJournalOfIcelandCategories.categories, + loading, + error, + } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useComments.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useComments.ts index d3d3485748c4..76f99f5c6889 100644 --- a/libs/application/templates/official-journal-of-iceland/src/hooks/useComments.ts +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useComments.ts @@ -1,21 +1,69 @@ -import { useQuery } from '@apollo/client' -import { GET_APPLICATION_COMMENTS_QUERY } from '../graphql/queries' +import { useMutation, useQuery } from '@apollo/client' +import { OfficialJournalOfIcelandApplicationGetCommentsResponse } from '@island.is/api/schema' +import { POST_COMMENT_MUTATION, GET_COMMENTS_QUERY } from '../graphql/queries' type Props = { applicationId: string } + +type CommentsResponse = { + officialJournalOfIcelandApplicationGetComments: OfficialJournalOfIcelandApplicationGetCommentsResponse +} + +type AddCommentVariables = { + comment: string +} + +type PostCommentResponse = { + officialJournalOfIcelandApplicationPostComment: { + success: boolean + } +} + export const useComments = ({ applicationId }: Props) => { - const { data, loading, error } = useQuery(GET_APPLICATION_COMMENTS_QUERY, { - variables: { - input: { - id: applicationId, + const { data, loading, error, refetch } = useQuery( + GET_COMMENTS_QUERY, + { + variables: { + input: { + id: applicationId, + }, }, }, + ) + + const [ + addCommentMutation, + { + data: addCommentSuccess, + loading: addCommentLoading, + error: addCommentError, + }, + ] = useMutation(POST_COMMENT_MUTATION, { + onCompleted: () => { + refetch() + }, }) + const addComment = (variables: AddCommentVariables) => { + addCommentMutation({ + variables: { + input: { + id: applicationId, + comment: variables.comment, + }, + }, + }) + } + return { comments: data?.officialJournalOfIcelandApplicationGetComments.comments, loading, error, + addComment, + addCommentLoading, + addCommentError, + addCommentSuccess: + addCommentSuccess?.officialJournalOfIcelandApplicationPostComment.success, } } diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useDepartment.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useDepartment.ts new file mode 100644 index 000000000000..ee9b89e91220 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useDepartment.ts @@ -0,0 +1,30 @@ +import { useQuery } from '@apollo/client' +import { OfficialJournalOfIcelandAdvertEntity } from '@island.is/api/schema' +import { DEPARTMENT_QUERY } from '../graphql/queries' + +type Props = { + departmentId?: string +} + +type DepartmentResponse = { + department: OfficialJournalOfIcelandAdvertEntity +} + +export const useDepartment = ({ departmentId }: Props) => { + const { data, loading, error } = useQuery<{ + officialJournalOfIcelandDepartment: DepartmentResponse + }>(DEPARTMENT_QUERY, { + skip: !departmentId, + variables: { + params: { + id: departmentId, + }, + }, + }) + + return { + department: data?.officialJournalOfIcelandDepartment?.department, + loading, + error, + } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useDepartments.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useDepartments.ts new file mode 100644 index 000000000000..dee972356a9b --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useDepartments.ts @@ -0,0 +1,26 @@ +import { useQuery } from '@apollo/client' +import { DEPARTMENTS_QUERY } from '../graphql/queries' +import { OfficialJournalOfIcelandAdvertsDepartmentsResponse } from '@island.is/api/schema' + +type DepartmentsResponse = { + officialJournalOfIcelandDepartments: OfficialJournalOfIcelandAdvertsDepartmentsResponse +} + +export const useDepartments = () => { + const { data, loading, error } = useQuery( + DEPARTMENTS_QUERY, + { + variables: { + params: { + page: 1, + }, + }, + }, + ) + + return { + departments: data?.officialJournalOfIcelandDepartments.departments, + loading, + error, + } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useFileUpload.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useFileUpload.ts new file mode 100644 index 000000000000..836ca2ada877 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useFileUpload.ts @@ -0,0 +1,227 @@ +import { UploadFile } from '@island.is/island-ui/core' +import { + ADD_APPLICATION_ATTACHMENT_MUTATION, + DELETE_APPLICATION_ATTACHMENT_MUTATION, + GET_APPLICATION_ATTACHMENTS_QUERY, + GET_PRESIGNED_URL_MUTATION, +} from '../graphql/queries' +import { useMutation, useQuery } from '@apollo/client' +import { useState } from 'react' +import { ApplicationAttachmentType } from '../lib/constants' + +/** + * + * @param applicationId id of the application + * @param attachmentType type of the attachment used for constructing the presigned URL key + */ +type UseFileUploadProps = { + applicationId: string + attachmentType: ApplicationAttachmentType +} + +type GetPresignedUrlResponse = { + url: string +} + +type AddAttachmentResponse = { + success: boolean +} + +type ApplicationAttachment = { + id: string + fileName: string + originalFileName: string + fileFormat: string + fileExtension: string + fileLocation: string + fileSize: number +} + +type GetAttachmentsResponse = { + attachments: ApplicationAttachment[] +} + +/** + * Hook for uploading files to S3 + * @param props UseFileUploadProps + * @param props.applicationId id of the application + * @param props.attachmentType type of the attachment used for constructing the presigned URL key + */ +export const useFileUpload = ({ + applicationId, + attachmentType, +}: UseFileUploadProps) => { + const [files, setFiles] = useState([]) + + const [getPresignedUrlMutation] = useMutation<{ + officialJournalOfIcelandApplicationGetPresignedUrl: GetPresignedUrlResponse + }>(GET_PRESIGNED_URL_MUTATION) + + const [addApplicationMutation] = useMutation<{ + officialJournalOfIcelandApplicationAddAttachment: AddAttachmentResponse + }>(ADD_APPLICATION_ATTACHMENT_MUTATION, { + onCompleted() { + refetch() + }, + }) + + const { refetch } = useQuery<{ + officialJournalOfIcelandApplicationGetAttachments: GetAttachmentsResponse + }>(GET_APPLICATION_ATTACHMENTS_QUERY, { + variables: { + input: { + applicationId: applicationId, + attachmentType: attachmentType, + }, + }, + fetchPolicy: 'no-cache', + onCompleted(data) { + const currentFiles = + data.officialJournalOfIcelandApplicationGetAttachments.attachments.map( + (attachment) => + ({ + name: attachment.originalFileName, + size: attachment.fileSize, + type: attachment.fileFormat, + key: attachment.fileLocation, + status: 'done', + } as UploadFile), + ) + setFiles((prevFiles) => [...prevFiles, ...currentFiles]) + }, + onError() { + setFiles([]) + }, + }) + + const [deleteApplicationAttachmentMutation] = useMutation<{ + officialJournalOfIcelandApplicationDeleteAttachment: AddAttachmentResponse + }>(DELETE_APPLICATION_ATTACHMENT_MUTATION, { + onCompleted() { + refetch() + }, + }) + + /** + * + * @param newFiles comes from the onChange function on the fileInput component + */ + const onChange = (newFiles: UploadFile[]) => { + newFiles.forEach(async (file) => { + const type = file?.type?.split('/')[1] + const name = file?.name?.split('.').slice(0, -1).join('.') + + if (!type || !name) { + return + } + + const url = await getPresignedUrl(name, type) + + if (!url) { + file.status = 'error' + return + } + + const loc = new URL(url).pathname + + uploadToS3(url, file as File) + addApplicationAttachments(loc, file as File) + + file.key = loc + + setFiles((prevFiles) => [...prevFiles, file]) + }) + } + + /** + * Deletes the file from the database and S3 + */ + const onRemove = async (file: UploadFile) => { + deleteApplicationAttachmentMutation({ + variables: { + input: { + applicationId: applicationId, + key: file.key, + }, + }, + }) + + setFiles(files.filter((f) => f.key !== file.key)) + } + + /** + * Gets a presigned URL for a file + * @param name name of the file ex. myFile + * @param type type of the file ex. pdf, doc, docx... + * @returns + */ + const getPresignedUrl = async (name: string, type: string) => { + const { data } = await getPresignedUrlMutation({ + variables: { + input: { + attachmentType: attachmentType, + applicationId: applicationId, + fileName: name, + fileType: type, + }, + }, + }) + + return data?.officialJournalOfIcelandApplicationGetPresignedUrl.url + } + + /** + * Uploads a file to S3 using a presigned URL + * Used when a presigned URL has been successfully retrieved + * @param preSignedUrl presigned URL + * @param file file to upload + * @param onSuccess callback function to run on success + */ + const uploadToS3 = async (preSignedUrl: string, file: File) => { + await fetch(preSignedUrl, { + headers: { + 'Content-Type': file.type, + 'Content-Length': file.size.toString(), + }, + method: 'PUT', + body: file, + }) + } + + /** + * Adds a record in the database for the uploaded file with the presigned URL. + * Used after the file has been successfully uploaded to S3 + * @param url presigned URL + * @param file file to upload + */ + const addApplicationAttachments = (url: string, file: UploadFile) => { + const type = file?.type?.split('/')[1] + const name = file?.name?.split('.').slice(0, -1).join('.') + if (!type || !name) { + return + } + + addApplicationMutation({ + variables: { + input: { + applicationId: applicationId, + attachmentType: attachmentType, + fileName: name, + originalFileName: file.name, + fileFormat: type, + fileExtension: type, + fileLocation: url, + fileSize: file.size, + }, + }, + onCompleted() { + file.status = 'done' + }, + onError() { + file.status = 'error' + }, + }) + } + + return { files, onChange, onRemove } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/usePrice.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/usePrice.ts new file mode 100644 index 000000000000..03551902f72e --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/usePrice.ts @@ -0,0 +1,24 @@ +import { useQuery } from '@apollo/client' +import { GET_PRICE_QUERY } from '../graphql/queries' +import { OfficialJournalOfIcelandApplicationGetPriceResponse } from '@island.is/api/schema' + +type Props = { + applicationId: string +} + +export const usePrice = ({ applicationId }: Props) => { + const { data, loading, error } = useQuery<{ + officialJournalOfIcelandApplicationGetPrice: OfficialJournalOfIcelandApplicationGetPriceResponse + }>(GET_PRICE_QUERY, { + skip: !applicationId, + variables: { + id: applicationId, + }, + }) + + return { + price: data?.officialJournalOfIcelandApplicationGetPrice.price ?? 0, + loading, + error, + } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useType.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useType.ts new file mode 100644 index 000000000000..40930127c1c7 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useType.ts @@ -0,0 +1,30 @@ +import { useQuery } from '@apollo/client' +import { TYPE_QUERY } from '../graphql/queries' +import { OfficialJournalOfIcelandAdvertType } from '@island.is/api/schema' + +type Props = { + typeId?: string +} + +type TypeResponse = { + type: OfficialJournalOfIcelandAdvertType +} + +export const useType = ({ typeId }: Props) => { + const { data, loading, error } = useQuery<{ + officialJournalOfIcelandType: TypeResponse + }>(TYPE_QUERY, { + skip: !typeId, + variables: { + params: { + id: typeId, + }, + }, + }) + + return { + type: data?.officialJournalOfIcelandType?.type, + loading, + error, + } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts new file mode 100644 index 000000000000..61d05bb9a6c7 --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts @@ -0,0 +1,45 @@ +import { NetworkStatus, useQuery } from '@apollo/client' +import { OfficialJournalOfIcelandAdvertsTypesResponse } from '@island.is/api/schema' + +import { TYPES_QUERY } from '../graphql/queries' + +type UseTypesParams = { + initalDepartmentId?: string +} + +type TypesResponse = { + officialJournalOfIcelandTypes: OfficialJournalOfIcelandAdvertsTypesResponse +} + +type TypesVariables = { + params: { + department: string + page?: number + pageSize?: number + } +} + +export const useTypes = ({ + initalDepartmentId: departmentId, +}: UseTypesParams) => { + const { data, loading, error, refetch, networkStatus } = useQuery< + TypesResponse, + TypesVariables + >(TYPES_QUERY, { + variables: { + params: { + department: departmentId ?? '', + page: 1, + pageSize: 1000, + }, + }, + notifyOnNetworkStatusChange: true, + }) + + return { + useLazyTypes: refetch, + types: data?.officialJournalOfIcelandTypes.types, + loading: loading || networkStatus === NetworkStatus.refetch, + error, + } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useUpdateApplication.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useUpdateApplication.ts new file mode 100644 index 000000000000..473c12aac10b --- /dev/null +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useUpdateApplication.ts @@ -0,0 +1,79 @@ +import { useMutation, useQuery } from '@apollo/client' +import { + UPDATE_APPLICATION, + APPLICATION_APPLICATION, +} from '@island.is/application/graphql' +import { useLocale } from '@island.is/localization' +import { partialSchema } from '../lib/dataSchema' +import { OJOIApplication } from '../lib/types' +import debounce from 'lodash/debounce' +import { DEBOUNCE_INPUT_TIMER } from '../lib/constants' + +type OJOIUseApplicationParams = { + applicationId?: string +} + +export const useApplication = ({ applicationId }: OJOIUseApplicationParams) => { + const { locale } = useLocale() + + const { + data: application, + loading: applicationLoading, + error: applicationError, + refetch: refetchApplication, + } = useQuery(APPLICATION_APPLICATION, { + variables: { + locale: locale, + input: { + id: applicationId, + }, + }, + }) + + const [ + mutation, + { data: updateData, loading: updateLoading, error: updateError }, + ] = useMutation(UPDATE_APPLICATION) + + const updateApplication = async (input: partialSchema, cb?: () => void) => { + await mutation({ + variables: { + locale, + input: { + id: applicationId, + answers: { + ...input, + }, + }, + }, + }) + + cb && cb() + } + + const debouncedUpdateApplication = debounce( + updateApplication, + DEBOUNCE_INPUT_TIMER, + ) + + const debouncedOnUpdateApplicationHandler = ( + input: partialSchema, + cb?: () => void, + ) => { + debouncedUpdateApplication.cancel() + debouncedUpdateApplication(input, cb) + } + + return { + application: application?.applicationApplication as OJOIApplication, + applicationLoading, + applicationError, + updateData, + updateLoading, + updateError, + isLoading: applicationLoading || updateLoading, + debouncedOnUpdateApplicationHandler, + updateApplication, + refetchApplication, + } +} diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/OJOIApplication.ts b/libs/application/templates/official-journal-of-iceland/src/lib/OJOIApplication.ts index f68c75a2a6e1..0c479ddd9e1f 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/OJOIApplication.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/OJOIApplication.ts @@ -11,7 +11,7 @@ import { InstitutionNationalIds, defineTemplateApi, } from '@island.is/application/types' -import { dataSchema } from './dataSchema' +import { partialSchema } from './dataSchema' import { general } from './messages' import { TemplateApiActions } from './types' import { Features } from '@island.is/feature-flags' @@ -47,7 +47,7 @@ const OJOITemplate: ApplicationTemplate< translationNamespaces: [ ApplicationConfigurations.OfficialJournalOfIceland.translation, ], - dataSchema: dataSchema, + dataSchema: partialSchema, allowMultipleApplicationsInDraft: true, stateMachineOptions: { actions: { @@ -99,18 +99,6 @@ const OJOITemplate: ApplicationTemplate< status: 'inprogress', progress: 0.66, lifecycle: pruneAfterDays(90), - onEntry: [ - defineTemplateApi({ - action: TemplateApiActions.departments, - externalDataId: 'departments', - order: 1, - }), - defineTemplateApi({ - action: TemplateApiActions.types, - externalDataId: 'types', - order: 2, - }), - ], roles: [ { id: Roles.APPLICANT, diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/constants.ts b/libs/application/templates/official-journal-of-iceland/src/lib/constants.ts index a6ae4f0e79c2..4ee1ecaaf486 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/constants.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/constants.ts @@ -1,6 +1,6 @@ export const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i -export const UPLOAD_ACCEPT = '.pdf' +export const ALLOWED_FILE_TYPES = ['.pdf', '.doc', '.docx'] export const FILE_SIZE_LIMIT = 10000000 @@ -12,12 +12,19 @@ export enum AnswerOption { NO = 'no', } +export enum ApplicationAttachmentType { + ORIGINAL = 'frumrit', + ADDITIONS = 'fylgiskjol', +} + +export const DEFAULT_PAGE = 1 +export const DEFAULT_PAGE_SIZE = 10 + export const MINIMUM_WEEKDAYS = 10 export enum Routes { - TEST = 'test', - COMMENTS = 'comments', REQUIREMENTS = 'requirements', + COMMENTS = 'comments', ADVERT = 'advert', SIGNATURE = 'signature', ATTACHMENTS = 'attachments', @@ -26,6 +33,7 @@ export enum Routes { PUBLISHING = 'publishing', SUMMARY = 'summary', COMPLETE = 'complete', + MISC = 'misc', } // this will be replaced with correct values once the api is ready @@ -38,7 +46,7 @@ export enum TypeIds { } export const MEMBER_INDEX = '{memberIndex}' -export const INSTITUTION_INDEX = '{institutionIndex}' +export const SIGNATURE_INDEX = '{institutionIndex}' export const INTERVAL_TIMER = 3000 export const DEBOUNCE_INPUT_TIMER = 333 @@ -48,66 +56,21 @@ export enum FileNames { ADDITIONS = 'additions', } -export const INITIAL_ANSWERS = { - [Routes.TEST]: { - name: '', - department: '', - job: '', - }, - [Routes.REQUIREMENTS]: { - approveExternalData: false, - }, - [Routes.ADVERT]: { - department: '', - type: '', - subType: '', - title: '', - template: '', - document: '', - }, - [Routes.SIGNATURE]: { - type: 'regular', - signature: '', - regular: [ - { - institution: '', - date: '', - members: [ - { - above: '', - name: '', - below: '', - after: '', - }, - ], - }, - ], - committee: { - institution: '', - date: '', - chairman: { - above: '', - name: '', - after: '', - below: '', - }, - members: [ - { - name: '', - below: '', - }, - ], - }, - additional: '', - }, - [Routes.ATTACHMENTS]: { - files: [], - fileNames: [], - }, - [Routes.PUBLISHING]: { - date: '', - contentCategories: [], - communicationChannels: [], - message: '', - }, +export const OJOI_INPUT_HEIGHT = 64 + +export type SignatureType = 'regular' | 'committee' +export enum SignatureTypes { + REGULAR = 'regular', + COMMITTEE = 'committee', } + +export const ONE = 1 +export const MINIMUM_REGULAR_SIGNATURE_MEMBER_COUNT = 1 +export const DEFAULT_REGULAR_SIGNATURE_MEMBER_COUNT = 1 +export const MAXIMUM_REGULAR_SIGNATURE_MEMBER_COUNT = 10 +export const MINIMUM_REGULAR_SIGNATURE_COUNT = 1 +export const DEFAULT_REGULAR_SIGNATURE_COUNT = 1 +export const MAXIMUM_REGULAR_SIGNATURE_COUNT = 3 +export const MINIMUM_COMMITTEE_SIGNATURE_MEMBER_COUNT = 2 +export const DEFAULT_COMMITTEE_SIGNATURE_MEMBER_COUNT = 2 +export const MAXIMUM_COMMITTEE_SIGNATURE_MEMBER_COUNT = 10 diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts b/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts index 92cfcb487db0..b4d6f02e29ad 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts @@ -1,197 +1,330 @@ import { z } from 'zod' import { error } from './messages' -import { InputFields } from './types' -import { - TypeIds, - INSTITUTION_INDEX, - MEMBER_INDEX, - FileNames, - AnswerOption, -} from './constants' - -const FileSchema = z.object({ - name: z.string(), - key: z.string(), - url: z.string().optional(), -}) +import { AnswerOption, SignatureTypes } from './constants' +import { institution } from '../components/signatures/Signatures.css' +import { MessageDescriptor } from 'react-intl' + +export const memberItemSchema = z + .object({ + name: z.string().optional(), + before: z.string().optional(), + below: z.string().optional(), + above: z.string().optional(), + after: z.string().optional(), + }) + .partial() + +export const membersSchema = z.array(memberItemSchema).optional() + +export const regularSignatureItemSchema = z + .object({ + date: z.string().optional(), + institution: z.string().optional(), + members: membersSchema.optional(), + html: z.string().optional(), + }) + .partial() + +export const regularSignatureSchema = z + .array(regularSignatureItemSchema) + .optional() + +export const signatureInstitutionSchema = z.enum(['institution', 'date']) + +export const committeeSignatureSchema = regularSignatureItemSchema + .extend({ + chairman: memberItemSchema.optional(), + }) + .partial() + +export const channelSchema = z + .object({ + email: z.string(), + phone: z.string(), + }) + .partial() + +const advertSchema = z + .object({ + departmentId: z.string().optional(), + typeId: z.string().optional(), + title: z.string().optional(), + html: z.string().optional(), + requestedDate: z.string().optional(), + categories: z.array(z.string()).optional(), + channels: z.array(channelSchema).optional(), + message: z.string().optional(), + }) + .partial() -const getPath = (path: string) => path.split('.').slice(1) +const miscSchema = z + .object({ + signatureType: z.string().optional(), + selectedTemplate: z.string().optional(), + }) + .partial() -export const dataSchema = z.object({ +export const partialSchema = z.object({ requirements: z .object({ approveExternalData: z.string(), }) .refine((schema) => schema.approveExternalData === AnswerOption.YES, { params: error.dataGathering, - path: getPath(InputFields.requirements.approveExternalData), + path: ['approveExternalData'], }), - advert: z + advert: advertSchema.optional(), + signatures: z .object({ - department: z.string().optional(), - type: z.string().optional(), - title: z.string().optional(), - document: z.string().optional(), - template: z.string().optional(), - subType: z.string().optional(), + additionalSignature: z.object({ + committee: z.string().optional(), + regular: z.string().optional(), + }), + regular: z.array(regularSignatureItemSchema).optional(), + committee: committeeSignatureSchema.optional(), }) - .superRefine((advert, ctx) => { - if (advert.type === TypeIds.REGLUGERDIR) { - if (!advert.subType) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - params: error.emptyFieldError, - path: getPath(InputFields.advert.subType), - }) - } - } + .partial() + .optional(), + misc: miscSchema.optional(), +}) + +// We make properties optional to throw custom error messages +export const advertValidationSchema = z.object({ + advert: z.object({ + departmentId: z + .string() + .optional() + .refine((value) => value && value.length > 0, { + params: error.missingDepartment, + }), + typeId: z + .string() + .optional() + .refine((value) => value && value.length > 0, { + params: error.missingType, + }), + title: z + .string() + .optional() + .refine((value) => value && value.length > 0, { + params: error.missingTitle, + }), + html: z + .string() + .optional() + .refine((value) => value && value.length > 0, { + params: error.missingHtml, + }), + }), +}) + +export const publishingValidationSchema = z.object({ + requestedDate: z + .string() + .optional() + .refine((value) => value && value.length > 0, { + // TODO: Add date validation + params: error.missingRequestedDate, }), - signature: z - .object({ - type: z.string().optional(), - signature: z.string().optional(), - regular: z - .array( - z.object({ - institution: z.string(), - date: z.string(), - members: z.array( - z.object({ - above: z.string(), - name: z.string(), - below: z.string(), - after: z.string(), - }), - ), - }), - ) - .optional(), - committee: z + categories: z + .array(z.string()) + .optional() + .refine((value) => Array.isArray(value) && value.length > 0, { + params: error.noCategorySelected, + }), +}) + +export const signatureValidationSchema = z + .object({ + signatures: z.object({ + additionalSignature: z .object({ - institution: z.string(), - date: z.string(), - chairman: z.object({ - above: z.string(), - name: z.string(), - below: z.string(), - after: z.string(), - }), - members: z.array( - z.object({ - name: z.string(), - below: z.string(), - }), - ), + committee: z.string().optional(), + regular: z.string().optional(), }) .optional(), - additional: z.string().optional(), + regular: z.array(regularSignatureItemSchema).optional(), + committee: committeeSignatureSchema.optional(), + }), + misc: miscSchema.optional(), + }) + .superRefine((schema, context) => { + const signatureType = schema.misc?.signatureType + + if (!signatureType) { + context.addIssue({ + code: z.ZodIssueCode.custom, + params: error.missingSignatureType, + path: ['misc', 'signatureType'], + }) + } + + let hasRegularIssues = false + let hasCommitteeIssues = false + + if (signatureType === SignatureTypes.REGULAR) { + hasRegularIssues = validateRegularSignature( + schema.signatures.regular, + context, + ) + } + + if (signatureType === SignatureTypes.COMMITTEE) { + hasCommitteeIssues = validateCommitteeSignature( + schema.signatures.committee as z.infer, + context, + ) + } + + if (!hasRegularIssues && !hasCommitteeIssues) { + return false + } + + return true + }) + +const validateMember = ( + schema: z.infer, + context: z.RefinementCtx, + params?: MessageDescriptor, +) => { + if (!schema || !schema.name) { + context.addIssue({ + code: z.ZodIssueCode.custom, + params: params ? params : error.missingSignatureMember, + }) + + return false + } + + return true +} + +const validateInstitutionAndDate = ( + institution: string | undefined, + date: string | undefined, + context: z.RefinementCtx, +) => { + if (!institution) { + context.addIssue({ + code: z.ZodIssueCode.custom, + params: error.missingSignatureInstitution, }) - .superRefine((signature, ctx) => { - switch (signature.type) { - case 'regular': - signature.regular?.forEach((institution, index) => { - // required fields are institution, date, member.name - - if (!institution.institution) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - params: error.emptyFieldError, - path: InputFields.signature.regular.institution - .replace(INSTITUTION_INDEX, `${index}`) - .split('.') - .slice(1), - }) - } - - if (!institution.date) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - params: error.emptyFieldError, - path: InputFields.signature.regular.date - .replace(INSTITUTION_INDEX, `${index}`) - .split('.') - .slice(1), - }) - } - - institution.members?.forEach((member, memberIndex) => { - if (!member.name) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - params: error.emptyFieldError, - path: InputFields.signature.regular.members.name - .replace(INSTITUTION_INDEX, `${index}`) - .replace(MEMBER_INDEX, `${memberIndex}`) - .split('.') - .slice(1), - }) - } - }) - }) - - break - case 'committee': - // required fields are institution, date, chairman.name, members.name - - if (!signature.committee?.institution) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - params: error.emptyFieldError, - path: getPath(InputFields.signature.committee.institution), - }) - } - - if (!signature.committee?.date) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - params: error.emptyFieldError, - path: getPath(InputFields.signature.committee.date), - }) - } - - if (!signature.committee?.chairman.name) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - params: error.emptyFieldError, - path: getPath(InputFields.signature.committee.chairman.name), - }) - } - - signature.committee?.members?.forEach((member, index) => { - if (!member.name) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - params: error.emptyFieldError, - path: InputFields.signature.committee.members.name - .replace(MEMBER_INDEX, `${index}`) - .split('.') - .slice(1), - }) - } - }) - break + + return false + } + + if (!date) { + context.addIssue({ + code: z.ZodIssueCode.custom, + params: error.missingSignatureDate, + }) + + return false + } + + return true +} + +const validateRegularSignature = ( + schema: z.infer, + context: z.RefinementCtx, +) => { + if (!schema || (Array.isArray(schema) && schema.length === 0)) { + context.addIssue({ + code: z.ZodIssueCode.custom, + params: error.signaturesValidationError, + }) + + return false + } + + const validSignatures = schema + ?.map((signature) => { + // institution and date are required + let hasValidInstitutionAndDate = true + let hasValidMembers = true + + hasValidInstitutionAndDate = validateInstitutionAndDate( + signature.institution, + signature.date, + context, + ) + + if (!signature.members && !Array.isArray(signature.members)) { + context.addIssue({ + code: z.ZodIssueCode.custom, + params: error.noSignatureMembers, + }) } - }), - attachments: z.object({ - files: z.array(FileSchema), - fileNames: z.enum([FileNames.ADDITIONS, FileNames.DOCUMENT]), - }), - publishing: z.object({ - date: z.string().optional(), - contentCategories: z.array( - z.object({ - label: z.string(), - value: z.string(), - }), - ), - communicationChannels: z.array( - z.object({ - email: z.string(), - phone: z.string(), - }), - ), - message: z.string().optional(), - }), -}) -export type answerSchemas = z.infer + hasValidMembers = + signature.members + ?.map((member) => validateMember(member, context)) + .every((isValid) => isValid) ?? false + + return hasValidInstitutionAndDate && hasValidMembers + }) + .every((isValid) => isValid) + + return validSignatures +} + +const validateCommitteeSignature = ( + schema: z.infer, + context: z.RefinementCtx, +) => { + if (!schema) { + context.addIssue({ + code: z.ZodIssueCode.custom, + params: error.signaturesValidationError, + }) + } + + let hasValidInstitutionAndDate = true + let hasValidChairman = true + let hasValidMembers = true + + hasValidInstitutionAndDate = validateInstitutionAndDate( + schema.institution, + schema.date, + context, + ) + + hasValidChairman = validateMember( + schema.chairman as z.infer, + context, + error.missingChairmanName, + ) + + hasValidMembers = + schema.members + ?.map((member) => + validateMember(member, context, error.missingCommitteeMemberName), + ) + .every((isValid) => isValid) ?? false + + return hasValidInstitutionAndDate && hasValidChairman && hasValidMembers +} + +type Flatten = T extends any[] ? T[number] : T + +type MapProps = { + [K in keyof T]: T[K] +} + +export type partialSchema = z.infer + +export type partialRegularSignatureSchema = Flatten< + z.infer +> + +export type partialCommitteeSignatureSchema = MapProps< + z.infer +> + +export type validationSchema = z.infer + +export const signatureProperties = committeeSignatureSchema.keyof() + +export const sharedSignatureProperties = signatureProperties diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/advert.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/advert.ts index ac24eaca9956..fcbcd5e3be03 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/advert.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/advert.ts @@ -10,7 +10,7 @@ export const advert = { intro: { id: 'ojoi.application:advert.general.intro', defaultMessage: - 'Veldu deild og tegund birtingar í fellilistanum hér að neðan og skráðu heiti auglýsingar í viðeigandi reit. Tegundarheitið birtist sjálfkrafa í hástöfum í fyrirsögn og titillinn í næstu línu. Efni auglýsinga er sett í ritilinn hér að neðan og skal vanda alla uppsetningu, setja inn töluliði, töflur o.þ.h. Til einföldunar við vinnslu meginmáls getur þú valið sniðmát og aðlagað það að þinni auglýsingu eða sótt eldri auglýsingu og breytt henni.', + 'Veldu deild og tegund birtingar í fellilistanum hér að neðan og skráðu heiti innsendingar í viðeigandi reit. Tegundarheitið birtist sjálfkrafa í hástöfum í fyrirsögn og titillinn í næstu línu. Efni innsendingar er sett í ritilinn hér að neðan og skal vanda alla uppsetningu, setja inn töluliði, töflur o.þ.h. Til einföldunar við vinnslu meginmáls getur þú valið sniðmát og aðlagað það að þinni innsendingu eða sótt eldri innsendingar og breytt henni.', description: 'Intro of the advert form', }, section: { @@ -73,12 +73,12 @@ export const advert = { title: defineMessages({ label: { id: 'ojoi.application:advert.inputs.title.label', - defaultMessage: 'Heiti auglýsingar', + defaultMessage: 'Titill innsendingar', description: 'Label for the title input', }, placeholder: { id: 'ojoi.application:advert.inputs.title.placeholder', - defaultMessage: 'Skráðu heiti auglýsinga', + defaultMessage: 'Skráðu heiti innsendingar', description: 'Placeholder for the title input', }, }), @@ -90,7 +90,7 @@ export const advert = { }, placeholder: { id: 'ojoi.application:advert.inputs.template.placeholder', - defaultMessage: 'Fyrirmynd auglýsinga', + defaultMessage: 'Fyrirmynd innsendingar', description: 'Placeholder for the template input', }, }), diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/comments.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/comments.ts index 80697969e702..c204aad33321 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/comments.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/comments.ts @@ -19,26 +19,26 @@ export const comments = { description: 'Title of comments section', }, }), - errors: defineMessages({ - fetchComments: { - id: 'ojoi.application:comments.errors.fetchComments', - defaultMessage: 'Villa kom upp við að sækja athugasemdir', - description: 'Error fetching comments', + warnings: defineMessages({ + noCommentsTitle: { + id: 'ojoi.application:comments.warnings.noComments', + defaultMessage: 'Engar athugasemdir', + description: 'No comments', }, - fetchCommentsMessage: { - id: 'ojoi.application:comments.errors.fetchCommentsMessage', - defaultMessage: 'Ekki tókst að sækja athugasemdir, reynið aftur síðar', - description: 'Error fetching comments message', + noCommentsMessage: { + id: 'ojoi.application:comments.warnings.noCommentsMessage', + defaultMessage: 'Engar athugasemdir eru skráðar á þessa innsendingu.', + description: 'No comments message', }, - addComment: { - id: 'ojoi.application:comments.errors.addComment', + postCommentFailedTitle: { + id: 'ojoi.application:comments.warnings.postCommentFailedTitle', defaultMessage: 'Ekki tókst að vista athugasemd', - description: 'Error adding comment', + description: 'Post comment failed title', }, - emptyComments: { - id: 'ojoi.application:comments.errors.emptyComments', - defaultMessage: 'Engar athugasemdir eru á þessari umsókn', - description: 'No comments on this application', + postCommentFailedMessage: { + id: 'ojoi.application:comments.warnings.postCommentFailedMessage', + defaultMessage: 'Ekki tókst að vista athugasemd, reyndu aftur síðar.', + description: 'Post comment failed message', }, }), dates: defineMessages({ diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/error.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/error.ts index 567d8088de0c..69b527a4b5e1 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/error.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/error.ts @@ -1,11 +1,152 @@ import { defineMessages } from 'react-intl' export const error = defineMessages({ + fetchCommentsFailedTitle: { + id: 'ojoi.application:error.fetchCommentsFailedTitle', + defaultMessage: 'Ekki tókst að sækja athugasemdir', + description: 'Error message when fetching comments fails', + }, + fetchCommentsFailedMessage: { + id: 'ojoi.application:error.fetchCommentsFailedMessage', + defaultMessage: + 'Villa kom upp við að sækja athugasemdir, reyndu aftur síðar', + description: 'Error message when fetching comments fails', + }, + fetchAdvertFailed: { + id: 'ojoi.application:error.fetchAdvertFailed', + defaultMessage: 'Ekki tókst að sækja auglýsingu', + description: 'Error message when fetching advert fails', + }, + fetchAdvertFailedMessage: { + id: 'ojoi.application:error.fetchAdvertFailedMessage', + defaultMessage: 'Villa kom upp við að sækja auglýsingu, reyndu aftur síðar', + description: 'Error message when fetching advert fails', + }, + fetchApplicationFailedTitle: { + id: 'ojoi.application:error.fetchApplicationFailedTitle', + defaultMessage: 'Ekki tókst að sækja umsókn', + description: 'Error message when fetching application fails', + }, + fetchApplicationFailedMessage: { + id: 'ojoi.application:error.fetchApplicationFailedMessage', + defaultMessage: 'Villa kom upp við að sækja umsókn, reyndu aftur síðar', + description: 'Error message when fetching application fails', + }, + missingChairmanName: { + id: 'ojoi.application:error.missingChairmanName', + defaultMessage: 'Nafn formanns vantar', + description: 'Error message when chairman name is missing', + }, + missingCommitteeMemberName: { + id: 'ojoi.application:error.missingCommitteeMemberName', + defaultMessage: 'Nafn nefndarmanns vantar', + description: 'Error message when committee member name is missing', + }, + missingSignatureInstitution: { + id: 'ojoi.application:error.missingSignatureInstitution', + defaultMessage: 'Nafn stofnunar vantar', + description: 'Error message when signature institution is missing', + }, + missingSignatureDate: { + id: 'ojoi.application:error.missingSignatureDate', + defaultMessage: 'Dagsetning undirskriftar vantar', + description: 'Error message when signature date is missing', + }, + missingSignatureType: { + id: 'ojoi.application:error.missingSignatureType', + defaultMessage: 'Tegund undirskriftar vantar', + description: 'Error message when signature type is missing', + }, + missingFieldsTitle: { + id: 'ojoi.application:error.missingFieldsTitle', + defaultMessage: 'Fylla þarf út eftirfarandi reiti í {x}', + description: 'Error message when fields are missing', + }, + missingSignatureFieldsMessage: { + id: 'ojoi.application:error.missingSignatureFieldsMessage', + defaultMessage: 'Undirritunarkafli er undir {x}', + description: 'Error message when signature fields are missing', + }, + noSignatureMembers: { + id: 'ojoi.application:error.noSignatureMembers', + defaultMessage: 'Engin undirskriftarmenn valdir', + description: 'Error message when no signature members are selected', + }, + missingSignatureMember: { + id: 'ojoi.application:error.missingSignatureMember', + defaultMessage: 'Nafn undirskriftar meðlims vantar', + description: 'Error message when signature member is missing', + }, + noCategorySelected: { + id: 'ojoi.application:error.noCategorySelected', + defaultMessage: 'Enginn efnisflokkur valinn, vinsamlegast veldu efnisflokk', + description: 'Error message when no category is selected', + }, + missingType: { + id: 'ojoi.application:error.missingType', + defaultMessage: 'Velja þarf tegund innsendingar', + description: 'Error message when type is missing', + }, + missingDepartment: { + id: 'ojoi.application:error.missingDepartment', + defaultMessage: 'Velja þarf deild innsendingar', + description: 'Error message when department is missing', + }, + missingTitle: { + id: 'ojoi.application:error.missingTitle', + defaultMessage: 'Fylla þarf út titill innsendingar', + description: 'Error message when title is missing', + }, + missingHtml: { + id: 'ojoi.application:error.missingHtml', + defaultMessage: 'Innihald innsendingar má ekki vera autt', + description: 'Error message when html is missing', + }, + missingHtmlMessage: { + id: 'ojoi.application:error.missingHtmlMessage', + defaultMessage: 'Innsending samanstendur af eftirfarandi reitum', + description: 'Error message when html is missing', + }, + missingRequestedDate: { + id: 'ojoi.application:error.missingRequestedDate', + defaultMessage: 'Útgáfudagsetning má ekki vera tóm', + description: 'Error message when requested date is missing', + }, + applicationValidationError: { + id: 'ojoi.application:error.applicationValidationError', + defaultMessage: 'Umsókn er ekki rétt útfyllt', + description: 'Error message when application is not valid', + }, + signaturesValidationError: { + id: 'ojoi.application:error.signaturesValidationError', + defaultMessage: 'Undirskriftir eru ekki réttar', + description: 'Error message when signatures are not valid', + }, dataSubmissionErrorTitle: { id: 'ojoi.application:error.dataSubmissionErrorTitle', defaultMessage: 'Villa kom upp við vistun gagna', description: 'Error message when data is not submitted', }, + fetchXFailedTitle: { + id: 'ojoi.application:error.fetchXFailedTitle', + defaultMessage: 'Ekki tókst að sækja {x}', + description: 'Error message when fetching x fails', + }, + fetchXFailedMessage: { + id: 'ojoi.application:error.fetchXFailedMessage', + defaultMessage: 'Villa kom upp við að sækja {x}', + description: 'Error message when fetching x fails', + }, + fetchFailedTitle: { + id: 'ojoi.application:error.fetchFailedTitle', + defaultMessage: 'Ekki tókst að sækja gögn', + description: 'Error message when fetching fails', + }, + fetchFailedMessage: { + id: 'ojoi.application:error.fetchFailedMessage', + defaultMessage: 'Villa kom upp við að sækja gögn', + description: 'Error message when fetching fails', + }, xIsNotValid: { id: 'ojoi.application:error.xIsNotValid', defaultMessage: '{x} er ekki gilt', diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/index.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/index.ts index a61e3a9cdf36..125c0744b8c7 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/index.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/index.ts @@ -9,3 +9,5 @@ export * from './requirements' export * from './preview' export * from './publishing' export * from './summary' +export * from './signatures' +export * from './comments' diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/preview.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/preview.ts index d0c399b4025a..7f12cc6c3987 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/preview.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/preview.ts @@ -19,6 +19,13 @@ export const preview = { description: 'Title of the preview section', }, }), + errors: defineMessages({ + noContent: { + id: 'ojoi.application:preview.errors.noContent', + defaultMessage: 'Innihald innsendingar er ekki útfyllt', + description: 'Error message when content is missing', + }, + }), buttons: defineMessages({ fetchPdf: { id: 'ojoi.application:preview.buttons.fetchPdf', diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/publishing.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/publishing.ts index f52271e8c72a..5e4ccbb90bc1 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/publishing.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/publishing.ts @@ -68,6 +68,11 @@ export const publishing = { defaultMessage: 'Efnisflokkar', description: 'Label of the content categories input', }, + placeholder: { + id: 'ojoi.application:publishing.inputs.contentCategories.placeholder', + defaultMessage: 'Veldu efnisflokka', + description: 'Placeholder of the content categories input', + }, }), messages: defineMessages({ label: { diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/signatures.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/signatures.ts index 037c9a484637..3c82160a966c 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/signatures.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/signatures.ts @@ -13,6 +13,11 @@ export const signatures = { 'Hér má velja þá uppsetningu undirrritana sem best á við. Mikilvægt er að tryggja samræmi við frumtexta, til dæmis varðandi stað og dagsetningu.', description: 'Intro of the signatures section', }, + section: { + id: 'ojoi.application:signatures.general.section', + defaultMessage: 'Undirritunarkafl{abbreviation}', + description: 'Title of the signatures section', + }, }), headings: defineMessages({ signedBy: { @@ -84,7 +89,7 @@ export const signatures = { }, placeholder: { id: 'ojoi.application:signatures.inputs.institution.placeholder', - defaultMessage: 'Veldu stofnun', + defaultMessage: 'Nafn stofnunar eða staðsetning', description: 'Placeholder for the institution input', }, }), diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/summary.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/summary.ts index 83240b61b3b3..33e74bc4896c 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/summary.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/summary.ts @@ -32,7 +32,7 @@ export const summary = { }, title: { id: 'ojoi.application:summary.properties.title', - defaultMessage: 'Heiti auglýsingar', + defaultMessage: 'Heiti innsendingar', description: 'Title of the advertisement', }, department: { diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/types.ts b/libs/application/templates/official-journal-of-iceland/src/lib/types.ts index d118cb9707ed..ab0c6d2a68a3 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/types.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/types.ts @@ -1,98 +1,50 @@ import { Application, FieldBaseProps } from '@island.is/application/types' -import { type answerSchemas } from './dataSchema' -import { INSTITUTION_INDEX, MEMBER_INDEX, Routes } from './constants' +import { Routes } from './constants' import { - OfficialJournalOfIcelandAdvert, OfficialJournalOfIcelandAdvertEntity, OfficialJournalOfIcelandPaging, } from '@island.is/api/schema' +import { partialSchema } from './dataSchema' export const InputFields = { - [Routes.TEST]: { - name: 'test.name', - department: 'test.department', - job: 'test.job', - }, [Routes.REQUIREMENTS]: { approveExternalData: 'requirements.approveExternalData', }, [Routes.ADVERT]: { - department: 'advert.department', - type: 'advert.type', - subType: 'advert.subType', + departmentId: 'advert.departmentId', + typeId: 'advert.typeId', title: 'advert.title', - template: 'advert.template', - document: 'advert.document', + html: 'advert.html', + requestedDate: 'advert.requestedDate', + categories: 'advert.categories', + channels: 'advert.channels', + message: 'advert.message', }, [Routes.SIGNATURE]: { - type: 'signature.type', - contents: 'signature.contents', - regular: { - institution: `signature.regular-${INSTITUTION_INDEX}.institution`, - date: `signature.regular-${INSTITUTION_INDEX}.date`, - members: { - above: `signature.regular-${INSTITUTION_INDEX}.members-${MEMBER_INDEX}.above`, - name: `signature.regular-${INSTITUTION_INDEX}.members-${MEMBER_INDEX}.name`, - below: `signature.regular-${INSTITUTION_INDEX}.members-${MEMBER_INDEX}.below`, - after: `signature.regular-${INSTITUTION_INDEX}.members-${MEMBER_INDEX}.after`, - }, - }, - committee: { - institution: 'signature.committee.institution', - date: 'signature.committee.date', - chairman: { - above: 'signature.committee.chairman.above', - name: 'signature.committee.chairman.name', - after: 'signature.committee.chairman.after', - below: 'signature.committee.chairman.below', - }, - members: { - name: `signature.committee.members-${MEMBER_INDEX}.name`, - below: `signature.committee.members-${MEMBER_INDEX}.below`, - }, + regular: 'signatures.regular', + committee: 'signatures.committee', + additionalSignature: { + regular: 'signatures.additionalSignature.regular', + committee: 'signatures.additionalSignature.committee', }, - additonalSignature: 'signature.additonalSignature', - }, - [Routes.ATTACHMENTS]: { - files: 'additionsAndDocuments.files', - fileNames: 'additionsAndDocuments.fileNames', }, - [Routes.ORIGINAL]: { - files: 'original.files', + [Routes.MISC]: { + signatureType: 'misc.signatureType', + selectedTemplate: 'misc.selectedTemplate', }, - [Routes.PUBLISHING]: { - date: 'publishing.date', - fastTrack: 'publishing.fastTrack', - contentCategories: 'publishing.contentCategories', - communicationChannels: 'publishing.communicationChannels', - message: 'publishing.message', - }, -} - -export type LocalError = { - type: string - message: string } -type Option = { - id: string - title: string - slug: string -} - -export type AdvertOption = { - [key in Key]: Array