diff --git a/apps/application-system/api/project.json b/apps/application-system/api/project.json index bc2b54ec13cb..adfc6700581b 100644 --- a/apps/application-system/api/project.json +++ b/apps/application-system/api/project.json @@ -88,6 +88,11 @@ "glob": "*", "input": "libs/application/template-api-modules/src/lib/modules/templates/aosh/transfer-of-machine-ownership/emailGenerators/assets", "output": "./aosh-transfer-of-machine-ownership-assets" + }, + { + "glob": "*", + "input": "libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/assets", + "output": "./secondary-school-assets" } ], "webpackConfig": "apps/application-system/api/webpack.config.js", diff --git a/apps/application-system/form/src/auth.ts b/apps/application-system/form/src/auth.ts index 59765a32bffc..2be4a4c0f96f 100644 --- a/apps/application-system/form/src/auth.ts +++ b/apps/application-system/form/src/auth.ts @@ -50,6 +50,7 @@ if (userMocked) { ApiScope.vinnueftirlitid, ApiScope.signatureCollection, ApiScope.licenses, + ApiScope.menntamalastofnun, ], post_logout_redirect_uri: `${window.location.origin}`, userStorePrefix: 'as.', diff --git a/libs/api/domains/secondary-school/src/lib/graphql/main.resolver.ts b/libs/api/domains/secondary-school/src/lib/graphql/main.resolver.ts index aa7cc8ce2942..d95ceac521dd 100644 --- a/libs/api/domains/secondary-school/src/lib/graphql/main.resolver.ts +++ b/libs/api/domains/secondary-school/src/lib/graphql/main.resolver.ts @@ -1,11 +1,16 @@ import { Args, Query, Resolver } from '@nestjs/graphql' +import { ApiScope } from '@island.is/auth/scopes' +import { UseGuards } from '@nestjs/common' +import { IdsUserGuard, Scopes, ScopesGuard } from '@island.is/auth-nest-tools' import { SecondarySchoolApi } from '../secondarySchool.service' import { SecondarySchoolProgram } from './models' +@UseGuards(IdsUserGuard, ScopesGuard) @Resolver() export class MainResolver { constructor(private readonly secondarySchoolApi: SecondarySchoolApi) {} + @Scopes(ApiScope.menntamalastofnun) @Query(() => [SecondarySchoolProgram]) async secondarySchoolProgramsBySchoolId( @Args('schoolId', { type: () => String }) schoolId: string, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/applicationRejectedEmail.ts b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/applicationRejectedEmail.ts new file mode 100644 index 000000000000..a6bad0190e8f --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/applicationRejectedEmail.ts @@ -0,0 +1,80 @@ +import { SecondarySchoolAnswers } from '@island.is/application/templates/secondary-school' +import { Message } from '@island.is/email-service' +import { EmailTemplateGeneratorProps } from '../../../../types' +import { EmailRecipient } from '../types' +import { pathToAsset } from '../utils' +import { ApplicationConfigurations } from '@island.is/application/types' + +export type ApplicationRejectedEmail = ( + props: EmailTemplateGeneratorProps, + recipient: EmailRecipient, +) => Message + +export const generateApplicationRejectedEmail: ApplicationRejectedEmail = ( + props, + recipient, +): Message => { + const { + application, + options: { email, clientLocationOrigin }, + } = props + const answers = application.answers as SecondarySchoolAnswers + + if (!recipient.email) throw new Error('Recipient email was undefined') + + const applicantName = answers.applicant.name + const applicantNationalId = answers.applicant.nationalId + + const subject = 'Umsókn um framhaldsskóla - Umsókn eydd' + + const message = + `Umsókn nemandans
` + + `${applicantName}, kt. ${applicantNationalId}
` + + `um nám í framhaldsskóla hefur verið eytt.
` + + `Þú getur farið inn á mínar síður og skoðað sögu umsóknarinnar.` + + return { + from: { + name: email.sender, + address: email.address, + }, + to: [{ name: recipient.name, address: recipient.email }], + subject, + template: { + title: subject, + body: [ + { + component: 'Image', + context: { + src: pathToAsset('logo.jpg'), + alt: 'Ísland.is logo', + }, + }, + { + component: 'Image', + context: { + src: pathToAsset('computerIllustration.jpg'), + alt: 'Kaffi við skjá myndskreyting', + }, + }, + { + component: 'Heading', + context: { copy: subject }, + }, + { + component: 'Copy', + context: { + copy: message, + }, + }, + { + component: 'Button', + context: { + copy: 'Skoða umsókn', + href: `${clientLocationOrigin}/${ApplicationConfigurations.SecondarySchool.slug}/${application.id}`, + }, + }, + ], + }, + } +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/applicationSubmittedEmail.ts b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/applicationSubmittedEmail.ts new file mode 100644 index 000000000000..2edac15b8098 --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/applicationSubmittedEmail.ts @@ -0,0 +1,80 @@ +import { SecondarySchoolAnswers } from '@island.is/application/templates/secondary-school' +import { Message } from '@island.is/email-service' +import { EmailTemplateGeneratorProps } from '../../../../types' +import { EmailRecipient } from '../types' +import { pathToAsset } from '../utils' +import { ApplicationConfigurations } from '@island.is/application/types' + +export type ApplicationSubmittedEmail = ( + props: EmailTemplateGeneratorProps, + recipient: EmailRecipient, +) => Message + +export const generateApplicationSubmittedEmail: ApplicationSubmittedEmail = ( + props, + recipient, +): Message => { + const { + application, + options: { email, clientLocationOrigin }, + } = props + const answers = application.answers as SecondarySchoolAnswers + + if (!recipient.email) throw new Error('Recipient email was undefined') + + const applicantName = answers.applicant.name + const applicantNationalId = answers.applicant.nationalId + + const subject = 'Umsókn um framhaldsskóla - Umsókn send' + + const message = + `Umsókn nemandans
` + + `${applicantName}, kt. ${applicantNationalId}
` + + `í framhaldsskóla hefur verið móttekin.
` + + `Þú getur farið inn á mínar síður og fylgst með framgangi umsóknarinnar.` + + return { + from: { + name: email.sender, + address: email.address, + }, + to: [{ name: recipient.name, address: recipient.email }], + subject, + template: { + title: subject, + body: [ + { + component: 'Image', + context: { + src: pathToAsset('logo.jpg'), + alt: 'Ísland.is logo', + }, + }, + { + component: 'Image', + context: { + src: pathToAsset('computerIllustration.jpg'), + alt: 'Kaffi við skjá myndskreyting', + }, + }, + { + component: 'Heading', + context: { copy: subject }, + }, + { + component: 'Copy', + context: { + copy: message, + }, + }, + { + component: 'Button', + context: { + copy: 'Skoða umsókn', + href: `${clientLocationOrigin}/${ApplicationConfigurations.SecondarySchool.slug}/${application.id}`, + }, + }, + ], + }, + } +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/assets/computerIllustration.jpg b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/assets/computerIllustration.jpg new file mode 100644 index 000000000000..6285a124ee09 Binary files /dev/null and b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/assets/computerIllustration.jpg differ diff --git a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/assets/logo.jpg b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/assets/logo.jpg new file mode 100644 index 000000000000..49ad45175f7b Binary files /dev/null and b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/assets/logo.jpg differ diff --git a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/index.ts b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/index.ts new file mode 100644 index 000000000000..e0691238cf3b --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/emailGenerators/index.ts @@ -0,0 +1,2 @@ +export * from './applicationSubmittedEmail' +export * from './applicationRejectedEmail' diff --git a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/secondary-school.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/secondary-school.module.ts index e82f9ed0570a..08d365d5e826 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/secondary-school.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/secondary-school.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common' import { SharedTemplateAPIModule } from '../../shared' import { SecondarySchoolService } from './secondary-school.service' import { SecondarySchoolClientModule } from '@island.is/clients/secondary-school' +import { AwsModule } from '@island.is/nest/aws' @Module({ - imports: [SharedTemplateAPIModule, SecondarySchoolClientModule], + imports: [SharedTemplateAPIModule, SecondarySchoolClientModule, AwsModule], providers: [SecondarySchoolService], exports: [SecondarySchoolService], }) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/secondary-school.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/secondary-school.service.ts index 986221565a23..8fe6acf143c7 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/secondary-school.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/secondary-school.service.ts @@ -1,18 +1,37 @@ import { Injectable } from '@nestjs/common' import { TemplateApiModuleActionProps } from '../../../types' import { BaseTemplateApiService } from '../../base-template-api.service' -import { ApplicationTypes } from '@island.is/application/types' -// import { SecondarySchoolAnswers } from '@island.is/application/templates/secondary-school' import { + ApplicationTypes, + ApplicationWithAttachments, + NationalRegistryParent, + YES, +} from '@island.is/application/types' +import { SecondarySchoolAnswers } from '@island.is/application/templates/secondary-school' +import { + ApplicationContact, + ApplicationSelectionSchool, SecondarySchool, SecondarySchoolClient, } from '@island.is/clients/secondary-school' import { TemplateApiError } from '@island.is/nest/problem' import { error } from '@island.is/application/templates/secondary-school' +import { S3Service } from '@island.is/nest/aws' +import { SharedTemplateApiService } from '../../shared' +import { logger } from '@island.is/logging' +import { getRecipients } from './utils' +import { + generateApplicationRejectedEmail, + generateApplicationSubmittedEmail, +} from './emailGenerators' @Injectable() export class SecondarySchoolService extends BaseTemplateApiService { - constructor(private readonly secondarySchoolClient: SecondarySchoolClient) { + constructor( + private readonly sharedTemplateAPIService: SharedTemplateApiService, + private readonly secondarySchoolClient: SecondarySchoolClient, + private readonly s3Service: S3Service, + ) { super(ApplicationTypes.SECONDARY_SCHOOL) } @@ -40,17 +59,259 @@ export class SecondarySchoolService extends BaseTemplateApiService { application, auth, }: TemplateApiModuleActionProps): Promise { - return this.secondarySchoolClient.delete(auth, application.id) + const answers = application.answers as SecondarySchoolAnswers + + // get the first registration date out of all the programs selected + const firstRegistrationEndDate = [ + answers?.selection?.first?.firstProgram?.registrationEndDate, + answers?.selection?.first?.secondProgram?.registrationEndDate, + answers?.selection?.second?.firstProgram?.registrationEndDate, + answers?.selection?.second?.secondProgram?.registrationEndDate, + answers?.selection?.third?.firstProgram?.registrationEndDate, + answers?.selection?.third?.secondProgram?.registrationEndDate, + ] + .filter((x) => !!x) + .map((x) => (x ? new Date(x) : new Date())) + .sort((a, b) => a.getTime() - b.getTime())[0] // ascending order + + const date = new Date(firstRegistrationEndDate.toUTCString()) + date.setHours(23, 59, 59) + + // If we are past the registration date for any of the selected programs, dont allow delete + if (date < new Date()) { + throw new TemplateApiError( + { + title: error.errorDeletePastRegistrationEndTitle, + summary: error.errorDeletePastRegistrationEndDescription, + }, + 500, + ) + } + + // Delete the application + await this.secondarySchoolClient.delete(auth, application.id) + + // Send email to applicant and all contacts + const recipientList = getRecipients(answers) + for (let i = 0; i < recipientList.length; i++) { + if (recipientList[i].email) { + await this.sharedTemplateAPIService + .sendEmail( + (props) => + generateApplicationRejectedEmail(props, recipientList[i]), + application, + ) + .catch((e) => { + logger.error( + `Error sending email in deleteApplication for applicationID: ${application.id}`, + e, + ) + }) + } + } } async submitApplication({ - // application, + application, auth, }: TemplateApiModuleActionProps): Promise { - // const answers = application.answers as SecondarySchoolAnswers + const answers = application.answers as SecondarySchoolAnswers + + // Get clean array of contacts + const contacts: ApplicationContact[] = [] - return this.secondarySchoolClient.create(auth, { + // Parents + const parents = + (application.externalData.nationalRegistryParents + .data as NationalRegistryParent[]) || [] + const parentsAnswers = answers?.custodians || [] + for (let i = 0; i < parents.length; i++) { + if (parents[i].nationalId) { + contacts.push({ + nationalId: parents[i].nationalId, + name: `${parents[i].givenName} ${parents[i].familyName}`, + phone: parentsAnswers[i]?.phone || '', + email: parentsAnswers[i]?.email || '', + address: parents[i].legalDomicile?.streetAddress, + postalCode: parents[i].legalDomicile?.postalCode || undefined, + city: parents[i].legalDomicile?.locality || undefined, + }) + } + } + + // Contacts + const otherContacts = answers?.otherContacts || [] + for (let i = 0; i < otherContacts.length; i++) { + if (otherContacts[i].include) { + contacts.push({ + nationalId: answers.otherContacts[i].nationalId || '', + name: answers.otherContacts[i].name || '', + phone: answers.otherContacts[i].phone || '', + email: answers.otherContacts[i].email || '', + }) + } + } + + // Get list of selected school and program ids with priority + const schools: ApplicationSelectionSchool[] = [] + let schoolPriority = 0 + if ( + answers?.selection?.first?.school?.id && + answers?.selection?.first?.firstProgram?.id + ) { + schools.push({ + priority: schoolPriority++, + schoolId: answers.selection.first.school.id, + programs: [ + { + priority: 0, + programId: answers.selection.first.firstProgram.id, + }, + { + priority: 1, + programId: answers.selection.first.secondProgram?.id || '', + }, + ].filter((x) => !!x.programId), + thirdLanguageCode: answers.selection.first.thirdLanguage?.code, + nordicLanguageCode: answers.selection.first.nordicLanguage?.code, + requestDormitory: + answers.selection.first.requestDormitory?.includes(YES), + }) + } + if ( + answers?.selection?.second?.include && + answers?.selection?.second?.school?.id && + answers?.selection?.second?.firstProgram?.id + ) { + schools.push({ + priority: schoolPriority++, + schoolId: answers.selection.second.school.id, + programs: [ + { + priority: 0, + programId: answers.selection.second.firstProgram.id, + }, + { + priority: 1, + programId: answers.selection.second.secondProgram?.id || '', + }, + ].filter((x) => !!x.programId), + thirdLanguageCode: answers.selection.second.thirdLanguage?.code, + nordicLanguageCode: answers.selection.second.nordicLanguage?.code, + requestDormitory: + answers.selection.second.requestDormitory?.includes(YES), + }) + } + if ( + answers?.selection?.third?.include && + answers?.selection?.third?.school?.id && + answers?.selection?.third?.firstProgram?.id + ) { + schools.push({ + priority: schoolPriority++, + schoolId: answers.selection.third.school.id, + programs: [ + { + priority: 0, + programId: answers.selection.third.firstProgram.id, + }, + { + priority: 1, + programId: answers.selection.third.secondProgram?.id || '', + }, + ].filter((x) => !!x.programId), + thirdLanguageCode: answers.selection.third.thirdLanguage?.code, + nordicLanguageCode: answers.selection.third.nordicLanguage?.code, + requestDormitory: + answers.selection.third.requestDormitory?.includes(YES), + }) + } + + // get base64 for each attachment + const attachments = await Promise.all( + (answers?.extraInformation?.supportingDocuments || []).map( + async (attachment) => { + const fileContent = await this.getAttachmentAsBase64( + application, + attachment, + ) + return { fileContent } + }, + ), + ) + + // Submit the application + const applicationId = await this.secondarySchoolClient.create(auth, { nationalId: auth.nationalId, + name: answers?.applicant?.name, + phone: answers?.applicant?.phoneNumber, + email: answers?.applicant?.email, + address: answers?.applicant?.address, + postalCode: answers?.applicant?.postalCode, + city: answers?.applicant?.city, + contacts: contacts, + schools: schools, + nativeLanguageCode: answers?.extraInformation?.nativeLanguage, + otherDescription: answers?.extraInformation?.otherDescription, + attachments: attachments, }) + + // Send email to applicant and all contacts + const recipientList = getRecipients(answers) + for (let i = 0; i < recipientList.length; i++) { + if (recipientList[i].email) { + await this.sharedTemplateAPIService + .sendEmail( + (props) => + generateApplicationSubmittedEmail(props, recipientList[i]), + application, + ) + .catch((e) => { + logger.error( + `Error sending email in submitApplication for applicationID: ${application.id}`, + e, + ) + }) + } + } + + return applicationId + } + + private async getAttachmentAsBase64( + application: ApplicationWithAttachments, + attachment: { + key: string + name: string + }, + ): Promise { + const attachmentKey = attachment.key + + const fileName = ( + application.attachments as { + [key: string]: string + } + )[attachmentKey] + + if (!fileName) { + throw new Error( + `Attachment filename not found in application on attachment key: ${attachmentKey}`, + ) + } + + try { + const fileContent = await this.s3Service.getFileContent( + fileName, + 'base64', + ) + + if (!fileContent) { + throw new Error(`File content not found for: ${fileName}`) + } + + return fileContent + } catch (error) { + throw new Error(`Failed to retrieve attachment: ${error.message}`) + } } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/types.ts b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/types.ts new file mode 100644 index 000000000000..03acae376513 --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/types.ts @@ -0,0 +1,6 @@ +export interface EmailRecipient { + nationalId: string + name: string + email?: string + phone?: string +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/utils.ts b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/utils.ts new file mode 100644 index 000000000000..b0eb0239699e --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/secondary-school/utils.ts @@ -0,0 +1,46 @@ +import { EmailRecipient } from './types' +import { join } from 'path' +import { SecondarySchoolAnswers } from '@island.is/application/templates/secondary-school' + +export const pathToAsset = (file: string) => { + return join(__dirname, `./secondary-school-assets/${file}`) +} + +export const getRecipients = ( + answers: SecondarySchoolAnswers, +): Array => { + const recipientList: Array = [] + + // Applicant + recipientList.push({ + nationalId: answers.applicant.nationalId, + name: answers.applicant.name, + email: answers.applicant.email, + }) + + // Custodians + const custodians = answers?.custodians || [] + for (let i = 0; i < custodians.length; i++) { + if (custodians[i].nationalId) { + recipientList.push({ + nationalId: custodians[i].nationalId || '', + name: custodians[i].name || '', + email: custodians[i].email || '', + }) + } + } + + // Other contacts + const otherContacts = answers?.otherContacts || [] + for (let i = 0; i < otherContacts.length; i++) { + if (otherContacts[i].include) { + recipientList.push({ + nationalId: otherContacts[i].nationalId || '', + name: otherContacts[i].name || '', + email: otherContacts[i].email || '', + }) + } + } + + return recipientList +} diff --git a/libs/application/templates/secondary-school/src/fields/OtherContact/index.tsx b/libs/application/templates/secondary-school/src/fields/OtherContact/index.tsx deleted file mode 100644 index b52695ba1d5b..000000000000 --- a/libs/application/templates/secondary-school/src/fields/OtherContact/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { FieldBaseProps } from '@island.is/application/types' -import { Box, Button, GridColumn, GridRow } from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { FC, useState } from 'react' -import { NationalIdWithName } from '@island.is/application/ui-components' -import { userInformation } from '../../lib/messages' -import { InputController } from '@island.is/shared/form-fields' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { useFormContext } from 'react-hook-form' - -//TODOx hvernig hreinsum við gildi ef strokað er út nationalId -export const OtherContact: FC = (props) => { - const { formatMessage } = useLocale() - const { application, field } = props - const { setValue, formState } = useFormContext() - - const [includeOtherContact, setIncludeOtherContact] = useState( - getValueViaPath(application.answers, `${field.id}.include`) || - false, - ) - - const onClickAdd = () => { - setIncludeOtherContact(true) - setValue(`${field.id}.include`, true) - } - - const onClickRemove = () => { - setIncludeOtherContact(false) - setValue(`${field.id}.include`, false) - } - - return ( - - {includeOtherContact && ( - - - - - - - - - - - - )} - - - {!includeOtherContact && ( - - )} - - {includeOtherContact && ( - - )} - - - ) -} diff --git a/libs/application/templates/secondary-school/src/fields/OtherContacts/index.tsx b/libs/application/templates/secondary-school/src/fields/OtherContacts/index.tsx new file mode 100644 index 000000000000..fb782062b708 --- /dev/null +++ b/libs/application/templates/secondary-school/src/fields/OtherContacts/index.tsx @@ -0,0 +1,222 @@ +import { FieldBaseProps } from '@island.is/application/types' +import { + Box, + Button, + GridColumn, + GridRow, + Text, +} from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' +import { FC, useEffect, useState } from 'react' +import { NationalIdWithName } from '@island.is/application/ui-components' +import { userInformation } from '../../lib/messages' +import { InputController } from '@island.is/shared/form-fields' +import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' +import { useFormContext } from 'react-hook-form' +import { getHasParent } from '../../utils' + +export const OtherContacts: FC = (props) => { + const { formatMessage } = useLocale() + const { application, field } = props + const { setValue, formState } = useFormContext() + + const hasParent = getHasParent(application.externalData, 0) + + const [includeOtherContactFirst, setIncludeOtherContactFirst] = + useState( + getValueViaPath(application.answers, `${field.id}[0].include`) || + false, + ) + const [includeOtherContactSecond, setIncludeOtherContactSecond] = + useState( + getValueViaPath(application.answers, `${field.id}[1].include`) || + false, + ) + + const onClickAddFirst = () => { + setIncludeOtherContactFirst(true) + setValue(`${field.id}[0].include`, true) + } + const onClickRemoveFirst = () => { + setIncludeOtherContactFirst(false) + setValue(`${field.id}[0].include`, false) + } + + const onClickAddSecond = () => { + setIncludeOtherContactSecond(true) + setValue(`${field.id}[1].include`, true) + } + const onClickRemoveSecond = () => { + setIncludeOtherContactSecond(false) + setValue(`${field.id}[1].include`, false) + } + + // default set include for first and second other contact + // if applicant has a parent, then first is optional and second is hidden (not available) + // if applicant has no parent (applicant is over 18 years old), then first is required and second is optional + useEffect(() => { + if (hasParent) { + setIncludeOtherContactSecond(false) + + // reset include for second other contact if for some reason + // if is true and applicant has a parent + const oldValue = getValueViaPath( + application.answers, + `${field.id}[1].include`, + ) + if (oldValue) setValue(`${field.id}[1].include`, false) + } + if (!hasParent) { + setIncludeOtherContactFirst(true) + setValue(`${field.id}[0].include`, true) + } + }, [application.answers, field.id, hasParent, setValue]) + + return ( + + {/* Other contact first */} + {/* Required if has parent, optional if has no parent */} + {hasParent && ( + + {formatMessage(userInformation.otherContact.subtitle)} + + )} + {includeOtherContactFirst && ( + + + + + + + + + + + + )} + + {/* Other contact second */} + {/* Optional if has no parent, hidden if has parent */} + {!hasParent && ( + + {formatMessage(userInformation.otherContact.subtitle)} + + )} + {includeOtherContactSecond && ( + + + + + + + + + + + + )} + + {hasParent ? ( + + {!includeOtherContactFirst && ( + + )} + + {includeOtherContactFirst && ( + + )} + + ) : ( + + {!includeOtherContactSecond && ( + + )} + + {includeOtherContactSecond && ( + + )} + + )} + + ) +} diff --git a/libs/application/templates/secondary-school/src/fields/Overview/ApplicantOverview.tsx b/libs/application/templates/secondary-school/src/fields/Overview/ApplicantOverview.tsx index 783ef6129b04..438c4d0c77c9 100644 --- a/libs/application/templates/secondary-school/src/fields/Overview/ApplicantOverview.tsx +++ b/libs/application/templates/secondary-school/src/fields/Overview/ApplicantOverview.tsx @@ -5,33 +5,45 @@ import { SecondarySchoolAnswers } from '../..' import { overview } from '../../lib/messages' import { useLocale } from '@island.is/localization' import { formatKennitala, formatPhoneNumber } from '../../utils' +import { ReviewGroup } from '../../components/ReviewGroup' +import { Routes } from '../../shared' -export const ApplicantOverview: FC = ({ application }) => { +export const ApplicantOverview: FC = ({ + application, + goToScreen, +}) => { const { formatMessage } = useLocale() const answers = application.answers as SecondarySchoolAnswers + const onClick = (page: string) => { + if (goToScreen) goToScreen(page) + } + return ( - - - - - {formatMessage(overview.applicant.subtitle)}: - - {answers?.applicant?.name} - {formatKennitala(answers?.applicant?.nationalId)} - {answers?.applicant?.address} - - {answers?.applicant?.postalCode} {answers?.applicant?.city} - - - {formatMessage(overview.applicant.phoneLabel)}:{' '} - {formatPhoneNumber(answers?.applicant?.phoneNumber)} - - {answers?.applicant?.email} - - - - + onClick(Routes.PERSONAL)} + editMessage={formatMessage(overview.general.editMessage)} + title={formatMessage(overview.applicant.subtitle)} + isFirst + > + + + + {answers?.applicant?.name} + {formatKennitala(answers?.applicant?.nationalId)} + {answers?.applicant?.address} + + {answers?.applicant?.postalCode} {answers?.applicant?.city} + + + {formatMessage(overview.applicant.phoneLabel)}:{' '} + {formatPhoneNumber(answers?.applicant?.phoneNumber)} + + {answers?.applicant?.email} + + + + ) } diff --git a/libs/application/templates/secondary-school/src/fields/Overview/CustodianOverview.tsx b/libs/application/templates/secondary-school/src/fields/Overview/CustodianOverview.tsx index 26937fc5d5d6..dbb4e5583330 100644 --- a/libs/application/templates/secondary-school/src/fields/Overview/CustodianOverview.tsx +++ b/libs/application/templates/secondary-school/src/fields/Overview/CustodianOverview.tsx @@ -1,12 +1,23 @@ import { FieldBaseProps } from '@island.is/application/types' import { FC } from 'react' -import { Box, GridColumn, GridRow, Text } from '@island.is/island-ui/core' +import { + Box, + Divider, + GridColumn, + GridRow, + Text, +} from '@island.is/island-ui/core' import { SecondarySchoolAnswers } from '../..' import { overview } from '../../lib/messages' import { useLocale } from '@island.is/localization' import { formatKennitala, formatPhoneNumber, getParent } from '../../utils' +import { ReviewGroup } from '../../components/ReviewGroup' +import { Routes } from '../../shared' -export const CustodianOverview: FC = ({ application }) => { +export const CustodianOverview: FC = ({ + application, + goToScreen, +}) => { const { formatMessage } = useLocale() const answers = application.answers as SecondarySchoolAnswers @@ -14,71 +25,91 @@ export const CustodianOverview: FC = ({ application }) => { const parent1 = getParent(application.externalData, 0) const parent2 = getParent(application.externalData, 1) + const otherContacts = (answers?.otherContacts || []).filter((x) => x.include) + + const onClick = (page: string) => { + if (goToScreen) goToScreen(page) + } + return ( - - - {parent1 && ( - - - {formatMessage(overview.custodian.subtitle)} 1: - - - {parent1?.givenName} {parent1?.familyName} - - {formatKennitala(parent1?.nationalId)} - {parent1?.legalDomicile?.streetAddress} - - {parent1?.legalDomicile?.postalCode}{' '} - {parent1?.legalDomicile?.locality} - - - {formatMessage(overview.custodian.phoneLabel)}:{' '} - {formatPhoneNumber(answers?.custodians[0]?.phone)} - - {answers?.custodians[0]?.email} - - )} - {parent2 && ( - - - {formatMessage(overview.custodian.subtitle)} 1: - - - {parent2?.givenName} {parent2?.familyName} - - {formatKennitala(parent2?.nationalId)} - {parent2?.legalDomicile?.streetAddress} - - {parent2?.legalDomicile?.postalCode}{' '} - {parent2?.legalDomicile?.locality} - - - {formatMessage(overview.custodian.phoneLabel)}:{' '} - {formatPhoneNumber(answers?.custodians[1]?.phone)} - - {answers?.custodians[1]?.email} - - )} - {parent1 && !parent2 && } - + <> + + onClick(Routes.CUSTODIAN)} + editMessage={formatMessage(overview.general.editMessage)} + title={formatMessage(overview.custodian.subtitle)} + > + + + {parent1 && ( + + + {formatMessage(overview.custodian.subtitle)} 1 + + + {parent1?.givenName} {parent1?.familyName} + + {formatKennitala(parent1?.nationalId)} + {parent1?.legalDomicile?.streetAddress} + + {parent1?.legalDomicile?.postalCode}{' '} + {parent1?.legalDomicile?.locality} + + + {formatMessage(overview.custodian.phoneLabel)}:{' '} + {formatPhoneNumber(answers?.custodians[0]?.phone)} + + {answers?.custodians[0]?.email} + + )} + {parent2 && ( + + + {formatMessage(overview.custodian.subtitle)} 2 + + + {parent2?.givenName} {parent2?.familyName} + + {formatKennitala(parent2?.nationalId)} + {parent2?.legalDomicile?.streetAddress} + + {parent2?.legalDomicile?.postalCode}{' '} + {parent2?.legalDomicile?.locality} + + + {formatMessage(overview.custodian.phoneLabel)}:{' '} + {formatPhoneNumber(answers?.custodians[1]?.phone)} + + {answers?.custodians[1]?.email} + + )} + {parent1 && !parent2 && } + - {answers?.otherContact?.include && ( - - - - {formatMessage(overview.otherContact.subtitle)}: - - {answers.otherContact.name} - {formatKennitala(answers.otherContact.nationalId)} - - {formatMessage(overview.otherContact.phoneLabel)}:{' '} - {formatPhoneNumber(answers.otherContact.phone)} - - {answers.otherContact.email} - - - - )} - + {!!otherContacts.length && ( + + {otherContacts.map((otherContact, index) => ( + + + {formatMessage(overview.otherContact.subtitle)}{' '} + {otherContacts.length > 0 ? index + 1 : ''} + + {otherContact.name} + {formatKennitala(otherContact.nationalId)} + + {formatMessage(overview.otherContact.phoneLabel)}:{' '} + {formatPhoneNumber(otherContact.phone)} + + {otherContact.email} + + ))} + {otherContacts.length % 2 !== 0 && ( + + )} + + )} + + + ) } diff --git a/libs/application/templates/secondary-school/src/fields/Overview/ExtraInformationOverview.tsx b/libs/application/templates/secondary-school/src/fields/Overview/ExtraInformationOverview.tsx index 23c30d0073bf..bfff9b0960c7 100644 --- a/libs/application/templates/secondary-school/src/fields/Overview/ExtraInformationOverview.tsx +++ b/libs/application/templates/secondary-school/src/fields/Overview/ExtraInformationOverview.tsx @@ -1,65 +1,102 @@ -import { FieldBaseProps, YES } from '@island.is/application/types' +import { FieldBaseProps } from '@island.is/application/types' import { FC } from 'react' -import { Box, GridColumn, GridRow, Text } from '@island.is/island-ui/core' +import { + Box, + Divider, + GridColumn, + GridRow, + Icon, + Text, +} from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' import { overview } from '../../lib/messages' import { SecondarySchoolAnswers } from '../..' +import { ReviewGroup } from '../../components/ReviewGroup' +import { Routes } from '../../shared' export const ExtraInformationOverview: FC = ({ application, + goToScreen, }) => { const { formatMessage } = useLocale() const answers = application.answers as SecondarySchoolAnswers const showNativeLanguage = !!answers?.extraInformation?.nativeLanguage - const showHasDisability = - !!answers?.extraInformation?.hasDisability?.includes(YES) - const showDisabilityDescription = - !!answers?.extraInformation?.disabilityDescription const showOtherDescription = !!answers?.extraInformation?.otherDescription + const showSupportingDocuments = + !!answers?.extraInformation?.supportingDocuments?.length + + const onClick = (page: string) => { + if (goToScreen) goToScreen(page) + } return ( - (showNativeLanguage || - showHasDisability || - showDisabilityDescription || - showOtherDescription) && ( - - - - - {formatMessage(overview.extraInformation.subtitle)}: - - {showNativeLanguage && ( - - {formatMessage(overview.extraInformation.nativeLanguageLabel)}:{' '} - {answers?.extraInformation?.nativeLanguage} - - )} - {showHasDisability && ( - - {formatMessage(overview.extraInformation.hasDisabilityLabel)}:{' '} - {formatMessage(overview.extraInformation.hasDisabilityYesValue)} - - )} - {showDisabilityDescription && ( - - {formatMessage( - overview.extraInformation.disabilityDescriptionLabel, + (showNativeLanguage || showOtherDescription || showSupportingDocuments) && ( + <> + + onClick(Routes.EXTRA_INFORMATION)} + editMessage={formatMessage(overview.general.editMessage)} + title={formatMessage(overview.extraInformation.subtitle)} + isLast + > + + + + {/* Native language */} + {showNativeLanguage && ( + + {formatMessage( + overview.extraInformation.nativeLanguageLabel, + )} + : {answers?.extraInformation?.nativeLanguage} + + )} + + {/* Other description */} + {showOtherDescription && ( + + {formatMessage(overview.extraInformation.otherLabel)}:{' '} + {answers?.extraInformation?.otherDescription} + + )} + + {/* Supporting documents */} + {showSupportingDocuments && ( + + {formatMessage( + overview.extraInformation.supportingDocumentsLabel, + )} + : + + )} + {answers?.extraInformation?.supportingDocuments?.map( + (attachment) => { + return ( + + + + + {attachment.name} + + ) + }, )} - : {answers?.extraInformation?.disabilityDescription} - - )} - {showOtherDescription && ( - - {formatMessage(overview.extraInformation.otherLabel)}:{' '} - {answers?.extraInformation?.otherDescription} - - )} - - - - + + + + + ) ) } diff --git a/libs/application/templates/secondary-school/src/fields/Overview/SchoolSelectionOverview.tsx b/libs/application/templates/secondary-school/src/fields/Overview/SchoolSelectionOverview.tsx index 6d969c243e67..b677a55384d0 100644 --- a/libs/application/templates/secondary-school/src/fields/Overview/SchoolSelectionOverview.tsx +++ b/libs/application/templates/secondary-school/src/fields/Overview/SchoolSelectionOverview.tsx @@ -1,80 +1,146 @@ import { FieldBaseProps } from '@island.is/application/types' import { FC } from 'react' -import { Box, GridColumn, GridRow, Text } from '@island.is/island-ui/core' +import { + Box, + Divider, + GridColumn, + GridRow, + Text, +} from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' import { overview } from '../../lib/messages' import { SecondarySchoolAnswers } from '../..' +import { getTranslatedProgram } from '../../utils' +import { ReviewGroup } from '../../components/ReviewGroup' +import { Routes } from '../../shared' export const SchoolSelectionOverview: FC = ({ application, + goToScreen, }) => { - const { formatMessage } = useLocale() + const { formatMessage, lang } = useLocale() const answers = application.answers as SecondarySchoolAnswers + const onClick = (page: string) => { + if (goToScreen) goToScreen(page) + } + return ( - - - - - {formatMessage(overview.selection.firstSubtitle)}: - - {answers?.selection?.first?.school?.name} - - {formatMessage(overview.selection.firstProgramLabel)}:{' '} - {answers?.selection?.first?.firstProgram?.name} - - - {formatMessage(overview.selection.secondProgramLabel)}:{' '} - {answers?.selection?.first?.secondProgram?.name} - - {answers?.selection?.first?.thirdLanguage?.name} - {!!answers?.selection?.first?.nordicLanguage?.code && ( - {answers?.selection?.first?.nordicLanguage?.name} - )} - - - - {formatMessage(overview.selection.secondSubtitle)}: - - {answers?.selection?.second?.school?.name} - - {formatMessage(overview.selection.firstProgramLabel)}:{' '} - {answers?.selection?.second?.firstProgram?.name} - - - {formatMessage(overview.selection.secondProgramLabel)}:{' '} - {answers?.selection?.second?.secondProgram?.name} - - {answers?.selection?.second?.thirdLanguage?.name} - {!!answers?.selection?.second?.nordicLanguage?.code && ( - {answers?.selection?.second?.nordicLanguage?.name} - )} - - {answers?.selection?.third?.include && ( - + <> + + onClick(Routes.SCHOOL)} + editMessage={formatMessage(overview.general.editMessage)} + title={formatMessage(overview.selection.subtitle)} + > + + + {/* First selection */} - - {formatMessage(overview.selection.thirdSubtitle)}: + + {formatMessage(overview.selection.firstSubtitle)} - {answers?.selection?.third?.school?.name} + {answers?.selection?.first?.school?.name} {formatMessage(overview.selection.firstProgramLabel)}:{' '} - {answers?.selection?.third?.firstProgram?.name} - - - {formatMessage(overview.selection.secondProgramLabel)}:{' '} - {answers?.selection?.third?.secondProgram?.name} + {getTranslatedProgram( + lang, + answers?.selection?.first?.firstProgram, + )} - {answers?.selection?.third?.thirdLanguage?.name} - {!!answers?.selection?.third?.nordicLanguage?.code && ( - {answers?.selection?.third?.nordicLanguage?.name} + {!!answers?.selection?.first?.secondProgram?.id && ( + + {formatMessage(overview.selection.secondProgramLabel)}:{' '} + {getTranslatedProgram( + lang, + answers?.selection?.first?.secondProgram, + )} + + )} + {!!answers?.selection?.first?.thirdLanguage?.code && ( + {answers?.selection?.first?.thirdLanguage?.name} + )} + {!!answers?.selection?.first?.nordicLanguage?.code && ( + {answers?.selection?.first?.nordicLanguage?.name} )} - - - )} - - + + {/* Second selection */} + {answers?.selection?.second?.include && ( + + + {formatMessage(overview.selection.secondSubtitle)} + + {answers?.selection?.second?.school?.name} + + {formatMessage(overview.selection.firstProgramLabel)}:{' '} + {getTranslatedProgram( + lang, + answers?.selection?.second?.firstProgram, + )} + + {!!answers?.selection?.second?.secondProgram?.id && ( + + {formatMessage(overview.selection.secondProgramLabel)}:{' '} + {getTranslatedProgram( + lang, + answers?.selection?.second?.secondProgram, + )} + + )} + {!!answers?.selection?.second?.thirdLanguage?.code && ( + {answers?.selection?.second?.thirdLanguage?.name} + )}{' '} + {!!answers?.selection?.second?.nordicLanguage?.code && ( + + {answers?.selection?.second?.nordicLanguage?.name} + + )} + + )} + + {/* Third selection */} + {answers?.selection?.third?.include && ( + + + + {formatMessage(overview.selection.thirdSubtitle)} + + {answers?.selection?.third?.school?.name} + + {formatMessage(overview.selection.firstProgramLabel)}:{' '} + {getTranslatedProgram( + lang, + answers?.selection?.third?.firstProgram, + )} + + {!!answers?.selection?.third?.secondProgram?.id && ( + + {formatMessage(overview.selection.secondProgramLabel)}:{' '} + {getTranslatedProgram( + lang, + answers?.selection?.third?.secondProgram, + )} + + )} + {!!answers?.selection?.third?.thirdLanguage?.code && ( + + {answers?.selection?.third?.thirdLanguage?.name} + + )} + {!!answers?.selection?.third?.nordicLanguage?.code && ( + + {answers?.selection?.third?.nordicLanguage?.name} + + )} + + + + )} + + + + ) } diff --git a/libs/application/templates/secondary-school/src/fields/Overview/SupportingDocumentsOverview.tsx b/libs/application/templates/secondary-school/src/fields/Overview/SupportingDocumentsOverview.tsx deleted file mode 100644 index e5036f071753..000000000000 --- a/libs/application/templates/secondary-school/src/fields/Overview/SupportingDocumentsOverview.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { FieldBaseProps } from '@island.is/application/types' -import { FC } from 'react' -import { Box, GridColumn, GridRow, Icon, Text } from '@island.is/island-ui/core' -import { overview } from '../../lib/messages' -import { SecondarySchoolAnswers } from '../..' -import { useLocale } from '@island.is/localization' - -export const SupportingDocumentsOverview: FC = ({ - application, -}) => { - const { formatMessage } = useLocale() - - const answers = application.answers as SecondarySchoolAnswers - - return ( - - - - - {formatMessage(overview.supportingDocuments.subtitle)} - - {formatMessage(overview.supportingDocuments.description)} - - {answers.supportingDocuments?.attachments?.map((attachment) => { - return ( - - - - - {attachment.name} - - ) - })} - - - - ) -} diff --git a/libs/application/templates/secondary-school/src/fields/Overview/index.tsx b/libs/application/templates/secondary-school/src/fields/Overview/index.tsx index 82a2f4841e21..b3d62a244b59 100644 --- a/libs/application/templates/secondary-school/src/fields/Overview/index.tsx +++ b/libs/application/templates/secondary-school/src/fields/Overview/index.tsx @@ -1,57 +1,21 @@ import { FieldBaseProps } from '@island.is/application/types' import { FC } from 'react' -import { Box, Divider } from '@island.is/island-ui/core' +import { Box } from '@island.is/island-ui/core' import { ApplicantOverview } from './ApplicantOverview' import { CustodianOverview } from './CustodianOverview' -import { SupportingDocumentsOverview } from './SupportingDocumentsOverview' import { SchoolSelectionOverview } from './SchoolSelectionOverview' -import { SecondarySchoolAnswers } from '../..' import { ExtraInformationOverview } from './ExtraInformationOverview' -import { ReviewGroup } from '../../components/ReviewGroup' -import { overview } from '../../lib/messages' -import { useLocale } from '@island.is/localization' - -export const Overview: FC = ({ - application, - field, - goToScreen, -}) => { - const { formatMessage } = useLocale() - - const answers = application.answers as SecondarySchoolAnswers - - const onClick = (page: string) => { - if (goToScreen) goToScreen(page) - } +export const Overview: FC = (props) => { return ( - onClick('applicant')} - editMessage={formatMessage(overview.general.editMessage)} - title={formatMessage(overview.applicant.subtitle)} - isFirst - > - - - - - - - - - - {!!answers.supportingDocuments?.attachments?.length && ( - - )} - - {!!answers.supportingDocuments?.attachments?.length && } + - + - + - + ) } diff --git a/libs/application/templates/secondary-school/src/fields/SchoolSelection/index.tsx b/libs/application/templates/secondary-school/src/fields/SchoolSelection/index.tsx index a54345933837..abb18690fd3e 100644 --- a/libs/application/templates/secondary-school/src/fields/SchoolSelection/index.tsx +++ b/libs/application/templates/secondary-school/src/fields/SchoolSelection/index.tsx @@ -6,17 +6,33 @@ import { school } from '../../lib/messages' import { getValueViaPath } from '@island.is/application/core' import { useFormContext } from 'react-hook-form' import { SelectionItem } from './selectionItem' +import { ApplicationType } from '../../shared' +import { hasDuplicates } from '../../utils' export const SchoolSelection: FC = (props) => { const { formatMessage } = useLocale() const { application, setBeforeSubmitCallback } = props const { setValue, watch } = useFormContext() + const isFreshman = + getValueViaPath( + application.answers, + 'applicationType.type', + ) === ApplicationType.FRESHMAN + const [schoolDuplicateError, setSchoolDuplicateError] = useState(false) const [programDuplicateError, setProgramDuplicateError] = useState(false) + const [includeSecondSelection, setIncludeSecondSelection] = useState( + isFreshman + ? true + : getValueViaPath( + application.answers, + `${props.field.id}.second.include`, + ) || false, + ) const [includeThirdSelection, setIncludeThirdSelection] = useState( getValueViaPath( application.answers, @@ -24,56 +40,63 @@ export const SchoolSelection: FC = (props) => { ) || false, ) - const onClickAdd = () => { - setIncludeThirdSelection(true) - setValue(`${props.field.id}.third.include`, true) + // Add / remove second selection (only non-freshman can to this) + const onClickAddSecond = () => { + if (!isFreshman) { + setIncludeSecondSelection(true) + setValue(`${props.field.id}.second.include`, true) + } + } + const onClickRemoveSecond = () => { + if (!isFreshman) { + setIncludeSecondSelection(false) + setValue(`${props.field.id}.second.include`, false) + } } - const onClickRemove = () => { - setIncludeThirdSelection(false) - setValue(`${props.field.id}.third.include`, false) + // Add / remove third selection (only freshman can to this) + const onClickAddThird = () => { + if (isFreshman) { + setIncludeThirdSelection(true) + setValue(`${props.field.id}.third.include`, true) + } + } + const onClickRemoveThird = () => { + if (isFreshman) { + setIncludeThirdSelection(false) + setValue(`${props.field.id}.third.include`, false) + } } const checkSchoolDuplicate = () => { - const firstSchoolId = watch(`${props.field.id}.first.school.id`) - const secondSchoolId = watch(`${props.field.id}.second.school.id`) - const thirdSchoolId = watch(`${props.field.id}.third.school.id`) - const includeThird = watch(`${props.field.id}.third.include`) as boolean - - if ( - firstSchoolId === secondSchoolId || - (includeThird && - (firstSchoolId === thirdSchoolId || secondSchoolId === thirdSchoolId)) - ) { - return true - } + const schoolIds = [watch(`${props.field.id}.first.school.id`)] + + if (includeSecondSelection) + schoolIds.push(watch(`${props.field.id}.second.school.id`)) - return false + if (includeThirdSelection) + schoolIds.push(watch(`${props.field.id}.third.school.id`)) + + return hasDuplicates(schoolIds) } const checkProgramDuplicate = () => { - if ( - watch(`${props.field.id}.first.firstProgram.id`) === - watch(`${props.field.id}.first.secondProgram.id`) - ) - return true - - if ( - watch(`${props.field.id}.second.firstProgram.id`) === - watch(`${props.field.id}.second.secondProgram.id`) - ) - return true - - const includeThird = watch(`${props.field.id}.third.include`) as boolean - - if ( - includeThird && - watch(`${props.field.id}.third.firstProgram.id`) === - watch(`${props.field.id}.third.secondProgram.id`) - ) - return true - - return false + const programIds: string[] = [ + watch(`${props.field.id}.first.firstProgram.id`), + watch(`${props.field.id}.first.secondProgram.id`) || '', + ] + + if (includeSecondSelection) { + programIds.push(watch(`${props.field.id}.second.firstProgram.id`)) + programIds.push(watch(`${props.field.id}.second.secondProgram.id`) || '') + } + + if (includeThirdSelection) { + programIds.push(watch(`${props.field.id}.third.firstProgram.id`)) + programIds.push(watch(`${props.field.id}.third.secondProgram.id`) || '') + } + + return hasDuplicates(programIds.filter((x) => !!x)) } setBeforeSubmitCallback?.(async () => { @@ -95,21 +118,27 @@ export const SchoolSelection: FC = (props) => { return [true, null] }) - // default set include=true for first and second selection since - // they should both be required + // default set include for first, second and third selection + // freshman has second selection as required, and third selection as optional + // non-freshman has second selection as optional, and third selection is hidden (not available) useEffect(() => { setValue(`${props.field.id}.first.include`, true) - setValue(`${props.field.id}.second.include`, true) - }, [props.field.id, setValue]) + if (isFreshman) setValue(`${props.field.id}.second.include`, true) + if (!isFreshman) setValue(`${props.field.id}.third.include`, false) + }, [isFreshman, props.field.id, setValue]) return ( + {/* First selection */} + {/* Required for everyone */} - - {formatMessage(school.firstSelection.subtitle)} - + {includeSecondSelection && ( + + {formatMessage(school.firstSelection.subtitle)} + + )} = (props) => { /> - - - {formatMessage(school.secondSelection.subtitle)} - - - - - {includeThirdSelection ? ( + {/* Second selection */} + {/* Required for freshman, optional for non-freshman */} + {includeSecondSelection ? ( - {formatMessage(school.thirdSelection.subtitle)} + {formatMessage(school.secondSelection.subtitle)} ) : ( - {formatMessage(school.thirdSelection.addSubtitle)} + {formatMessage(school.secondSelection.addSubtitle)} {formatMessage(school.thirdSelection.addDescription)} )} + {!isFreshman && ( + + {!includeSecondSelection && ( + + )} + {includeSecondSelection && ( + + )} + + )} - - {!includeThirdSelection && ( - - )} - {includeThirdSelection && ( - - )} - + {/* Third selection */} + {/* Optional for freshman, hidden for non-freshman */} + {isFreshman && ( + <> + {includeThirdSelection ? ( + + + {formatMessage(school.thirdSelection.subtitle)} + + + + ) : ( + + + {formatMessage(school.thirdSelection.addSubtitle)} + + {formatMessage(school.thirdSelection.addDescription)} + + )} + + {!includeThirdSelection && ( + + )} + {includeThirdSelection && ( + + )} + + + )} + {/* Duplicate error */} {schoolDuplicateError && ( = (props) => { - const { formatMessage } = useLocale() + const { formatMessage, lang } = useLocale() const { application, setFieldLoadingState } = props const { setValue } = useFormContext() - - const [selectedFirstProgram, setSelectedFirstProgram] = useState() //TODOx afhverju hreinsast ekki út í SelectController const [isLoadingPrograms, setIsLoadingPrograms] = useState(false) + const [secondProgramRequire, setSecondProgramRequire] = + useState(true) + + // options for dropdowns + const schoolOptions = application.externalData.schools + .data as SecondarySchool[] + const [programOptions, setProgramOptions] = useState([]) + const [thirdLanguageOptions, setThirdLanguageOptions] = useState( + [], + ) + const [nordicLanguageOptions, setNordicLanguageOptions] = useState< + Language[] + >([]) - const oldSchoolId = getValueViaPath( + // state variables for values in dropdown that use Controller + Select + const [selectedFirstProgram, setSelectedFirstProgram] = + useState + - { - return { - label: program.nameIs, - value: program.id, - } - })} - onSelect={(value) => - selectProgram('firstProgram', value.value as string) - } + { + return ( + { + return { + label: getTranslatedProgram(lang, program), + value: program.id, + } + })} + onChange={(option: Option | null) => { + onChange(option?.value) + setSelectedSecondProgram(option) + setValueProgram('secondProgram', option?.value) + }} + /> + ) + }} /> + - { - return { - label: language.nameIs, - value: language.code, - } - })} - onSelect={(value) => selectThirdLanguage(value.value as string)} + { + return ( + { + return { + label: language.name, + value: language.code, + } + })} + onChange={(option: Option | null) => { + onChange(option?.value) + setSelectedNordicLanguage(option) + setValueNordicLanguage(option?.value) + }} + /> + ) + }} /> + + + {!!selectedNordicLanguage && ( + )} + + + ) diff --git a/libs/application/templates/secondary-school/src/fields/index.ts b/libs/application/templates/secondary-school/src/fields/index.ts index 86db4016e7f5..42393a957da5 100644 --- a/libs/application/templates/secondary-school/src/fields/index.ts +++ b/libs/application/templates/secondary-school/src/fields/index.ts @@ -1,3 +1,3 @@ -export * from './OtherContact' +export * from './OtherContacts' export * from './SchoolSelection' export * from './Overview' diff --git a/libs/application/templates/secondary-school/src/forms/Conclusion.ts b/libs/application/templates/secondary-school/src/forms/Conclusion.ts index 6ca777fd2994..8959a2bd1a85 100644 --- a/libs/application/templates/secondary-school/src/forms/Conclusion.ts +++ b/libs/application/templates/secondary-school/src/forms/Conclusion.ts @@ -16,6 +16,7 @@ export const Conclusion: Form = buildForm({ title: '', logo: Logo, mode: FormModes.COMPLETED, + children: [ buildSection({ id: 'externalData', diff --git a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/ExtraInformationSection/index.ts b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/ExtraInformationSection/index.ts index 48aa8ff24aaa..fade9beb711c 100644 --- a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/ExtraInformationSection/index.ts +++ b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/ExtraInformationSection/index.ts @@ -1,14 +1,16 @@ import { - buildCheckboxField, + buildAlertMessageField, buildDescriptionField, + buildFileUploadField, buildMultiField, buildSection, buildSelectField, buildTextField, - YES, } from '@island.is/application/core' -import { extraInformation } from '../../../lib/messages' +import { error, extraInformation } from '../../../lib/messages' import { getAllLanguageCodes } from '@island.is/shared/utils' +import { FILE_SIZE_LIMIT, FILE_TYPES_ALLOWED } from '../../../shared' +import { getEndOfDayUTC, getFirstRegistrationEndDate } from '../../../utils' export const extraInformationSection = buildSection({ id: 'extraInformationSection', @@ -19,6 +21,17 @@ export const extraInformationSection = buildSection({ title: extraInformation.general.pageTitle, description: extraInformation.general.description, children: [ + buildAlertMessageField({ + id: 'alertPastRegistrationdate', + alertType: 'error', + title: error.errorPastRegistrationDateTitle, + message: error.errorPastRegistrationDateDescription, + condition: (answers) => { + return ( + getEndOfDayUTC(getFirstRegistrationEndDate(answers)) < new Date() + ) + }, + }), // Native language buildDescriptionField({ id: 'extraInformation.nativeLanguage.subtitle', @@ -41,46 +54,41 @@ export const extraInformationSection = buildSection({ }, }), - // Disability + // Other buildDescriptionField({ - id: 'extraInformation.disability.subtitle', - title: extraInformation.disability.subtitle, + id: 'extraInformation.otherDescription.subtitle', + title: extraInformation.other.subtitle, + description: extraInformation.other.description, titleVariant: 'h5', space: 3, }), - buildCheckboxField({ - id: 'extraInformation.hasDisability', - title: '', - large: false, - backgroundColor: 'white', - options: [ - { - value: YES, - label: extraInformation.disability.checkboxLabel, - }, - ], - }), buildTextField({ - id: 'extraInformation.disabilityDescription', + id: 'extraInformation.otherDescription', variant: 'textarea', rows: 5, - title: extraInformation.disability.textareaLabel, - placeholder: extraInformation.disability.textareaPlaceholder, + title: extraInformation.other.textareaLabel, + placeholder: extraInformation.other.textareaPlaceholder, }), - // Other + // Supporting documents buildDescriptionField({ - id: 'extraInformation.other.subtitle', - title: extraInformation.other.subtitle, + id: 'extraInformation.supportingDocuments.subtitle', + title: extraInformation.supportingDocuments.subtitle, + description: extraInformation.supportingDocuments.description, titleVariant: 'h5', space: 3, }), - buildTextField({ - id: 'extraInformation.otherDescription', - variant: 'textarea', - rows: 5, - title: extraInformation.other.textareaLabel, - placeholder: extraInformation.other.textareaPlaceholder, + buildFileUploadField({ + id: 'extraInformation.supportingDocuments', + title: '', + introduction: '', + uploadAccept: FILE_TYPES_ALLOWED, + maxSize: FILE_SIZE_LIMIT, + uploadHeader: extraInformation.supportingDocuments.fileUploadHeader, + uploadDescription: + extraInformation.supportingDocuments.fileUploadDescription, + uploadButtonLabel: + extraInformation.supportingDocuments.fileUploadButtonLabel, }), ], }), diff --git a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/OverviewSection/index.ts b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/OverviewSection/index.ts index 0afe774327b3..0fdc99acc3e6 100644 --- a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/OverviewSection/index.ts +++ b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/OverviewSection/index.ts @@ -1,12 +1,13 @@ import { - buildCheckboxField, + buildAlertMessageField, buildCustomField, buildMultiField, buildSection, buildSubmitField, } from '@island.is/application/core' -import { overview } from '../../../lib/messages' -import { DefaultEvents, NO, YES } from '@island.is/application/types' +import { error, overview } from '../../../lib/messages' +import { DefaultEvents } from '@island.is/application/types' +import { getEndOfDayUTC, getFirstRegistrationEndDate } from '../../../utils' export const overviewSection = buildSection({ id: 'overviewSection', @@ -17,20 +18,23 @@ export const overviewSection = buildSection({ title: overview.general.pageTitle, description: overview.general.description, children: [ + buildAlertMessageField({ + id: 'alertPastRegistrationdate', + alertType: 'error', + title: error.errorPastRegistrationDateTitle, + message: error.errorPastRegistrationDateDescription, + condition: (answers) => { + return ( + getEndOfDayUTC(getFirstRegistrationEndDate(answers)) < new Date() + ) + }, + }), buildCustomField({ component: 'Overview', id: 'overview', title: '', description: '', }), - buildCheckboxField({ - id: 'approveTermsAndConditions', - title: '', - options: [ - { value: YES, label: overview.confirmation.checkboxMessage }, - ], - defaultValue: [NO], - }), buildSubmitField({ id: 'submit', placement: 'footer', diff --git a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/SchoolSection/index.ts b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/SchoolSection/index.ts index 266590eea284..a2b215769fc4 100644 --- a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/SchoolSection/index.ts +++ b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/SchoolSection/index.ts @@ -1,19 +1,24 @@ import { buildCustomField, + buildHiddenInput, buildMultiField, buildSection, } from '@island.is/application/core' import { school } from '../../../lib/messages' +import { Routes } from '../../../shared' export const schoolSection = buildSection({ id: 'schoolSection', title: school.general.sectionTitle, children: [ buildMultiField({ - id: 'schoolMultiField', + id: Routes.SCHOOL, title: school.general.pageTitle, description: school.general.description, children: [ + buildHiddenInput({ id: 'selection.first' }), + buildHiddenInput({ id: 'selection.second' }), + buildHiddenInput({ id: 'selection.third' }), buildCustomField({ component: 'SchoolSelection', id: 'selection', diff --git a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/CustodianSubSection.ts b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/CustodianSubSection.ts index 66fa84947dea..f95f5f276ca7 100644 --- a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/CustodianSubSection.ts +++ b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/CustodianSubSection.ts @@ -8,16 +8,22 @@ import { } from '@island.is/application/core' import { userInformation } from '../../../lib/messages' import { Application } from '@island.is/application/types' -import { getParent, hasParent } from '../../../utils' +import { getParent, getHasParent } from '../../../utils' +import { Routes } from '../../../shared' -//TODOx þarf að gera tengilið required ef aðili er yfir 18 og það eru engir forsjáraðliar? export const custodianSubSection = buildSubSection({ id: 'custodianSubSection', - title: userInformation.custodian.subSectionTitle, + title: (application) => + getHasParent(application.externalData, 0) + ? userInformation.custodian.subSectionTitle + : userInformation.otherContact.subSectionTitle, children: [ buildMultiField({ - id: 'custodianMultiField', - title: userInformation.custodian.pageTitle, + id: Routes.CUSTODIAN, + title: (application) => + getHasParent(application.externalData, 0) + ? userInformation.custodian.pageTitle + : userInformation.otherContact.pageTitle, children: [ // Custodian 1 buildDescriptionField({ @@ -25,7 +31,7 @@ export const custodianSubSection = buildSubSection({ title: userInformation.custodian.subtitle1, titleVariant: 'h5', space: 3, - condition: (_, externalData) => hasParent(externalData, 0), + condition: (_, externalData) => getHasParent(externalData, 0), }), buildTextField({ id: 'custodians[0].name', @@ -33,7 +39,7 @@ export const custodianSubSection = buildSubSection({ backgroundColor: 'blue', width: 'half', readOnly: true, - condition: (_, externalData) => hasParent(externalData, 0), + condition: (_, externalData) => getHasParent(externalData, 0), defaultValue: (application: Application) => { const parent = getParent(application.externalData, 0) return `${parent?.givenName} ${parent?.familyName}` @@ -46,7 +52,7 @@ export const custodianSubSection = buildSubSection({ width: 'half', readOnly: true, format: '######-####', - condition: (_, externalData) => hasParent(externalData, 0), + condition: (_, externalData) => getHasParent(externalData, 0), defaultValue: (application: Application) => { const parent = getParent(application.externalData, 0) return `${parent?.nationalId}` @@ -58,19 +64,19 @@ export const custodianSubSection = buildSubSection({ backgroundColor: 'blue', width: 'half', readOnly: true, - condition: (_, externalData) => hasParent(externalData, 0), + condition: (_, externalData) => getHasParent(externalData, 0), defaultValue: (application: Application) => { const parent = getParent(application.externalData, 0) return `${parent?.legalDomicile?.streetAddress}` }, }), buildTextField({ - id: 'custodians[0].postalCode', - title: userInformation.custodian.postalCode, + id: 'custodians[0].postalCodeAndCity', + title: userInformation.custodian.postalCodeAndCity, backgroundColor: 'blue', width: 'half', readOnly: true, - condition: (_, externalData) => hasParent(externalData, 0), + condition: (_, externalData) => getHasParent(externalData, 0), defaultValue: (application: Application) => { const parent = getParent(application.externalData, 0) return `${parent?.legalDomicile?.postalCode} ${parent?.legalDomicile?.locality}` @@ -83,14 +89,14 @@ export const custodianSubSection = buildSubSection({ width: 'half', variant: 'email', required: true, - condition: (_, externalData) => hasParent(externalData, 0), + condition: (_, externalData) => getHasParent(externalData, 0), }), buildPhoneField({ id: 'custodians[0].phone', title: userInformation.custodian.phone, width: 'half', required: true, - condition: (_, externalData) => hasParent(externalData, 0), + condition: (_, externalData) => getHasParent(externalData, 0), }), // Custodian 2 @@ -99,7 +105,7 @@ export const custodianSubSection = buildSubSection({ title: userInformation.custodian.subtitle2, titleVariant: 'h5', space: 3, - condition: (_, externalData) => hasParent(externalData, 1), + condition: (_, externalData) => getHasParent(externalData, 1), }), buildTextField({ id: 'custodians[1].name', @@ -107,7 +113,7 @@ export const custodianSubSection = buildSubSection({ backgroundColor: 'blue', width: 'half', readOnly: true, - condition: (_, externalData) => hasParent(externalData, 1), + condition: (_, externalData) => getHasParent(externalData, 1), defaultValue: (application: Application) => { const parent = getParent(application.externalData, 1) return `${parent?.givenName} ${parent?.familyName}` @@ -120,7 +126,7 @@ export const custodianSubSection = buildSubSection({ width: 'half', readOnly: true, format: '######-####', - condition: (_, externalData) => hasParent(externalData, 1), + condition: (_, externalData) => getHasParent(externalData, 1), defaultValue: (application: Application) => { const parent = getParent(application.externalData, 1) return `${parent?.nationalId}` @@ -132,19 +138,19 @@ export const custodianSubSection = buildSubSection({ backgroundColor: 'blue', width: 'half', readOnly: true, - condition: (_, externalData) => hasParent(externalData, 1), + condition: (_, externalData) => getHasParent(externalData, 1), defaultValue: (application: Application) => { const parent = getParent(application.externalData, 1) return `${parent?.legalDomicile?.streetAddress}` }, }), buildTextField({ - id: 'custodians[1].postalCode', - title: userInformation.custodian.postalCode, + id: 'custodians[1].postalCodeAndCity', + title: userInformation.custodian.postalCodeAndCity, backgroundColor: 'blue', width: 'half', readOnly: true, - condition: (_, externalData) => hasParent(externalData, 1), + condition: (_, externalData) => getHasParent(externalData, 1), defaultValue: (application: Application) => { const parent = getParent(application.externalData, 1) return `${parent?.legalDomicile?.postalCode} ${parent?.legalDomicile?.locality}` @@ -157,27 +163,20 @@ export const custodianSubSection = buildSubSection({ width: 'half', variant: 'email', required: true, - condition: (_, externalData) => hasParent(externalData, 1), + condition: (_, externalData) => getHasParent(externalData, 1), }), buildPhoneField({ id: 'custodians[1].phone', title: userInformation.custodian.phone, width: 'half', required: true, - condition: (_, externalData) => hasParent(externalData, 1), + condition: (_, externalData) => getHasParent(externalData, 1), }), // Other contact - buildDescriptionField({ - id: 'otherContact.subtitle', - title: userInformation.otherContact.subtitle, - titleVariant: 'h5', - space: 3, - condition: (_, externalData) => hasParent(externalData, 1), - }), buildCustomField({ - component: 'OtherContact', - id: 'otherContact', + component: 'OtherContacts', + id: 'otherContacts', title: '', description: '', }), diff --git a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/PersonalSubSection.ts b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/PersonalSubSection.ts index ac30f5caec9b..96835cf8ff30 100644 --- a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/PersonalSubSection.ts +++ b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/PersonalSubSection.ts @@ -3,18 +3,17 @@ import { buildMultiField, buildRadioField, buildSubSection, - buildTextField, } from '@island.is/application/core' import { userInformation } from '../../../lib/messages' import { applicantInformationMultiField } from '@island.is/application/ui-forms' -import { ApplicationType } from '../../../shared' +import { ApplicationType, Routes } from '../../../shared' export const personalSubSection = buildSubSection({ id: 'personalSubSection', title: userInformation.applicant.subSectionTitle, children: [ buildMultiField({ - id: 'personalMultiField', + id: Routes.PERSONAL, title: userInformation.applicant.pageTitle, description: userInformation.applicant.description, children: [ @@ -28,24 +27,6 @@ export const personalSubSection = buildSubSection({ phoneRequired: true, readOnly: true, }).children, - buildDescriptionField({ - id: 'otherAddressInfo.subtitle', - title: userInformation.otherAddress.subtitle, - titleVariant: 'h5', - space: 3, - }), - buildTextField({ - id: 'otherAddress.address', - title: userInformation.otherAddress.address, - backgroundColor: 'blue', - width: 'half', - }), - buildTextField({ - id: 'otherAddress.postalCode', - title: userInformation.otherAddress.postalCode, - backgroundColor: 'blue', - width: 'half', - }), buildDescriptionField({ id: 'applicationTypeInfo.subtitle', title: userInformation.applicationType.subtitle, diff --git a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/SupportingDocumentsSubSection.ts b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/SupportingDocumentsSubSection.ts deleted file mode 100644 index 7887a94c9ee9..000000000000 --- a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/SupportingDocumentsSubSection.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - buildFileUploadField, - buildMultiField, - buildSubSection, -} from '@island.is/application/core' -import { userInformation } from '../../../lib/messages' -import { FILE_SIZE_LIMIT, FILE_TYPES_ALLOWED } from '../../../shared' - -export const supportingDocumentsSubSection = buildSubSection({ - id: 'supportingDocumentsSubSection', - title: userInformation.supportingDocuments.subSectionTitle, - children: [ - buildMultiField({ - id: 'supportingDocumentsMultiField', - title: userInformation.supportingDocuments.pageTitle, - description: userInformation.supportingDocuments.description, - children: [ - buildFileUploadField({ - id: 'supportingDocuments.attachments', - title: '', - introduction: '', - uploadAccept: FILE_TYPES_ALLOWED, - maxSize: FILE_SIZE_LIMIT, - uploadHeader: userInformation.supportingDocuments.fileUploadHeader, - uploadDescription: - userInformation.supportingDocuments.fileUploadDescription, - uploadButtonLabel: - userInformation.supportingDocuments.fileUploadButtonLabel, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/index.ts b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/index.ts index a98a1730f03e..26b331bd64ad 100644 --- a/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/index.ts +++ b/libs/application/templates/secondary-school/src/forms/SecondarySchoolForm/UserInformationSection/index.ts @@ -2,14 +2,9 @@ import { buildSection } from '@island.is/application/core' import { userInformation } from '../../../lib/messages' import { personalSubSection } from './PersonalSubSection' import { custodianSubSection } from './CustodianSubSection' -import { supportingDocumentsSubSection } from './SupportingDocumentsSubSection' export const userInformationSection = buildSection({ id: 'userInformationSection', title: userInformation.general.sectionTitle, - children: [ - personalSubSection, - custodianSubSection, - supportingDocumentsSubSection, - ], + children: [personalSubSection, custodianSubSection], }) diff --git a/libs/application/templates/secondary-school/src/index.ts b/libs/application/templates/secondary-school/src/index.ts index f672751362d0..947bf275cbf0 100644 --- a/libs/application/templates/secondary-school/src/index.ts +++ b/libs/application/templates/secondary-school/src/index.ts @@ -7,6 +7,7 @@ export const getDataProviders = () => import('./dataProviders/') export type SecondarySchoolAnswers = SecondarySchool // export * from './utils' +export { ApplicationType } from './shared/constants' // export * from './lib/messages/externalData' export * from './lib/messages/error' diff --git a/libs/application/templates/secondary-school/src/lib/SecondarySchoolTemplate.ts b/libs/application/templates/secondary-school/src/lib/SecondarySchoolTemplate.ts index 68af0fd723ed..e52add55791d 100644 --- a/libs/application/templates/secondary-school/src/lib/SecondarySchoolTemplate.ts +++ b/libs/application/templates/secondary-school/src/lib/SecondarySchoolTemplate.ts @@ -25,26 +25,17 @@ import { UserProfileApi, } from '../dataProviders' import { Features } from '@island.is/feature-flags' -import { SecondarySchoolAnswers } from '..' +import { getEndOfDayUTC, getLastRegistrationEndDate } from '../utils' +import { AuthDelegationType } from '@island.is/shared/types' +import { ApiScope } from '@island.is/auth/scopes' const pruneInDaysAfterRegistrationCloses = ( application: Application, days: number, ) => { - const answers = application.answers as SecondarySchoolAnswers - - // get the last regirastion date out of all the programs selected - const lastRegistrationEndDate = [ - answers?.selection?.first?.firstProgram?.registrationEndDate, - answers?.selection?.first?.secondProgram?.registrationEndDate, - answers?.selection?.second?.firstProgram?.registrationEndDate, - answers?.selection?.second?.secondProgram?.registrationEndDate, - answers?.selection?.third?.firstProgram?.registrationEndDate, - answers?.selection?.third?.secondProgram?.registrationEndDate, - ] - .filter((x) => !!x) - .map((x) => (x ? new Date(x) : new Date())) - .sort((a, b) => b.getTime() - a.getTime())[0] // descending order + const lastRegistrationEndDate = getLastRegistrationEndDate( + application.answers, + ) // add days to registration end date const date = lastRegistrationEndDate @@ -53,9 +44,7 @@ const pruneInDaysAfterRegistrationCloses = ( date.setDate(date.getDate() + days) // set time to right before midnight - const pruneDate = new Date(date.toUTCString()) - pruneDate.setHours(23, 59, 59) - return pruneDate + return getEndOfDayUTC(date) } const template: ApplicationTemplate< @@ -70,8 +59,13 @@ const template: ApplicationTemplate< ApplicationConfigurations.SecondarySchool.translation, ], dataSchema: SecondarySchoolSchema, - //TODOx should allow delegation? featureFlag: Features.SecondarySchoolEnabled, + allowedDelegations: [ + { + type: AuthDelegationType.Custom, + }, + ], + requiredScopes: [ApiScope.menntamalastofnun], stateMachineConfig: { initial: States.PREREQUISITES, states: { @@ -93,7 +87,7 @@ const template: ApplicationTemplate< ], }, lifecycle: EphemeralStateLifeCycle, - onEntry: defineTemplateApi({ + onExit: defineTemplateApi({ action: ApiActions.validateCanCreate, }), roles: [ @@ -141,7 +135,10 @@ const template: ApplicationTemplate< }, ], }, - lifecycle: pruneAfterDays(1), //TODOx ef hægt að fá sameiginlegt registrationEndDate fyrir alla, væri gott að setja það inn hér + lifecycle: pruneAfterDays(7), + onExit: defineTemplateApi({ + action: ApiActions.validateCanCreate, + }), roles: [ { id: Roles.APPLICANT, diff --git a/libs/application/templates/secondary-school/src/lib/dataSchema.ts b/libs/application/templates/secondary-school/src/lib/dataSchema.ts index b5a74f65458d..83ca6ab5a042 100644 --- a/libs/application/templates/secondary-school/src/lib/dataSchema.ts +++ b/libs/application/templates/secondary-school/src/lib/dataSchema.ts @@ -1,7 +1,7 @@ import { applicantInformationSchema } from '@island.is/application/ui-forms' import { z } from 'zod' import { ApplicationType } from '../shared' -import { YES } from '@island.is/application/core' +import { YES } from '@island.is/application/types' const FileDocumentSchema = z.object({ name: z.string(), @@ -11,6 +11,7 @@ const FileDocumentSchema = z.object({ const CustodianSchema = z .object({ nationalId: z.string().optional(), + name: z.string().optional(), email: z.string().optional(), phone: z.string().optional(), }) @@ -62,28 +63,42 @@ const OtherContactSchema = z const SelectionSchema = z .object({ include: z.boolean().optional(), - school: z.object({ - id: z.string().optional(), - name: z.string().optional(), - }), - firstProgram: z.object({ - id: z.string().optional(), - name: z.string().optional(), - registrationEndDate: z.string().optional(), - }), - secondProgram: z.object({ - id: z.string().optional(), - name: z.string().optional(), - registrationEndDate: z.string().optional(), - }), - thirdLanguage: z.object({ - code: z.string().optional(), - name: z.string().optional(), - }), - nordicLanguage: z.object({ - code: z.string().optional(), - name: z.string().optional(), - }), + school: z + .object({ + id: z.string().optional(), + name: z.string().optional(), + }) + .optional(), + firstProgram: z + .object({ + id: z.string().optional(), + nameIs: z.string().optional(), + nameEn: z.string().optional(), + registrationEndDate: z.string().optional(), + }) + .optional(), + secondProgram: z + .object({ + require: z.boolean().optional(), + id: z.string().optional(), + nameIs: z.string().optional(), + nameEn: z.string().optional(), + registrationEndDate: z.string().optional(), + }) + .optional(), + thirdLanguage: z + .object({ + code: z.string().optional(), + name: z.string().optional(), + }) + .optional(), + nordicLanguage: z + .object({ + code: z.string().optional(), + name: z.string().optional(), + }) + .optional(), + requestDormitory: z.array(z.enum([YES])).optional(), }) .refine( ({ include, school }) => { @@ -102,17 +117,11 @@ const SelectionSchema = z .refine( ({ include, secondProgram }) => { if (!include) return true + if (!secondProgram?.require) return true return !!secondProgram?.id }, { path: ['secondProgram.id'] }, ) - .refine( - ({ include, thirdLanguage }) => { - if (!include) return true - return !!thirdLanguage?.code - }, - { path: ['thirdLanguage.code'] }, - ) export const SecondarySchoolSchema = z.object({ approveExternalData: z.boolean().refine((v) => v), @@ -120,35 +129,24 @@ export const SecondarySchoolSchema = z.object({ phoneRequired: true, emailRequired: true, }), - otherAddress: z.object({ - address: z.string().optional(), - postalCode: z.string().optional(), - }), applicationType: z.object({ type: z.enum([ ApplicationType.FRESHMAN, ApplicationType.GENERAL_APPLICATION, ]), }), - custodians: z.array(CustodianSchema), - otherContact: OtherContactSchema.optional(), - supportingDocuments: z - .object({ - attachments: z.array(FileDocumentSchema), - }) - .optional(), + custodians: z.array(CustodianSchema).max(2), + otherContacts: z.array(OtherContactSchema).max(2), selection: z.object({ first: SelectionSchema, - second: SelectionSchema, + second: SelectionSchema.optional(), third: SelectionSchema.optional(), }), extraInformation: z.object({ nativeLanguage: z.string().optional(), - hasDisability: z.array(z.enum([YES])).optional(), - disabilityDescription: z.string().optional(), otherDescription: z.string().optional(), + supportingDocuments: z.array(FileDocumentSchema).optional(), }), - approveTermsAndConditions: z.array(z.enum([YES])), }) export type SecondarySchool = z.TypeOf diff --git a/libs/application/templates/secondary-school/src/lib/messages/conclusion.ts b/libs/application/templates/secondary-school/src/lib/messages/conclusion.ts index 187932f89c1c..627c3c7a82b8 100644 --- a/libs/application/templates/secondary-school/src/lib/messages/conclusion.ts +++ b/libs/application/templates/secondary-school/src/lib/messages/conclusion.ts @@ -14,7 +14,8 @@ export const conclusion = { }, alertMessage: { id: 'ss.application:confirmation.general.alertMessage', - defaultMessage: 'Umsókn þín í framhaldsskóla hefur verið móttekin!', + defaultMessage: + 'Umsókn þín í framhaldsskóla hefur verið móttekin! Hægt er að senda inn nýja umsókn til xx. júní 2025', description: 'Conclusion general alert message', }, accordionTitle: { @@ -24,7 +25,11 @@ export const conclusion = { }, accordionText: { id: 'ss.application:confirmation.general.accordionText', - defaultMessage: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget congue nisi. Maecenas auctor nulla lorem, id eleifend ante dapibus vitae.`, + defaultMessage: + `* **xx. júní 2025**` + + `\n\n Umsóknartímabili lýkur. Ekki verður farið að vinna úr umsóknum fyrr en eftir þessa dagsetningu. Ef þú af einhverjum ástæðum vilt gera breytingar þarftu að eyða núverandi umsókn og gera nýja áður en tímabilinu lýkur.\n` + + `* **yy. - zz. júní 2025**` + + `\n\n Unnið er úr umsóknum á þessu tímabili og verður tilkynning send á alla umsækjendur. Hnipp verður sent í gegnum island.is og ættir þú að fá tilkynningu í appið og tölvupóst um niðurstöðuna.`, description: 'Conclusion accordion text', }, }), diff --git a/libs/application/templates/secondary-school/src/lib/messages/error.ts b/libs/application/templates/secondary-school/src/lib/messages/error.ts index 95127f5bf27b..f602a2e77096 100644 --- a/libs/application/templates/secondary-school/src/lib/messages/error.ts +++ b/libs/application/templates/secondary-school/src/lib/messages/error.ts @@ -17,4 +17,27 @@ export const error = defineMessages({ 'Þú ert með aðra opna umsókn í gangi, vinsamlegast eyðið henni áður en opnað er nýja umsókn', description: 'Error validate can create description', }, + errorDeletePastRegistrationEndTitle: { + id: 'ss.application:error.errorDeletePastRegistrationEndTitle', + defaultMessage: 'Ekki er hægt að eyða umsókn', + description: 'Error delete application past registration end date title', + }, + errorDeletePastRegistrationEndDescription: { + id: 'ss.application:error.errorDeletePastRegistrationEndDescription', + defaultMessage: + 'Ekki er hægt að eyða umsókn eftir að skráningartímabili lýkur', + description: + 'Error delete application past registration end date description', + }, + errorPastRegistrationDateTitle: { + id: 'ss.application:error.errorPastRegistrationDateTitle', + defaultMessage: 'Athugið', + description: 'Error past registration date title', + }, + errorPastRegistrationDateDescription: { + id: 'ss.application:error.errorPastRegistrationDateDescription', + defaultMessage: + 'Ekki er hægt að senda inn umsókn, þar sem umsóknartímabilinu hefur lokið', + description: 'Error past registration date title', + }, }) diff --git a/libs/application/templates/secondary-school/src/lib/messages/externalData.ts b/libs/application/templates/secondary-school/src/lib/messages/externalData.ts index 4f7e046148af..d0870af27db1 100644 --- a/libs/application/templates/secondary-school/src/lib/messages/externalData.ts +++ b/libs/application/templates/secondary-school/src/lib/messages/externalData.ts @@ -58,12 +58,13 @@ export const externalData = { title: { id: 'ss.application:externalData.educationalCareer.title', defaultMessage: - 'Upplýsingar um námsferil frá mínum síðum Ísland.is og námsumsjónarkerfum', + 'Upplýsingar um námsferil frá mínum síðum Ísland.is og námsumsjónarkerfum skólanna', description: 'Information about your educational career', }, subTitle: { id: 'ss.application:externalData.educationalCareer.subTitle', - defaultMessage: 'Upplýsingar um núverandi námsgráður og skírteini.', + defaultMessage: + 'Lokaeinkunnir úr grunnskóla eða upplýsingar um fyrirliggjandi námsgráður og skírteini.', description: 'We will fetch degrees and certificates', }, }), diff --git a/libs/application/templates/secondary-school/src/lib/messages/extraInformation.ts b/libs/application/templates/secondary-school/src/lib/messages/extraInformation.ts index 5191047bc94a..6a2e93832c16 100644 --- a/libs/application/templates/secondary-school/src/lib/messages/extraInformation.ts +++ b/libs/application/templates/secondary-school/src/lib/messages/extraInformation.ts @@ -36,35 +36,18 @@ export const extraInformation = { description: 'Native language select placeholder', }, }), - disability: defineMessages({ - subtitle: { - id: 'ss.application:extraInformation.disabilites.subtitle', - defaultMessage: 'Fatlanir', - description: 'Disabilites sub title', - }, - checkboxLabel: { - id: 'ss.application:extraInformation.disabilites.checkboxLabel', - defaultMessage: 'Fötlunargreining liggur fyrir', - description: 'Disabilites checkbox label', - }, - textareaLabel: { - id: 'ss.application:extraInformation.disabilites.textareaLabel', - defaultMessage: 'Fötlun', - description: 'Disabilites textarea label', - }, - textareaPlaceholder: { - id: 'ss.application:extraInformation.disabilites.textareaPlaceholder', - defaultMessage: - 'Vinsamlegast skrifið stutta lýsingu á fötlun og þörf fyrir helstu þjónustu', - description: 'Disabilites textarea placeholder', - }, - }), other: defineMessages({ subtitle: { id: 'ss.application:extraInformation.other.subtitle', - defaultMessage: 'Aðrar upplýsingar sem nemandi vill koma á framfæri', + defaultMessage: 'Aðrar upplýsingar sem umsækjandi vill koma á framfæri', description: 'Other sub title', }, + description: { + id: 'ss.application:extraInformation.other.description', + defaultMessage: + 'Hérna er hægt að tilgreina upplýsingar sem þú telur mikilvægt að koma á framfæri, s.s. ef fötlunargreining liggur fyrir eða aðrar upplýsingar sem þú telur nauðsynlegt að skólinn búi yfir.', + description: 'Other description', + }, textareaLabel: { id: 'ss.application:extraInformation.other.textareaLabel', defaultMessage: 'Annað', @@ -76,4 +59,32 @@ export const extraInformation = { description: 'Other textarea placeholder', }, }), + supportingDocuments: defineMessages({ + subtitle: { + id: 'ss.application:extraInformation.supportingDocuments.subtitle', + defaultMessage: 'Fylgigögn', + description: 'Supporting documents sub title', + }, + description: { + id: 'ss.application:extraInformation.supportingDocuments.description', + defaultMessage: + 'Hér getur þú hlaðið inn gögnum sem þú telur mikilvægt að fylgi umsókninni og styðji við hana. Athugið að ekki er nauðsynlegt að láta nein viðbótargögn fylgja umsókn.', + description: 'Supporting documents description', + }, + fileUploadHeader: { + id: 'ss.application:extraInformation.supportingDocuments.fileUploadHeader', + defaultMessage: 'Viðhengi', + description: 'Supporting documents file upload title', + }, + fileUploadDescription: { + id: 'ss.application:extraInformation.supportingDocuments.fileUploadDescription', + defaultMessage: 'Tekið er við skjölum með endingu: .pdf, .docx, .rtf', + description: 'Supporting documents file upload title', + }, + fileUploadButtonLabel: { + id: 'ss.application:extraInformation.supportingDocuments.fileUploadButtonLabel', + defaultMessage: 'Velja skjöl til að hlaða upp', + description: 'Supporting documents file upload title', + }, + }), } diff --git a/libs/application/templates/secondary-school/src/lib/messages/overview.ts b/libs/application/templates/secondary-school/src/lib/messages/overview.ts index e08704513590..16672f9f30c2 100644 --- a/libs/application/templates/secondary-school/src/lib/messages/overview.ts +++ b/libs/application/templates/secondary-school/src/lib/messages/overview.ts @@ -60,20 +60,12 @@ export const overview = { description: 'Other contact phone label', }, }), - supportingDocuments: defineMessages({ + selection: defineMessages({ subtitle: { - id: 'ss.application:overview.supportingDocuments.subtitle', - defaultMessage: 'Fylgigögn', - description: 'Supporting documents subtitle', - }, - description: { - id: 'ss.application:overview.supportingDocuments.description', - defaultMessage: - 'Risus eget mauris ut vestibulum scelerisque ac. Vivamus vitae purus.', - description: 'Supporting documents label', + id: 'ss.application:overview.selection.subtitle', + defaultMessage: 'Val á skóla', + description: 'Selection subtitle', }, - }), - selection: defineMessages({ firstSubtitle: { id: 'ss.application:overview.selection.firstSubtitle', defaultMessage: 'Fyrsta val', @@ -111,34 +103,18 @@ export const overview = { defaultMessage: 'Móðurmál', description: 'Native language label', }, - hasDisabilityLabel: { - id: 'ss.application:overview.extraInformation.hasDisabilityLabel', - defaultMessage: 'Fötlunargreining', - description: 'Has disability label', - }, - hasDisabilityYesValue: { - id: 'ss.application:overview.extraInformation.hasDisabilityYesValue', - defaultMessage: 'Já', - description: 'Has disability yes value', - }, - disabilityDescriptionLabel: { - id: 'ss.application:overview.extraInformation.disabilityDescriptionLabel', - defaultMessage: 'Fötlun', - description: 'Disability description label', - }, otherLabel: { id: 'ss.application:overview.extraInformation.otherLabel', defaultMessage: 'Aðrar upplýsingar', description: 'Other label', }, + supportingDocumentsLabel: { + id: 'ss.application:overview.extraInformation.supportingDocumentsLabel', + defaultMessage: 'Fylgigögn', + description: 'Supporting documents label', + }, }), confirmation: defineMessages({ - checkboxMessage: { - id: 'ss.application:overview.confirmation.checkboxMessage', - defaultMessage: - 'Með því að haka í þetta box samþykki ég að gangast undir skilmála þess framhaldsskóla sem ég fæ pláss í. Skilmálar eru á vefsíðum skólanna.', - description: 'Confirm checkbox message', - }, confirm: { id: 'ss.application:overview.confirmation.confirm', defaultMessage: 'Áfram', diff --git a/libs/application/templates/secondary-school/src/lib/messages/school.ts b/libs/application/templates/secondary-school/src/lib/messages/school.ts index c9efd905c99a..b8f9c8da3e41 100644 --- a/libs/application/templates/secondary-school/src/lib/messages/school.ts +++ b/libs/application/templates/secondary-school/src/lib/messages/school.ts @@ -37,7 +37,7 @@ export const school = { }, thirdLanguageLabel: { id: 'ss.application:school.selection.thirdLanguageLabel', - defaultMessage: 'Þriðja tungumál á bóknámsbraut', + defaultMessage: 'Þriðja tungumál ef við á', description: 'Third language select label', }, nordicLanguageLabel: { @@ -51,6 +51,11 @@ export const school = { 'Þetta er aðeins valmöguleiki ef nemandinn er með bakgrunn í öðru norðurlandamáli en dönsku. Önnur norðurlandamál eru yfirleitt kennd fyrir utan stundarskrá og stundum í öðrum skóla.', description: 'Nordic language alert message', }, + requestDormitoryCheckboxLabel: { + id: 'ss.application:school.selection.requestDormitoryCheckboxLabel', + defaultMessage: 'Ég óska eftir heimavist', + description: 'Request dormitory checkbox label', + }, schoolDuplicateError: { id: 'ss.application:school.selection.schoolDuplicateError', defaultMessage: 'Það má ekki velja sama skóla tvisvar', @@ -75,6 +80,11 @@ export const school = { defaultMessage: 'Annað val', description: 'Second selection sub title', }, + addSubtitle: { + id: 'ss.application:school.secondSelection.addSubtitle', + defaultMessage: 'Viltu bæta við öðrum skóla?', + description: 'Add second selection sub title', + }, }), thirdSelection: defineMessages({ subtitle: { @@ -90,7 +100,7 @@ export const school = { addDescription: { id: 'ss.application:school.thirdSelection.addDescription', defaultMessage: - 'Ef þú vilt auka líkurnar á að komast inn í skóla sem þú hefur valið geturðu bætt við þriðja vali. Athugið að hverjum nemanda er úthlutaður aðeins einn skóli. ', + 'Ákveðnir framhaldsskólar á Íslandi fá jafnan fleiri umsóknir en pláss leyfa. Til að auka líkur á að komast inn í skóla að þínu vali getur þú bætt inn þriðja skólanum. Mikilvægt er að kynna sér inntökuskilyrði hvers skóla vandlega. Fáir þú ekki inn í einhverjum þeirra skóla sem þú sækir um mun Miðstöð menntunar og skólaþjónustu útvega þér skólapláss.', description: 'Add third selection description', }, addButtonLabel: { diff --git a/libs/application/templates/secondary-school/src/lib/messages/userInformation.ts b/libs/application/templates/secondary-school/src/lib/messages/userInformation.ts index db7c5cb3ce60..3f8ffedb9f9d 100644 --- a/libs/application/templates/secondary-school/src/lib/messages/userInformation.ts +++ b/libs/application/templates/secondary-school/src/lib/messages/userInformation.ts @@ -31,37 +31,20 @@ export const userInformation = { description: 'Applicant subtitle', }, }), - otherAddress: defineMessages({ - subtitle: { - id: 'ss.application:userInformation.otherAddress.subtitle', - defaultMessage: 'Dvalarstaður ef annar en lögheimili', - description: 'Other address subtitle', - }, - address: { - id: 'ss.application:userInformation.otherAddress.address', - defaultMessage: 'Heimilisfang', - description: 'Other address address', - }, - postalCode: { - id: 'ss.application:userInformation.otherAddress.postalCode', - defaultMessage: 'Póstnúmer og sveitarfélag', - description: 'Other address postal code', - }, - }), applicationType: defineMessages({ subtitle: { id: 'ss.application:userInformation.applicationType.subtitle', - defaultMessage: 'Núverandi staða umsækjanda', + defaultMessage: 'Tegund umsækjanda', description: 'Application typa subtitle', }, freshmanOptionTitle: { id: 'ss.application:userInformation.applicationType.freshmanOptionTitle', - defaultMessage: 'Nýnemi að koma úr 10. bekk', + defaultMessage: 'Nýnemi (að koma beint úr grunnskóla)', description: 'Freshman option title', }, generalApplicationOptionTitle: { id: 'ss.application:userInformation.applicationType.generalApplicationOptionTitle', - defaultMessage: 'Almenn umsókn', + defaultMessage: 'Aðrir', description: 'General application option title', }, }), @@ -101,10 +84,10 @@ export const userInformation = { defaultMessage: 'Heimilisfang', description: 'Custodian address', }, - postalCode: { - id: 'ss.application:userInformation.custodian.postalCode', + postalCodeAndCity: { + id: 'ss.application:userInformation.custodian.postalCodeAndCity', defaultMessage: 'Póstnúmer og staður', - description: 'Custodian postal code', + description: 'Custodian postal code and city', }, email: { id: 'ss.application:userInformation.custodian.email', @@ -118,6 +101,16 @@ export const userInformation = { }, }), otherContact: defineMessages({ + subSectionTitle: { + id: 'ss.application:userInformation.otherContact.subSectionTitle', + defaultMessage: 'Tengiliður', + description: 'Title of other contact sub section', + }, + pageTitle: { + id: 'ss.application:userInformation.otherContact.pageTitle', + defaultMessage: 'Tengiliður', + description: 'Title of other contact page', + }, subtitle: { id: 'ss.application:userInformation.otherContact.subtitle', defaultMessage: 'Aðrir tengiliðir', @@ -154,37 +147,4 @@ export const userInformation = { description: 'Other contact phone', }, }), - supportingDocuments: defineMessages({ - subSectionTitle: { - id: 'ss.application:userInformation.supportingDocuments.subSectionTitle', - defaultMessage: 'Fylgigögn', - description: 'Title of supporting documents sub section', - }, - pageTitle: { - id: 'ss.application:userInformation.supportingDocuments.pageTitle', - defaultMessage: 'Fylgigögn', - description: 'Title of supporting documents page', - }, - description: { - id: 'ss.application:userInformation.supportingDocuments.description', - defaultMessage: - 'Þú getur hengt hér inn skjöl eins og staðfestingu sérfræðings á greiningu t.d. vegna sértækra námsörðugleika (ekki mælt með að láta full greiningargögn fylgja með fyrr en ef skóli biður um þau þegar umsókn hefur verið samþykkt), meðmæli eða annað það sem þú vilt að fylgi umsókninni. Ef þú hefur ekki tök á að setja skjöl rafrænt hér inn getur þú sent þau í pósti til þeirra skóla sem sótt er um.', - description: 'Description of supporting documents page', - }, - fileUploadHeader: { - id: 'ss.application:userInformation.supportingDocuments.fileUploadHeader', - defaultMessage: 'Viðhengi', - description: 'File upload title', - }, - fileUploadDescription: { - id: 'ss.application:userInformation.supportingDocuments.fileUploadDescription', - defaultMessage: 'Tekið er við skjölum með endingu: .pdf, .docx, .rtf', - description: 'File upload title', - }, - fileUploadButtonLabel: { - id: 'ss.application:userInformation.supportingDocuments.fileUploadButtonLabel', - defaultMessage: 'Velja skjöl til að hlaða upp', - description: 'File upload title', - }, - }), } diff --git a/libs/application/templates/secondary-school/src/shared/constants.ts b/libs/application/templates/secondary-school/src/shared/constants.ts index ee2f7463c099..b6b4749b79c2 100644 --- a/libs/application/templates/secondary-school/src/shared/constants.ts +++ b/libs/application/templates/secondary-school/src/shared/constants.ts @@ -1,5 +1,3 @@ -import { Language } from './types' - export enum ApplicationType { FRESHMAN = 'FRESHMAN', GENERAL_APPLICATION = 'GENERAL_APPLICATION', @@ -8,7 +6,9 @@ export enum ApplicationType { export const FILE_SIZE_LIMIT = 10000000 export const FILE_TYPES_ALLOWED = '.pdf, .docx, .rtf' -export const NORDIC_LANGUAGES: Language[] = [ - { code: 'sv', nameIs: 'Sænska', nameEn: 'Swedish' }, - { code: 'no', nameIs: 'Norska', nameEn: 'Norwegian' }, -] +export enum Routes { + PERSONAL = 'personalMultiField', + CUSTODIAN = 'custodianMultiField', + EXTRA_INFORMATION = 'extraInformationMultiField', + SCHOOL = 'schoolMultiField', +} diff --git a/libs/application/templates/secondary-school/src/shared/types.ts b/libs/application/templates/secondary-school/src/shared/types.ts index 4e5f9b374495..c4635096e6ba 100644 --- a/libs/application/templates/secondary-school/src/shared/types.ts +++ b/libs/application/templates/secondary-school/src/shared/types.ts @@ -1,19 +1,17 @@ export type Language = { code: string - //TODOx þýða í öll dropdown - nameIs: string - nameEn: string + name: string } export type SecondarySchool = { id: string name: string thirdLanguages: Language[] + nordicLanguages: Language[] } export type Program = { id: string - //TODOx þýða í öll dropdown nameIs: string nameEn: string registrationEndDate: Date diff --git a/libs/application/templates/secondary-school/src/utils/index.ts b/libs/application/templates/secondary-school/src/utils/index.ts index b6c30d95470f..8607aff4d554 100644 --- a/libs/application/templates/secondary-school/src/utils/index.ts +++ b/libs/application/templates/secondary-school/src/utils/index.ts @@ -1,10 +1,12 @@ import { getValueViaPath } from '@island.is/application/core' import { ExternalData, + FormValue, NationalRegistryParent, } from '@island.is/application/types' import { parsePhoneNumberFromString } from 'libphonenumber-js' import kennitala from 'kennitala' +import { SecondarySchoolAnswers } from '..' export const getParent = ( externalData: ExternalData, @@ -17,7 +19,7 @@ export const getParent = ( return parents?.[index] } -export const hasParent = ( +export const getHasParent = ( externalData: ExternalData, index: number, ): boolean => { @@ -35,3 +37,62 @@ export const formatKennitala = (nationalId: string | undefined): string => { if (!nationalId) return '' return kennitala.format(nationalId, '-') } + +export const getTranslatedProgram = ( + lang: string, + program?: { + nameIs?: string + nameEn?: string + }, +): string => { + return (lang === 'is' ? program?.nameIs : program?.nameEn) || '' +} + +export const hasDuplicates = (arr: string[]): boolean => { + for (let i = 0; i < arr.length; i++) { + for (let j = i + 1; j < arr.length; j++) { + if (arr[i] === arr[j]) { + return true // Duplicate found + } + } + } + return false // No duplicates +} + +const getRegistrationEndDates = (formValue: FormValue): Date[] => { + const answers = formValue as SecondarySchoolAnswers + return [ + answers?.selection?.first?.firstProgram?.registrationEndDate, + answers?.selection?.first?.secondProgram?.registrationEndDate, + answers?.selection?.second?.firstProgram?.registrationEndDate, + answers?.selection?.second?.secondProgram?.registrationEndDate, + answers?.selection?.third?.firstProgram?.registrationEndDate, + answers?.selection?.third?.secondProgram?.registrationEndDate, + ] + .filter((x) => !!x) + .map((x) => (x ? new Date(x) : new Date())) +} + +export const getFirstRegistrationEndDate = (answers: FormValue): Date => { + return getRegistrationEndDates(answers).sort( + (a, b) => a.getTime() - b.getTime(), // ascending order + )[0] +} + +export const getLastRegistrationEndDate = (answers: FormValue): Date => { + return getRegistrationEndDates(answers).sort( + (a, b) => b.getTime() - a.getTime(), // descending order + )[0] +} + +export const getEndOfDayUTC = (date: Date | undefined): Date => { + if (!date) date = new Date() + + // Clone the date to avoid mutating the original + const newDate = new Date(date.getTime()) + + // Set the time to 23:59:59.999 UTC + newDate.setUTCHours(23, 59, 59, 999) + + return newDate +} diff --git a/libs/auth/scopes/src/lib/api.scope.ts b/libs/auth/scopes/src/lib/api.scope.ts index b21d714c0228..0d6d419a0761 100644 --- a/libs/auth/scopes/src/lib/api.scope.ts +++ b/libs/auth/scopes/src/lib/api.scope.ts @@ -32,4 +32,5 @@ export enum ApiScope { carRecycling = '@island.is/applications/urvinnslusjodur', energyFunds = '@island.is/applications/orkusjodur', signatureCollection = '@island.is/signature-collection', + menntamalastofnun = '@island.is/applications/mms', } diff --git a/libs/clients/secondary-school/src/clientConfig.json b/libs/clients/secondary-school/src/clientConfig.json index dbfa78cb00a5..0efde8654f90 100644 --- a/libs/clients/secondary-school/src/clientConfig.json +++ b/libs/clients/secondary-school/src/clientConfig.json @@ -236,8 +236,16 @@ }, "get": { "tags": ["Applications"], - "summary": "[GET] Get a list of applications (Not implemented).", + "summary": "[GET] Get a list of applications.", "parameters": [ + { + "name": "nationalId", + "in": "query", + "description": "(Optional) If searching for appliactions by National ID", + "schema": { + "type": "string" + } + }, { "name": "rowOffset", "in": "query", @@ -447,15 +455,142 @@ "tags": ["Programmes"], "summary": "[GET] Get a list of programmes.", "parameters": [ + { + "name": "rowOffset", + "in": "query", + "description": "(optional) Offset for pagination. Row where to start fetching from. Default 0", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "fetchSize", + "in": "query", + "description": "(optional) Fetch size for pagination. How many rows to fetch. Default 100000", + "schema": { + "type": "integer", + "format": "int32", + "default": 100000 + } + }, { "name": "schoolId", "in": "query", - "description": "(optional) SchoolId.", + "description": "(optional) Id of the school which the programmes belong to.", "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProgrammeDto" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProgrammeDto" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProgrammeDto" + } + } + } + } + } + } + } + }, + "/v1/programmes/simple": { + "get": { + "tags": ["Programmes"], + "summary": "[GET] Get a list of programmes.", + "parameters": [ + { + "name": "rowOffset", + "in": "query", + "description": "(optional) Offset for pagination. Row where to start fetching from. Default 0", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "fetchSize", + "in": "query", + "description": "(optional) Fetch size for pagination. How many rows to fetch. Default 1000000", + "schema": { + "type": "integer", + "format": "int32", + "default": 100000 + } }, + { + "name": "schoolId", + "in": "query", + "description": "(optional) Id of the school which the programmes belong to.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProgrammeSimpleDto" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProgrammeSimpleDto" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProgrammeSimpleDto" + } + } + } + } + } + } + } + }, + "/v1/programmes/all": { + "get": { + "tags": ["Programmes"], + "summary": "[GET] Get a list of programmes.", + "parameters": [ { "name": "rowOffset", "in": "query", @@ -510,7 +645,7 @@ } } }, - "/v1/programmes/school/{schoolId}": { + "/v1/programmes/schools/{schoolId}": { "get": { "tags": ["Programmes"], "summary": "[GET] Get all programmes for a school.", @@ -747,6 +882,70 @@ } } }, + "/v1/schools/{schoolId}/thirdlanguage/{languageId}": { + "post": { + "tags": ["Schools"], + "summary": "[POST] Add a language to a school.", + "parameters": [ + { + "name": "schoolId", + "in": "path", + "description": "(required) School ID.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "languageId", + "in": "path", + "description": "(required) Language ID.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "delete": { + "tags": ["Schools"], + "summary": "[DELETE] Removes a link between a school and a third language.", + "parameters": [ + { + "name": "schoolId", + "in": "path", + "description": "(required) School ID.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "languageId", + "in": "path", + "description": "(required) Language ID.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/v1/semesters/{semesterId}": { "get": { "tags": ["Semesters"], @@ -1048,79 +1247,260 @@ } } } - } - }, - "components": { - "schemas": { - "ApplicationBaseDto": { - "type": "object", - "properties": { - "applicantNationalId": { - "type": "string", - "nullable": true - }, - "applicantName": { - "type": "string", - "nullable": true - }, - "phoneNumber": { - "type": "string", - "nullable": true - }, - "email": { - "type": "string", - "nullable": true - }, - "placeOfResidence": { - "type": "string", - "nullable": true - }, - "postCode": { - "type": "string", - "nullable": true - }, - "municipality": { - "type": "string", - "nullable": true - }, - "nextOfKin": { - "type": "array", - "items": { - "$ref": "#/components/schemas/NextOfKinDto" - }, - "nullable": true - }, - "speakingLanguage": { - "type": "string", - "nullable": true - }, - "northernLanguage": { - "$ref": "#/components/schemas/NorthernLanguage" - }, - "hasDisabilityDiagnosis": { - "type": "boolean" - }, - "disabilityDiagnosisDescription": { - "type": "string", - "nullable": true - }, - "otherInformation": { - "type": "string", - "nullable": true - }, - "applicationChoices": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationChoicesDto" - }, - "nullable": true - } - }, - "additionalProperties": false - }, - "ApplicationChoicesDto": { - "type": "object", - "properties": { + }, + "/v1/users": { + "get": { + "tags": ["Users"], + "summary": "[GET] Fetches all the users for current users school.", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + } + } + } + } + }, + "post": { + "tags": ["Users"], + "summary": "[POST] Create a new user for the current users school.", + "requestBody": { + "description": "The user information.", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + } + } + } + } + }, + "/v1/users/school": { + "post": { + "tags": ["Users"], + "summary": "[POST] Create a new user for a school.", + "requestBody": { + "description": "The user information.", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + } + } + } + } + }, + "/v1/users/{userId}": { + "delete": { + "tags": ["Users"], + "summary": "[DELETE] Deletes a user.", + "parameters": [ + { + "name": "userId", + "in": "path", + "description": "The user ID.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ApplicationBaseDto": { + "type": "object", + "properties": { + "applicantNationalId": { + "type": "string", + "nullable": true + }, + "applicantName": { + "type": "string", + "nullable": true + }, + "phoneNumber": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "placeOfResidence": { + "type": "string", + "nullable": true + }, + "postCode": { + "type": "string", + "nullable": true + }, + "municipality": { + "type": "string", + "nullable": true + }, + "nextOfKin": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NextOfKinDto" + }, + "nullable": true + }, + "speakingLanguage": { + "type": "string", + "nullable": true + }, + "northernLanguage": { + "$ref": "#/components/schemas/NorthernLanguage" + }, + "hasDisabilityDiagnosis": { + "type": "boolean" + }, + "disabilityDiagnosisDescription": { + "type": "string", + "nullable": true + }, + "otherInformation": { + "type": "string", + "nullable": true + }, + "applicationChoices": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationChoicesBaseDto" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "ApplicationChoicesBaseDto": { + "type": "object", + "properties": { "priority": { "type": "integer", "format": "int32" @@ -1139,6 +1519,40 @@ "thirdLanguage": { "type": "string", "nullable": true + }, + "northernLanguage": { + "type": "string", + "nullable": true + }, + "requestDormitory": { + "type": "boolean", + "nullable": true + } + }, + "additionalProperties": false + }, + "ApplicationChoicesReturnDto": { + "type": "object", + "properties": { + "priority": { + "type": "integer", + "format": "int32" + }, + "school": { + "$ref": "#/components/schemas/SchoolDto" + }, + "programmeChoice": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProgrammeChoiceDto" + }, + "nullable": true + }, + "thirdLanguage": { + "$ref": "#/components/schemas/LanguageDto" + }, + "northernLanguage": { + "$ref": "#/components/schemas/LanguageDto" } }, "additionalProperties": false @@ -1158,6 +1572,16 @@ "type": "string", "format": "date-time" }, + "speakingLanguage": { + "$ref": "#/components/schemas/Language" + }, + "applicationChoices": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationChoicesReturnDto" + }, + "nullable": true + }, "currentApplicantStatus": { "$ref": "#/components/schemas/CurrentApplicantStatus" }, @@ -1196,10 +1620,6 @@ }, "nullable": true }, - "speakingLanguage": { - "type": "string", - "nullable": true - }, "northernLanguage": { "$ref": "#/components/schemas/NorthernLanguage" }, @@ -1213,13 +1633,6 @@ "otherInformation": { "type": "string", "nullable": true - }, - "applicationChoices": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationChoicesDto" - }, - "nullable": true } }, "additionalProperties": false @@ -1287,7 +1700,7 @@ "applicationChoices": { "type": "array", "items": { - "$ref": "#/components/schemas/ApplicationChoicesDto" + "$ref": "#/components/schemas/ApplicationChoicesBaseDto" }, "nullable": true } @@ -1324,6 +1737,25 @@ }, "additionalProperties": false }, + "CreateUserDto": { + "type": "object", + "properties": { + "nationalId": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "schoolId": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "additionalProperties": false + }, "CurrentApplicantStatus": { "enum": [ "Neither", @@ -1332,26 +1764,77 @@ ], "type": "string" }, - "GradeDto": { + "GradeDto": { + "type": "object", + "properties": { + "schoolNationalId": { + "type": "string", + "nullable": true + }, + "grade": { + "type": "string", + "nullable": true + }, + "courseName": { + "type": "string", + "nullable": true + }, + "review": { + "type": "string", + "nullable": true + }, + "evaluationCriteria": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "Language": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "nullable": true + }, + "nameEnglish": { + "type": "string", + "nullable": true + }, + "code": { + "type": "string", + "nullable": true + }, + "schools": { + "type": "array", + "items": { + "$ref": "#/components/schemas/School" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "LanguageDto": { "type": "object", "properties": { - "schoolNationalId": { - "type": "string", - "nullable": true - }, - "grade": { + "id": { "type": "string", - "nullable": true + "format": "uuid" }, - "courseName": { + "name": { "type": "string", "nullable": true }, - "review": { + "nameEnglish": { "type": "string", "nullable": true }, - "evaluationCriteria": { + "code": { "type": "string", "nullable": true } @@ -1390,7 +1873,7 @@ "additionalProperties": false }, "NorthernLanguage": { - "enum": ["DK", "NO", "SE"], + "enum": ["dk", "no", "se", "fi"], "type": "string" }, "PatchProgrammeDto": { @@ -1447,6 +1930,95 @@ }, "additionalProperties": false }, + "Programme": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "code": { + "type": "string", + "nullable": true + }, + "version": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "titleEnglish": { + "type": "string", + "nullable": true + }, + "credits": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "nullable": true + }, + "statusTranslation": { + "type": "string", + "nullable": true + }, + "prerequisites": { + "type": "string", + "nullable": true + }, + "isMinistryRejected": { + "type": "boolean" + }, + "evaluation": { + "type": "string", + "nullable": true + }, + "registryEnabled": { + "type": "boolean" + }, + "registryStartDate": { + "type": "string", + "format": "date-time" + }, + "registryEndDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "numberOfStudents": { + "type": "integer", + "format": "int32" + }, + "schoolId": { + "type": "string", + "format": "uuid" + }, + "school": { + "$ref": "#/components/schemas/School" + }, + "standardizedProgrammeId": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "standardizedProgramme": { + "$ref": "#/components/schemas/StandardizedProgramme" + }, + "innaId": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, "ProgrammeChoiceDto": { "type": "object", "properties": { @@ -1472,6 +2044,10 @@ "type": "string", "nullable": true }, + "code": { + "type": "string", + "nullable": true + }, "title": { "type": "string", "nullable": true @@ -1531,6 +2107,14 @@ "type": "integer", "format": "int32", "nullable": true + }, + "standardizedProgrammeTitle": { + "type": "string", + "nullable": true + }, + "standardizedProgrammeCode": { + "type": "string", + "nullable": true } }, "additionalProperties": false @@ -1581,6 +2165,77 @@ }, "additionalProperties": false }, + "School": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "nullable": true + }, + "nationalId": { + "type": "string", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + }, + "municipality": { + "type": "string", + "nullable": true + }, + "postCode": { + "type": "string", + "nullable": true + }, + "abbreviation": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "phoneNumber": { + "type": "string", + "nullable": true + }, + "website": { + "type": "string", + "nullable": true + }, + "innaId": { + "type": "integer", + "format": "int32" + }, + "programmes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Programme" + }, + "nullable": true + }, + "semesters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Semester" + }, + "nullable": true + }, + "thirdLanguages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Language" + }, + "nullable": true + } + }, + "additionalProperties": false + }, "SchoolDto": { "type": "object", "properties": { @@ -1628,13 +2283,66 @@ "thirdLanguages": { "type": "array", "items": { - "$ref": "#/components/schemas/ThirdLanguageDto" + "$ref": "#/components/schemas/LanguageDto" }, "nullable": true } }, "additionalProperties": false }, + "Semester": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "schoolId": { + "type": "string", + "format": "uuid" + }, + "school": { + "$ref": "#/components/schemas/School" + }, + "registryEnabled": { + "type": "boolean" + }, + "registryStartDate": { + "type": "string", + "format": "date-time" + }, + "registryEndDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "numberOfStudents": { + "type": "integer", + "format": "int32" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "created": { + "type": "string", + "format": "date-time" + }, + "updated": { + "type": "string", + "format": "date-time" + }, + "createdBy": { + "type": "string", + "nullable": true + }, + "updatedBy": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, "SemesterDto": { "type": "object", "properties": { @@ -1669,7 +2377,29 @@ }, "additionalProperties": false }, - "ThirdLanguageDto": { + "StandardizedProgramme": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string", + "nullable": true + }, + "code": { + "type": "string", + "nullable": true + }, + "innaId": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "UserDto": { "type": "object", "properties": { "id": { @@ -1680,11 +2410,27 @@ "type": "string", "nullable": true }, - "nameEnglish": { + "nationalId": { "type": "string", "nullable": true }, - "code": { + "email": { + "type": "string", + "nullable": true + }, + "phoneNumber": { + "type": "string", + "nullable": true + }, + "schoolId": { + "type": "string", + "format": "uuid" + }, + "schoolName": { + "type": "string", + "nullable": true + }, + "schoolAbbreviation": { "type": "string", "nullable": true } diff --git a/libs/clients/secondary-school/src/lib/secondarySchoolClient.service.ts b/libs/clients/secondary-school/src/lib/secondarySchoolClient.service.ts index d8ef37ff09b5..0dae5c9c7c03 100644 --- a/libs/clients/secondary-school/src/lib/secondarySchoolClient.service.ts +++ b/libs/clients/secondary-school/src/lib/secondarySchoolClient.service.ts @@ -1,4 +1,5 @@ -import { Auth, AuthMiddleware, User } from '@island.is/auth-nest-tools' +// import { Auth, AuthMiddleware, User } from '@island.is/auth-nest-tools' +import { User } from '@island.is/auth-nest-tools' import { Injectable } from '@nestjs/common' import { SchoolsApi, @@ -10,6 +11,7 @@ import { Program, SecondarySchool, } from './secondarySchoolClient.types' +import { getAllLanguageCodes } from '@island.is/shared/utils' @Injectable() export class SecondarySchoolClient { @@ -43,14 +45,19 @@ export class SecondarySchoolClient { thirdLanguages: school.thirdLanguages?.map((language) => ({ code: language.code || '', - nameIs: language.name || '', - nameEn: language.nameEnglish || '', + name: language.name || '', })) || [], + // TODOx vantar í API - nordicLanguages + nordicLanguages: getAllLanguageCodes().filter((x) => + ['sv', 'no', 'fi'].includes(x.code), + ), + // TODOx vantar í API - allowRequestDormitory + allowRequestDormitory: false, })) } async getPrograms(schoolId: string): Promise { - const res = await this.programmesApi.v1ProgrammesGet({ + const res = await this.programmesApi.v1ProgrammesSimpleGet({ schoolId, rowOffset: undefined, fetchSize: undefined, @@ -70,7 +77,7 @@ export class SecondarySchoolClient { // fetchSize: undefined, // }) - //TODOx get information about how we know if application is in progress + //TODOx vantar í API - get information about how we know if application is in progress return true } @@ -83,15 +90,50 @@ export class SecondarySchoolClient { async create(auth: User, application: Application): Promise { const applicationId = await this.applicationsApi.v1ApplicationsPost({ applicationBaseDto: { - //TODOx add fields to create application + applicantNationalId: application.nationalId, + applicantName: application.name, + phoneNumber: application.phone, + email: application.email, + placeOfResidence: application.address, + postCode: application.postalCode, + municipality: application.city, + nextOfKin: application.contacts.map((contact) => ({ + nationalId: contact.nationalId, + phoneNumber: contact.phone, + name: contact.name, + email: contact.email, + address: contact.address, + postCode: contact.postalCode, + })), + speakingLanguage: application.nativeLanguageCode, + otherInformation: application.otherDescription, + applicationChoices: application.schools.map((school) => ({ + priority: school.priority, + schoolId: school.schoolId, + programmeChoice: school.programs.map((program) => ({ + priority: program.priority, + programmeId: program.programId, + })), + thirdLanguages: school.thirdLanguageCode, + northernLanguage: school.nordicLanguageCode, + requestDormitory: school.requestDormitory, + })), + //TODOx vantar í API - missing applicationType }, }) await this.applicationsApi.v1ApplicationsApplicationIdAttachmentsPatch({ applicationId, - files: [], //TODOx add attachments + files: application.attachments + .filter((x) => !!x) + .map((x) => this.base64ToBlob(x.fileContent)), }) return applicationId } + + private base64ToBlob(base64: string): Blob { + const byteCharacters = Buffer.from(base64, 'base64') + return new Blob([byteCharacters]) + } } diff --git a/libs/clients/secondary-school/src/lib/secondarySchoolClient.types.ts b/libs/clients/secondary-school/src/lib/secondarySchoolClient.types.ts index 4ae476382551..549af79f8786 100644 --- a/libs/clients/secondary-school/src/lib/secondarySchoolClient.types.ts +++ b/libs/clients/secondary-school/src/lib/secondarySchoolClient.types.ts @@ -1,13 +1,14 @@ export interface Language { code: string - nameIs: string - nameEn: string + name: string } export interface SecondarySchool { id: string name: string thirdLanguages: Language[] + nordicLanguages: Language[] + allowRequestDormitory: boolean } export interface Program { @@ -17,6 +18,45 @@ export interface Program { registrationEndDate: Date } +export interface ApplicationContact { + nationalId: string + name: string + phone: string + email: string + address?: string + postalCode?: string + city?: string +} + +export interface ApplicationSelectionSchoolProgram { + priority: number + programId: string +} + +export interface ApplicationSelectionSchool { + priority: number + schoolId: string + programs: ApplicationSelectionSchoolProgram[] + thirdLanguageCode?: string + nordicLanguageCode?: string + requestDormitory?: boolean +} + +export interface ApplicationAttachment { + fileContent: string +} + export interface Application { nationalId: string + name: string + phone: string + email: string + address: string + postalCode: string + city: string + contacts: ApplicationContact[] + schools: ApplicationSelectionSchool[] + nativeLanguageCode?: string + otherDescription?: string + attachments: ApplicationAttachment[] }