diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3fb6c5ac063b..0f1602b5aadb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,6 +21,8 @@ /* @island-is/core /libs/api/domains/identity/ @island-is/core +# Edge case escaping the /infra/ and **/infra/ devops patterns below +/libs/infra-nest-server/src/lib/infra/ @island-is/core /libs/testing/ @island-is/core /libs/nest/swagger/ @island-is/core @@ -166,6 +168,7 @@ codemagic.yaml /libs/clients/firearm-license/ @island-is/hugsmidjan /libs/clients/hunting-license/ @island-is/hugsmidjan /libs/clients/smartsolutions/ @island-is/hugsmidjan +/libs/clients/smart-solutions-v2/ @island-is/hugsmidjan /libs/clients/license-client/ @island-is/hugsmidjan @island-is/aranja /libs/clients/intellectual-properties/ @island-is/hugsmidjan /libs/clients/islykill/ @island-is/hugsmidjan @@ -190,6 +193,7 @@ codemagic.yaml /libs/portals/admin/document-provider/ @island-is/hugsmidjan @island-is/core /libs/clients/icelandic-health-insurance/rights-portal/ @island-is/hugsmidjan /libs/clients/health-directorate @island-is/hugsmidjan @island-is/origo +/libs/clients/mms/grade @island-is/hugsmidjan /libs/portals/admin/air-discount-scheme @island-is/hugsmidjan /libs/application/templates/official-journal-of-iceland/ @island-is/hugsmidjan /libs/application/template-api-modules/src/lib/modules/templates/official-journal-of-iceland/ @island-is/hugsmidjan diff --git a/apps/air-discount-scheme/backend/docker-compose.yml b/apps/air-discount-scheme/backend/docker-compose.yml index b936e4336e86..f661e2b0b8f8 100644 --- a/apps/air-discount-scheme/backend/docker-compose.yml +++ b/apps/air-discount-scheme/backend/docker-compose.yml @@ -15,7 +15,7 @@ services: redis-cluster: container_name: ads_redis_cluster - image: docker.io/grokzen/redis-cluster:7.0.0 + image: docker.io/grokzen/redis-cluster:6.0.16 privileged: true environment: - IP=0.0.0.0 diff --git a/apps/api/docker-compose.yml b/apps/api/docker-compose.yml index 4e7a43ad9202..be437e3c31a1 100644 --- a/apps/api/docker-compose.yml +++ b/apps/api/docker-compose.yml @@ -3,7 +3,7 @@ version: '3.3' services: redis_cluster: container_name: api_redis_cluster - image: docker.io/grokzen/redis-cluster:7.0.0 + image: docker.io/grokzen/redis-cluster:6.0.16 networks: - local privileged: true diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index f538ecfaee35..885e71d4dc1f 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -23,6 +23,7 @@ import { DocumentModule } from '@island.is/api/domains/documents' import { DrivingLicenseModule } from '@island.is/api/domains/driving-license' import { DrivingLicenseBookModule } from '@island.is/api/domains/driving-license-book' import { EducationModule } from '@island.is/api/domains/education' +import { EducationV2Module } from '@island.is/api/domains/education' import { SocialInsuranceModule } from '@island.is/api/domains/social-insurance' import { ElectronicRegistrationsModule } from '@island.is/api/domains/electronic-registration-statistics' import { @@ -192,6 +193,7 @@ import { CriminalRecordClientConfig } from '@island.is/clients/criminal-record' import { HealthInsuranceV2ClientConfig } from '@island.is/clients/icelandic-health-insurance/health-insurance' import { VmstClientConfig } from '@island.is/clients/vmst' import { FriggClientConfig } from '@island.is/clients/mms/frigg' +import { GradeClientConfig } from '@island.is/clients/mms/grade' import { UmbodsmadurSkuldaraModule } from '@island.is/api/domains/umbodsmadur-skuldara' import { UmbodsmadurSkuldaraClientConfig } from '@island.is/clients/ums-cost-of-living-calculator' import { emailModuleConfig } from '@island.is/email-service' @@ -230,6 +232,7 @@ const environment = getConfig }, fileDownloadBucket: environment.education.fileDownloadBucket!, }), + EducationV2Module, ApplicationModule.register({ baseApiUrl: environment.applicationSystem.baseApiUrl!, }), @@ -417,6 +420,7 @@ const environment = getConfig UserProfileClientConfig, UltravioletRadiationClientConfig, FriggClientConfig, + GradeClientConfig, VmstClientConfig, HealthInsuranceV2ClientConfig, CriminalRecordClientConfig, diff --git a/apps/application-system/api/docker-compose.yml b/apps/application-system/api/docker-compose.yml index 3520303edf14..2b754bf67fe7 100644 --- a/apps/application-system/api/docker-compose.yml +++ b/apps/application-system/api/docker-compose.yml @@ -15,7 +15,7 @@ services: redis_cluster: container_name: redis_cluster - image: docker.io/grokzen/redis-cluster:7.0.0 + image: docker.io/grokzen/redis-cluster:6.0.16 networks: - local privileged: true diff --git a/apps/application-system/api/infra/application-system-api.ts b/apps/application-system/api/infra/application-system-api.ts index c138e02c7c39..799dab8fee5b 100644 --- a/apps/application-system/api/infra/application-system-api.ts +++ b/apps/application-system/api/infra/application-system-api.ts @@ -57,7 +57,7 @@ export const workerSetup = }, XROAD_CHARGE_FJS_V2_PATH: { dev: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2', - staging: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2', + staging: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2', prod: 'IS/GOV/5402697509/FJS-Public/chargeFJS_v2', }, APPLICATION_ATTACHMENT_BUCKET: { @@ -221,12 +221,12 @@ export const serviceSetup = (services: { ), XROAD_COURT_BANKRUPTCY_CERT_PATH: { dev: 'IS-DEV/GOV/10019/Domstolasyslan/JusticePortal-v1', - staging: 'IS-DEV/GOV/10019/Domstolasyslan/JusticePortal-v1', + staging: 'IS-TEST/GOV/10019/Domstolasyslan/JusticePortal-v1', prod: 'IS/GOV/4707171140/Domstolasyslan/JusticePortal-v1', }, XROAD_ALTHINGI_OMBUDSMAN_SERVICE_PATH: { dev: 'IS-DEV/GOV/10047/UA-Protected/kvortun-v1/', - staging: 'IS-DEV/GOV/10047/UA-Protected/kvortun-v1/', + staging: 'IS-TEST/GOV/10047/UA-Protected/kvortun-v1/', prod: 'IS/GOV/5605882089/UA-Protected/kvortun-v1', }, NOVA_ACCEPT_UNAUTHORIZED: { diff --git a/apps/application-system/api/src/app/modules/application/admin.controller.ts b/apps/application-system/api/src/app/modules/application/admin.controller.ts index c2270e947d46..4f96f961053f 100644 --- a/apps/application-system/api/src/app/modules/application/admin.controller.ts +++ b/apps/application-system/api/src/app/modules/application/admin.controller.ts @@ -7,7 +7,7 @@ import { UseGuards, Inject, } from '@nestjs/common' -import { ApiTags, ApiHeader } from '@nestjs/swagger' +import { ApiTags, ApiHeader, ApiBearerAuth } from '@nestjs/swagger' import { IdsUserGuard, ScopesGuard, Scopes } from '@island.is/auth-nest-tools' import { AdminPortalScope } from '@island.is/auth/scopes' @@ -31,10 +31,7 @@ import { @UseGuards(IdsUserGuard, ScopesGuard, DelegationGuard) @ApiTags('applications') -@ApiHeader({ - name: 'authorization', - description: 'Bearer token authorization', -}) +@ApiBearerAuth() @ApiHeader({ name: 'locale', description: 'Front-end language selected', diff --git a/apps/application-system/api/src/app/modules/application/application.controller.ts b/apps/application-system/api/src/app/modules/application/application.controller.ts index ad35f8690a55..d8d3180484aa 100644 --- a/apps/application-system/api/src/app/modules/application/application.controller.ts +++ b/apps/application-system/api/src/app/modules/application/application.controller.ts @@ -27,6 +27,7 @@ import { ApiTags, ApiHeader, ApiQuery, + ApiBearerAuth, } from '@nestjs/swagger' import { ApplicationTemplateHelper, @@ -102,14 +103,11 @@ import { ApplicationActionService } from './application-action.service' @UseGuards(IdsUserGuard, ScopesGuard, DelegationGuard) @ApiTags('applications') -@ApiHeader({ - name: 'authorization', - description: 'Bearer token authorization', -}) @ApiHeader({ name: 'locale', description: 'Front-end language selected', }) +@ApiBearerAuth() @Controller() export class ApplicationController { constructor( diff --git a/apps/application-system/api/src/app/modules/application/application.module.ts b/apps/application-system/api/src/app/modules/application/application.module.ts index 7609f18cec88..2fffc93fefc4 100644 --- a/apps/application-system/api/src/app/modules/application/application.module.ts +++ b/apps/application-system/api/src/app/modules/application/application.module.ts @@ -29,7 +29,6 @@ import { PaymentModule } from '@island.is/application/api/payment' import { HistoryModule } from '@island.is/application/api/history' import { AuthPublicApiClientModule } from '@island.is/clients/auth/public-api' import { ApplicationActionService } from './application-action.service' -import { TemplateAPIConfig } from '@island.is/application/template-api-modules' @Module({ imports: [ @@ -37,9 +36,7 @@ import { TemplateAPIConfig } from '@island.is/application/template-api-modules' AuditModule.forRoot(environment.audit), AuthModule.register(environment.auth), TemplateAPIModule.register({ - // TODO: Update typing to accomodate incomplete module setup - // Some modules don't need to configure everything, yet the type requires it. - ...(environment.templateApi as TemplateAPIConfig), + ...environment.templateApi, applicationService: TemplateApiApplicationService, }), ApplicationApiCoreModule, diff --git a/apps/application-system/api/src/app/modules/application/dto/application.response.dto.ts b/apps/application-system/api/src/app/modules/application/dto/application.response.dto.ts index 310339292962..b776368ece8c 100644 --- a/apps/application-system/api/src/app/modules/application/dto/application.response.dto.ts +++ b/apps/application-system/api/src/app/modules/application/dto/application.response.dto.ts @@ -41,6 +41,11 @@ class PendingAction { @Expose() @IsString() content?: string + + @ApiPropertyOptional() + @Expose() + @IsString() + button?: string } class History { @@ -96,6 +101,11 @@ class ActionCardMetaData { @Expose() @IsNumber() draftTotalSteps?: number + + @ApiPropertyOptional() + @Expose() + @IsString() + historyButton?: string } export class BaseApplicationResponseDto { diff --git a/apps/application-system/api/src/app/modules/application/tools/application.serializer.ts b/apps/application-system/api/src/app/modules/application/tools/application.serializer.ts index a26f6cf08379..160a2f92d833 100644 --- a/apps/application-system/api/src/app/modules/application/tools/application.serializer.ts +++ b/apps/application-system/api/src/app/modules/application/tools/application.serializer.ts @@ -147,6 +147,9 @@ export class ApplicationSerializer history, draftFinishedSteps: application.draftFinishedSteps, draftTotalSteps: application.draftTotalSteps, + historyButton: actionCardMeta.historyButton + ? intl.formatMessage(actionCardMeta.historyButton) + : null, }, name: getApplicationNameTranslationString( template, diff --git a/apps/application-system/api/src/environments/environment.interface.ts b/apps/application-system/api/src/environments/environment.interface.ts index 9d1dad05eb64..0136d18bd16b 100644 --- a/apps/application-system/api/src/environments/environment.interface.ts +++ b/apps/application-system/api/src/environments/environment.interface.ts @@ -2,16 +2,14 @@ import type { AuthConfig } from '@island.is/auth-nest-tools' import { TemplateAPIConfig } from '@island.is/application/template-api-modules' import { AuditOptions } from '@island.is/nest/audit' -export interface Environment< - TemplateAPISubset extends keyof TemplateAPIConfig = keyof TemplateAPIConfig, -> { +export interface Environment { production: boolean environment: string name: string baseApiUrl: string audit: AuditOptions auth: AuthConfig - templateApi: Pick + templateApi: TemplateAPIConfig contentful: { accessToken: string } diff --git a/apps/application-system/api/src/environments/environment.ts b/apps/application-system/api/src/environments/environment.ts index 178189e4413c..7294e37f0878 100644 --- a/apps/application-system/api/src/environments/environment.ts +++ b/apps/application-system/api/src/environments/environment.ts @@ -1,18 +1,6 @@ import { Environment } from './environment.interface' -type EnvironmentKeys = - | 'attachmentBucket' - | 'baseApiUrl' - | 'clientLocationOrigin' - | 'email' - | 'generalPetition' - | 'islykill' - | 'jwtSecret' - | 'presignBucket' - | 'userProfile' - | 'xRoadBasePathWithEnv' - -const devConfig: Environment = { +const devConfig = { production: false, environment: 'local', name: 'local', @@ -36,8 +24,8 @@ const devConfig: Environment = { jwtSecret: 'supersecret', xRoadBasePathWithEnv: process.env.XROAD_BASE_PATH_WITH_ENV ?? '', baseApiUrl: 'http://localhost:4444', - presignBucket: process.env.FILE_SERVICE_PRESIGN_BUCKET ?? '', - attachmentBucket: process.env.APPLICATION_ATTACHMENT_BUCKET ?? '', + presignBucket: process.env.FILE_SERVICE_PRESIGN_BUCKET, + attachmentBucket: process.env.APPLICATION_ATTACHMENT_BUCKET, generalPetition: { endorsementsApiBasePath: 'http://localhost:4246', }, @@ -45,60 +33,59 @@ const devConfig: Environment = { serviceBasePath: 'http://localhost:3366', }, islykill: { - cert: process.env.ISLYKILL_CERT ?? '', - passphrase: process.env.ISLYKILL_SERVICE_PASSPHRASE ?? '', - basePath: process.env.ISLYKILL_SERVICE_BASEPATH ?? '', + cert: process.env.ISLYKILL_CERT, + passphrase: process.env.ISLYKILL_SERVICE_PASSPHRASE, + basePath: process.env.ISLYKILL_SERVICE_BASEPATH, }, }, contentful: { - accessToken: process.env.CONTENTFUL_ACCESS_TOKEN ?? '', + accessToken: process.env.CONTENTFUL_ACCESS_TOKEN, }, -} +} as Environment -// TODO: Remove empty-string defaulting -const prodConfig: Environment = { +const prodConfig = { production: true, - environment: process.env.ENVIRONMENT ?? '', - name: process.env.name ?? '', - baseApiUrl: process.env.GRAPHQL_API_URL ?? '', + environment: process.env.ENVIRONMENT, + name: process.env.name, + baseApiUrl: process.env.GRAPHQL_API_URL, audit: { defaultNamespace: '@island.is/applications', - groupName: process.env.AUDIT_GROUP_NAME ?? '', + groupName: process.env.AUDIT_GROUP_NAME, serviceName: 'application-system-api', }, auth: { - issuer: process.env.IDENTITY_SERVER_ISSUER_URL ?? '', + issuer: process.env.IDENTITY_SERVER_ISSUER_URL, audience: ['@island.is', '@admin.island.is'], allowClientNationalId: true, }, templateApi: { - clientLocationOrigin: process.env.CLIENT_LOCATION_ORIGIN ?? '', + clientLocationOrigin: process.env.CLIENT_LOCATION_ORIGIN, email: { - sender: process.env.EMAIL_FROM_NAME ?? '', - address: process.env.EMAIL_FROM ?? '', + sender: process.env.EMAIL_FROM_NAME, + address: process.env.EMAIL_FROM, }, - jwtSecret: process.env.AUTH_JWT_SECRET ?? '', + jwtSecret: process.env.AUTH_JWT_SECRET, xRoadBasePathWithEnv: process.env.XROAD_BASE_PATH_WITH_ENV ?? '', - baseApiUrl: process.env.GRAPHQL_API_URL ?? '', - presignBucket: process.env.FILE_SERVICE_PRESIGN_BUCKET ?? '', - attachmentBucket: process.env.APPLICATION_ATTACHMENT_BUCKET ?? '', + baseApiUrl: process.env.GRAPHQL_API_URL, + presignBucket: process.env.FILE_SERVICE_PRESIGN_BUCKET, + attachmentBucket: process.env.APPLICATION_ATTACHMENT_BUCKET, generalPetition: { - endorsementsApiBasePath: process.env.ENDORSEMENTS_API_BASE_PATH ?? '', + endorsementsApiBasePath: process.env.ENDORSEMENTS_API_BASE_PATH, }, userProfile: { - serviceBasePath: process.env.SERVICE_USER_PROFILE_URL ?? '', + serviceBasePath: process.env.SERVICE_USER_PROFILE_URL, }, islykill: { - cert: process.env.ISLYKILL_CERT ?? '', - passphrase: process.env.ISLYKILL_SERVICE_PASSPHRASE ?? '', - basePath: process.env.ISLYKILL_SERVICE_BASEPATH ?? '', + cert: process.env.ISLYKILL_CERT, + passphrase: process.env.ISLYKILL_SERVICE_PASSPHRASE, + basePath: process.env.ISLYKILL_SERVICE_BASEPATH, }, }, contentful: { - accessToken: process.env.CONTENTFUL_ACCESS_TOKEN ?? '', + accessToken: process.env.CONTENTFUL_ACCESS_TOKEN, }, -} +} as Environment export default process.env.PROD_MODE === 'true' || process.env.NODE_ENV === 'production' diff --git a/apps/application-system/api/src/openApi.ts b/apps/application-system/api/src/openApi.ts index 9e2a5235d78a..1c22ee62d9ab 100644 --- a/apps/application-system/api/src/openApi.ts +++ b/apps/application-system/api/src/openApi.ts @@ -8,4 +8,5 @@ export const openApi = new DocumentBuilder() .setVersion('1.0') .addTag('application') .addTag('payment') + .addBearerAuth() .build() diff --git a/apps/application-system/form/src/routes/Applications.tsx b/apps/application-system/form/src/routes/Applications.tsx index 476497479022..f89bb22513f3 100644 --- a/apps/application-system/form/src/routes/Applications.tsx +++ b/apps/application-system/form/src/routes/Applications.tsx @@ -197,7 +197,9 @@ export const Applications: FC> = () => { flexDirection={['column', 'row']} > - {formatMessage(coreMessages.applications)} + {template.applicationText + ? formatMessage(template.applicationText) + : formatMessage(coreMessages.applications)} {shouldRenderNewApplicationButton ? ( @@ -205,7 +207,9 @@ export const Applications: FC> = () => { onClick={createApplication} data-testid="create-new-application" > - {formatMessage(coreMessages.newApplication)} + {template.newApplicationButtonLabel + ? formatMessage(template.newApplicationButtonLabel) + : formatMessage(coreMessages.newApplication)} ) : null} diff --git a/apps/financial-aid/backend/src/app/modules/file/cloudFront.service.ts b/apps/financial-aid/backend/src/app/modules/file/cloudFront.service.ts index cdbd152549ff..b059bd58e971 100644 --- a/apps/financial-aid/backend/src/app/modules/file/cloudFront.service.ts +++ b/apps/financial-aid/backend/src/app/modules/file/cloudFront.service.ts @@ -1,19 +1,28 @@ -import { getSignedUrl } from '@aws-sdk/cloudfront-signer' +import { CloudFront } from 'aws-sdk' + import { Injectable } from '@nestjs/common' + import { environment } from '../../../environments' @Injectable() export class CloudFrontService { + private readonly signer: CloudFront.Signer + + constructor() { + this.signer = new CloudFront.Signer( + environment.files.cloudFrontPublicKeyId, + environment.files.cloudFrontPrivateKey, + ) + } + createPresignedPost(url: string): string { const expiresInSeconds = environment.files.postTimeToLiveMinutes * 60 - const expiresAt = new Date(Date.now() + expiresInSeconds * 1000) + const expires = Math.floor((Date.now() + expiresInSeconds * 1000) / 1000) - return getSignedUrl({ - keyPairId: environment.files.cloudFrontPublicKeyId, - privateKey: environment.files.cloudFrontPrivateKey, - dateLessThan: expiresAt.toISOString(), + return this.signer.getSignedUrl({ url, + expires, }) } } diff --git a/apps/github-actions-cache/docker-compose.yml b/apps/github-actions-cache/docker-compose.yml index 3b2fefe9d393..effe31dfc563 100644 --- a/apps/github-actions-cache/docker-compose.yml +++ b/apps/github-actions-cache/docker-compose.yml @@ -3,7 +3,7 @@ version: '3.3' services: redis-cluster: container_name: gha_cache_redis_cluster - image: docker.io/grokzen/redis-cluster:7.0.0 + image: docker.io/grokzen/redis-cluster:6.0.16 privileged: true environment: - IP=0.0.0.0 diff --git a/apps/github-actions-cache/src/app/external.ts b/apps/github-actions-cache/src/app/external.ts index 49fdd2cc927e..24a2ce551f0d 100644 --- a/apps/github-actions-cache/src/app/external.ts +++ b/apps/github-actions-cache/src/app/external.ts @@ -1,9 +1,9 @@ -import { S3Client } from '@aws-sdk/client-s3' +import { S3 } from 'aws-sdk' import environment from '../environments/environment' import { createCache } from '@island.is/cache' const { redis, production } = environment -export const s3 = new S3Client() +export const s3 = new S3({ region: 'eu-west-1' }) export const cache = createCache({ name: 'github-actions-cache', nodes: redis.urls, diff --git a/apps/github-actions-cache/src/app/handlers.ts b/apps/github-actions-cache/src/app/handlers.ts index fa500023eade..0c7f0b5732ca 100644 --- a/apps/github-actions-cache/src/app/handlers.ts +++ b/apps/github-actions-cache/src/app/handlers.ts @@ -9,16 +9,8 @@ import { GetContentInfoError, } from './utils' import { cache, s3 } from './external' -import { - CompleteMultipartUploadCommand, - CreateMultipartUploadCommand, - GetObjectCommand, - HeadObjectCommand, -} from '@aws-sdk/client-s3' -import { getSignedUrl } from '@aws-sdk/s3-request-presigner' const { bucket } = environment -const SIGNED_URL_TTL = 60 export const reserveCache = async ( req: express.Request, @@ -45,9 +37,7 @@ export const reserveCache = async ( // Invalidate reservation after 300 seconds await cache.expire(cacheId, 300) logger.debug(`Creating multipartupload for ${cacheId}`) - const { UploadId } = await s3.send( - new CreateMultipartUploadCommand(objectParams), - ) + const { UploadId } = await s3.createMultipartUpload(objectParams).promise() if (!UploadId) { throw new Error('No upload id received from createMultipartUpload') } @@ -108,7 +98,7 @@ export const uploadChunk = async ( export const commitCache = async ( req: express.Request, res: express.Response, - _next: express.NextFunction, + next: express.NextFunction, ) => { const { cacheId } = req.params logger.debug(`Finalizing cache upload for ${cacheId}`) @@ -131,14 +121,14 @@ export const commitCache = async ( Parts: uploadMap.map((item) => JSON.parse(item)), } logger.debug(`Completing multipartupload with map`, MultipartUpload) - await s3.send( - new CompleteMultipartUploadCommand({ + await s3 + .completeMultipartUpload({ Bucket: bucket, Key: cacheId, MultipartUpload, UploadId: uploadId, - }), - ) + }) + .promise() cache.expire(cacheId, 1) res.sendStatus(200) } @@ -160,20 +150,15 @@ export const retrieveCache = async ( Key: cacheId, } try { - await s3.send(new HeadObjectCommand(objectParams)) + const h = await s3.headObject(objectParams).promise() } catch (e) { return error(res, `Cache entry ${cacheId} not found!`, 404) } try { - // See migration docs - // https://github.com/aws/aws-sdk-js-v3/blob/794a37e9795f390d15c9530209e465d099716c86/UPGRADING.md#s3-presigned-url - const signedUrl = await getSignedUrl( - s3, - new GetObjectCommand(objectParams), - { - expiresIn: SIGNED_URL_TTL, - }, - ) + const signedUrl = await s3.getSignedUrlPromise('getObject', { + ...objectParams, + Expires: 60, + }) logger.info(`Signed url: ${signedUrl}`) res.send({ archiveLocation: signedUrl, cacheKey: keys as string }) } catch (e) { diff --git a/apps/github-actions-cache/src/app/utils.ts b/apps/github-actions-cache/src/app/utils.ts index 2a00171dc591..56b9894ba786 100644 --- a/apps/github-actions-cache/src/app/utils.ts +++ b/apps/github-actions-cache/src/app/utils.ts @@ -1,5 +1,5 @@ import * as express from 'express' -import { S3Client, UploadPartCommand } from '@aws-sdk/client-s3' +import { S3 } from 'aws-sdk' import { logger } from '@island.is/logging' import stream from 'stream' @@ -17,7 +17,7 @@ export const getCacheId = (key: string, version: string) => const uploadChunkSize = 32 * 1024 * 1024 export const uploadChunkStream = ( - s3: S3Client, + s3: S3, Bucket: string, UploadId: string, key: string, @@ -27,16 +27,16 @@ export const uploadChunkStream = ( const pass = new stream.PassThrough() return { writeStream: pass, - promise: s3.send( - new UploadPartCommand({ + promise: s3 + .uploadPart({ Body: pass, Bucket, Key: key, PartNumber, ContentLength, UploadId, - }), - ), + }) + .promise(), } } diff --git a/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.ts b/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.ts index e345c8eb040b..40e3ff319e56 100644 --- a/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.ts +++ b/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.ts @@ -118,8 +118,10 @@ export const getIndictmentInfo = ( ).toISOString() if (defendants) { - const verdictViewDates = defendants?.map( - (defendant) => defendant.verdictViewDate, + const verdictViewDates = defendants?.map((defendant) => + defendant.verdictViewDate + ? new Date(defendant.verdictViewDate) + : undefined, ) const verdictAppealDeadline = diff --git a/apps/judicial-system/api/src/app/modules/file/dto/createFile.input.ts b/apps/judicial-system/api/src/app/modules/file/dto/createFile.input.ts index ec1b593eb949..51137a3310e5 100644 --- a/apps/judicial-system/api/src/app/modules/file/dto/createFile.input.ts +++ b/apps/judicial-system/api/src/app/modules/file/dto/createFile.input.ts @@ -65,4 +65,9 @@ export class CreateFileInput { @IsOptional() @Field(() => String, { nullable: true }) readonly policeFileId?: string + + @Allow() + @IsOptional() + @Field(() => String, { nullable: true }) + readonly userGeneratedFilename?: string } diff --git a/apps/judicial-system/api/src/app/modules/file/models/file.model.ts b/apps/judicial-system/api/src/app/modules/file/models/file.model.ts index cb7c9c13c2b6..f7d619e9cc34 100644 --- a/apps/judicial-system/api/src/app/modules/file/models/file.model.ts +++ b/apps/judicial-system/api/src/app/modules/file/models/file.model.ts @@ -57,4 +57,7 @@ export class CaseFile { @Field(() => String, { nullable: true }) readonly policeFileId?: string + + @Field(() => String, { nullable: true }) + readonly submittedBy?: string } diff --git a/apps/judicial-system/backend/src/app/formatters/formatters.ts b/apps/judicial-system/backend/src/app/formatters/formatters.ts index 7133cdac313f..babb0c0da5e6 100644 --- a/apps/judicial-system/backend/src/app/formatters/formatters.ts +++ b/apps/judicial-system/backend/src/app/formatters/formatters.ts @@ -19,6 +19,7 @@ import { CaseCustodyRestrictions, CaseLegalProvisions, CaseType, + courtSessionTypeNames, isIndictmentCase, isInvestigationCase, isRestrictionCase, @@ -249,6 +250,9 @@ export const formatPostponedCourtDateEmailNotification = ( courtName: theCase.court?.name ?? '', courtCaseNumber: theCase.courtCaseNumber, courtDate: formatDate(courtDate.date, 'PPPp')?.replace(' kl.', ', kl.'), + courtSessionTypeName: theCase.courtSessionType + ? courtSessionTypeNames[theCase.courtSessionType] + : 'Óþekkt', courtRoomText, judgeText, hasAccessToRvg: Boolean(overviewUrl), diff --git a/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts index 0229097bc1c0..40af05fe4ba9 100644 --- a/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts +++ b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts @@ -7,12 +7,7 @@ import { formatDOB, lowercase, } from '@island.is/judicial-system/formatters' -import { - DateType, - DistrictCourtLocation, - DistrictCourts, - SubpoenaType, -} from '@island.is/judicial-system/types' +import { DateType, SubpoenaType } from '@island.is/judicial-system/types' import { subpoena as strings } from '../messages' import { Case } from '../modules/case' @@ -27,6 +22,28 @@ import { setTitle, } from './pdfHelpers' +type DistrictCourts = + | 'Héraðsdómur Reykjavíkur' + | 'Héraðsdómur Reykjaness' + | 'Héraðsdómur Vesturlands' + | 'Héraðsdómur Vestfjarða' + | 'Héraðsdómur Norðurlands vestra' + | 'Héraðsdómur Norðurlands eystra' + | 'Héraðsdómur Austurlands' + | 'Héraðsdómur Suðurlands' + +// TODO: Move to databas +const DistrictCourtLocation: Record = { + 'Héraðsdómur Reykjavíkur': 'Dómhúsið við Lækjartorg, Reykjavík', + 'Héraðsdómur Reykjaness': 'Fjarðargata 9, Hafnarfirði', + 'Héraðsdómur Vesturlands': 'Bjarnarbraut 8, Borgarnesi', + 'Héraðsdómur Vestfjarða': 'Hafnarstræti 9, Ísafirði', + 'Héraðsdómur Norðurlands vestra': 'Skagfirðingabraut 21, Sauðárkróki', + 'Héraðsdómur Norðurlands eystra': 'Hafnarstræti 107, 4. hæð, Akureyri', + 'Héraðsdómur Austurlands': 'Lyngás 15, Egilsstöðum', + 'Héraðsdómur Suðurlands': 'Austurvegur 4, Selfossi', +} + export const createSubpoena = ( theCase: Case, defendant: Defendant, diff --git a/apps/judicial-system/backend/src/app/messages/notifications.ts b/apps/judicial-system/backend/src/app/messages/notifications.ts index c64652bb07d0..a8868a3b3595 100644 --- a/apps/judicial-system/backend/src/app/messages/notifications.ts +++ b/apps/judicial-system/backend/src/app/messages/notifications.ts @@ -195,15 +195,15 @@ export const notifications = { }), postponedCourtDateEmail: defineMessages({ subject: { - id: 'judicial.system.backend:notifications.postponed_court_date_email.subject', - defaultMessage: 'Frestun - nýtt þinghald í máli {courtCaseNumber}', + id: 'judicial.system.backend:notifications.postponed_court_date_email.subject_v1', + defaultMessage: 'Nýtt þinghald í máli {courtCaseNumber}', description: 'Notaður sem titill á pósti til sækjanda og verjenda þegar þinghaldi er frestað', }, body: { - id: 'judicial.system.backend:notifications.postponed_court_date_email.body_v3', + id: 'judicial.system.backend:notifications.postponed_court_date_email.body_v4', defaultMessage: - '{courtName} boðar til þinghalds í máli {courtCaseNumber}.
Fyrirtaka mun fara fram {courtDate}.

{courtRoomText}

{judgeText}

{hasAccessToRvg, select, false {Hægt er að nálgast gögn málsins hjá {courtName}} other {Hægt er að nálgast gögn málsins á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}}}.', + '{courtName} boðar til þinghalds í máli {courtCaseNumber}.
Fyrirtaka mun fara fram {courtDate}.

Tegund þinghalds: {courtSessionTypeName}.

{courtRoomText}

{judgeText}

{hasAccessToRvg, select, false {Hægt er að nálgast gögn málsins hjá {courtName}} other {Hægt er að nálgast gögn málsins á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}}}.', description: 'Notaður sem texti á pósti til sækjanda og verjenda þegar þinghaldi er frestað', }, @@ -221,7 +221,7 @@ export const notifications = { defaultMessage: '{court} boðar til þingfestingar í máli {courtCaseNumber}.', description: - 'Notaður sem texti í pósti sem tilgreinir að dómstól boði til þingfestingar', + 'Notaður sem texti í pósti sem tilgreinir að dómstóll boði til þingfestingar', }, courtDate: { id: 'judicial.system.backend:notifications.prosecutor_court_date_email.court_date', diff --git a/apps/judicial-system/backend/src/app/modules/aws-s3/awsS3.service.ts b/apps/judicial-system/backend/src/app/modules/aws-s3/awsS3.service.ts index 472635873cfb..04c57cf5f55e 100644 --- a/apps/judicial-system/backend/src/app/modules/aws-s3/awsS3.service.ts +++ b/apps/judicial-system/backend/src/app/modules/aws-s3/awsS3.service.ts @@ -1,12 +1,4 @@ -import { - DeleteObjectCommand, - GetObjectCommand, - HeadObjectCommand, - PutObjectCommand, - S3Client, -} from '@aws-sdk/client-s3' -import { createPresignedPost } from '@aws-sdk/s3-presigned-post' -import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import { S3 } from 'aws-sdk' import { Inject, Injectable } from '@nestjs/common' @@ -45,66 +37,72 @@ const formatS3Key = (caseType: CaseType, key: string) => { @Injectable() export class AwsS3Service { - private readonly s3Client: S3Client + private readonly s3: S3 constructor( @Inject(awsS3ModuleConfig.KEY) private readonly config: ConfigType, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) { - this.s3Client = new S3Client({ region: this.config.region }) + this.s3 = new S3({ region: this.config.region }) } - async createPresignedPost( + createPresignedPost( caseType: CaseType, key: string, type: string, - ): Promise<{ url: string; fields: Record }> { - const { url, fields } = await createPresignedPost(this.s3Client, { - Bucket: this.config.bucket, - Key: formatS3Key(caseType, key), - Conditions: [ - ['content-length-range', 0, 104857600], // 100 MB - ['starts-with', '$Content-Type', type], - ], - Fields: { - 'content-type': type, - 'Content-Disposition': 'inline', - }, - Expires: this.config.timeToLivePost, + ): Promise { + return new Promise((resolve, reject) => { + this.s3.createPresignedPost( + { + Bucket: this.config.bucket, + Expires: this.config.timeToLivePost, + Fields: { + key: formatS3Key(caseType, key), + 'content-type': type, + 'Content-Disposition': 'inline', + }, + }, + (err, data) => { + if (err) { + reject(err) + } else { + resolve(data) + } + }, + ) }) - - return { url, fields } } - async objectExists(caseType: CaseType, key: string): Promise { - try { - await this.s3Client.send( - new HeadObjectCommand({ - Bucket: this.config.bucket, - Key: formatS3Key(caseType, key), - }), + objectExists(caseType: CaseType, key: string): Promise { + return this.s3 + .headObject({ + Bucket: this.config.bucket, + Key: formatS3Key(caseType, key), + }) + .promise() + .then( + () => true, + () => { + // The error is either 404 Not Found or 403 Forbidden. + // Normally, we would check if the error is 404 Not Found. + // However, to avoid granting the service ListBucket permissions, + // we also allow 403 Forbidden. + return false + }, ) - return true - } catch (error) { - // The error is either 404 Not Found or 403 Forbidden. - // Normally, we would check if the error is 404 Not Found. - // However, to avoid granting the service ListBucket permissions, - // we also allow 403 Forbidden. - return false - } } private async putObjectToS3(key: string, content: string): Promise { - await this.s3Client.send( - new PutObjectCommand({ + return this.s3 + .putObject({ Bucket: this.config.bucket, Key: key, Body: Buffer.from(content, 'binary'), ContentType: 'application/pdf', - }), - ) - return key + }) + .promise() + .then(() => key) } putObject(caseType: CaseType, key: string, content: string): Promise { @@ -123,7 +121,7 @@ export class AwsS3Service { return this.putObjectToS3(formatGeneratedRequestCaseKey(key), content) } - async getSignedUrl( + getSignedUrl( caseType: CaseType, key?: string, timeToLive?: number, @@ -132,13 +130,22 @@ export class AwsS3Service { throw new Error('Key is required') } - const command = new GetObjectCommand({ - Bucket: this.config.bucket, - Key: formatS3Key(caseType, key), - }) - - return getSignedUrl(this.s3Client, command, { - expiresIn: timeToLive ?? this.config.timeToLiveGet, + return new Promise((resolve, reject) => { + this.s3.getSignedUrl( + 'getObject', + { + Bucket: this.config.bucket, + Key: formatS3Key(caseType, key), + Expires: timeToLive ?? this.config.timeToLiveGet, + }, + (err, url) => { + if (err) { + reject(err) + } else { + resolve(url) + } + }, + ) }) } @@ -177,17 +184,13 @@ export class AwsS3Service { } private async getObjectFromS3(key: string): Promise { - const { Body } = await this.s3Client.send( - new GetObjectCommand({ + return this.s3 + .getObject({ Bucket: this.config.bucket, Key: key, - }), - ) - if (!Body) { - this.logger.error(`Failure when getting object from S3`, { key }) - throw new Error('Failure when getting object from S3') - } - return Buffer.from(await Body.transformToByteArray()) + }) + .promise() + .then((data) => data.Body as Buffer) } async getObject(caseType: CaseType, key?: string): Promise { @@ -245,21 +248,21 @@ export class AwsS3Service { async deleteObject(caseType: CaseType, key: string): Promise { const s3Key = formatS3Key(caseType, key) - try { - await this.s3Client.send( - new DeleteObjectCommand({ - Bucket: this.config.bucket, - Key: s3Key, - }), - ) - return true - } catch (error) { - // Tolerate failure, but log error - this.logger.error(`Failed to delete object ${s3Key} from AWS S3`, { - reason: error, + return this.s3 + .deleteObject({ + Bucket: this.config.bucket, + Key: s3Key, + }) + .promise() + .then(() => true) + .catch((reason) => { + // Tolerate failure, but log error + this.logger.error(`Failed to delete object ${s3Key} from AWS S3`, { + reason, + }) + + return false }) - return false - } } deleteConfirmedIndictmentCaseObject( diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts index 954025e9b815..cca384ac525a 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts @@ -150,7 +150,7 @@ export class CaseController { const createdCase = await this.caseService.create(caseToCreate, user) - this.eventService.postEvent(CaseEvent.CREATE, createdCase as Case) + this.eventService.postEvent('CREATE', createdCase) return createdCase } @@ -419,10 +419,7 @@ export class CaseController { ) // No need to wait - this.eventService.postEvent( - transition.transition as unknown as CaseEvent, - updatedCase ?? theCase, - ) + this.eventService.postEvent(transition.transition, updatedCase ?? theCase) return updatedCase ?? theCase } @@ -891,7 +888,7 @@ export class CaseController { const extendedCase = await this.caseService.extend(theCase, user) - this.eventService.postEvent(CaseEvent.EXTEND, extendedCase as Case) + this.eventService.postEvent('EXTEND', extendedCase as Case) return extendedCase } diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index faaa1375015f..65a7daca3a84 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -1671,7 +1671,7 @@ export class CaseService { await this.addMessagesForUpdatedCaseToQueue(theCase, updatedCase, user) if (receivingCase) { - this.eventService.postEvent(CaseEvent.RECEIVE, updatedCase) + this.eventService.postEvent(CaseTransition.RECEIVE, updatedCase) } if (returnUpdatedCase) { diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts b/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts index 8d6c854a4578..d49a4ea4a964 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts @@ -24,6 +24,7 @@ import { import { nowFactory } from '../../../factories' import { Case } from '../models/case.model' +import { DateLog } from '../models/dateLog.model' const canProsecutionUserAccessCase = ( theCase: Case, @@ -185,8 +186,8 @@ const canPrisonSystemUserAccessCase = ( // Check case type access if (user.institution?.type === InstitutionType.PRISON_ADMIN) { if (isIndictmentCase(theCase.type)) { - const verdictViewDates = theCase.defendants?.map((defendant) => - defendant.verdictViewDate?.toISOString(), + const verdictViewDates = theCase.defendants?.map( + (defendant) => defendant.verdictViewDate, ) const indictmentVerdictAppealDeadline = @@ -269,9 +270,7 @@ const canDefenceUserAccessCase = (theCase: Case, user: User): boolean => { return false } - const arraignmentDate = theCase.dateLogs?.find( - (d) => d.dateType === DateType.ARRAIGNMENT_DATE, - )?.date + const arraignmentDate = DateLog.arraignmentDate(theCase.dateLogs) // Check submitted case access const canDefenderAccessSubmittedCase = diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts b/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts index d3fe3fb0aa59..c4bfe1eb6331 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts @@ -263,9 +263,7 @@ const getDefenceUserCasesQueryFilter = (user: User): WhereOptions => { ], }, { - defender_national_id: { - [Op.or]: [user.nationalId, formattedNationalId], - }, + defender_national_id: [user.nationalId, formattedNationalId], }, ], }, @@ -280,9 +278,10 @@ const getDefenceUserCasesQueryFilter = (user: User): WhereOptions => { ], }, { - '$defendants.defender_national_id$': { - [Op.or]: [user.nationalId, formattedNationalId], - }, + '$defendants.defender_national_id$': [ + user.nationalId, + formattedNationalId, + ], }, ], }, diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts b/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts index a698194f4ce3..8c93ddecc010 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts @@ -446,9 +446,7 @@ describe('getCasesQueryFilter', () => { ], }, { - defender_national_id: { - [Op.or]: [user.nationalId, user.nationalId], - }, + defender_national_id: [user.nationalId, user.nationalId], }, ], }, @@ -463,9 +461,10 @@ describe('getCasesQueryFilter', () => { ], }, { - '$defendants.defender_national_id$': { - [Op.or]: [user.nationalId, user.nationalId], - }, + '$defendants.defender_national_id$': [ + user.nationalId, + user.nationalId, + ], }, ], }, diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts index 0f49489d0179..9149585ce497 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts @@ -54,7 +54,7 @@ export class InternalCaseController { const createdCase = await this.internalCaseService.create(caseToCreate) - this.eventService.postEvent(CaseEvent.CREATE_XRD, createdCase as Case) + this.eventService.postEvent('CREATE_XRD', createdCase as Case) return createdCase } diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts index 1947fce47527..ebee2422a741 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts @@ -511,7 +511,7 @@ export class InternalCaseService { ) }) - this.eventService.postEvent(CaseEvent.ARCHIVE, theCase) + this.eventService.postEvent('ARCHIVE', theCase) return { caseArchived: true } } diff --git a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts index aee8f67f1e40..7e17c55bab85 100644 --- a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts @@ -182,10 +182,7 @@ export class LimitedAccessCaseController { user, ) - this.eventService.postEvent( - transition.transition as unknown as CaseEvent, - updatedCase, - ) + this.eventService.postEvent(transition.transition, updatedCase) return updatedCase } diff --git a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts index 098b175f67ee..691c040563a7 100644 --- a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts @@ -192,6 +192,8 @@ export const include: Includeable[] = [ CaseFileCategory.COST_BREAKDOWN, CaseFileCategory.CASE_FILE, CaseFileCategory.CASE_FILE_RECORD, + CaseFileCategory.PROSECUTOR_CASE_FILE, + CaseFileCategory.DEFENDANT_CASE_FILE, ], }, }, diff --git a/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts b/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts index 3f45b1d50d1b..5905ae989067 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts @@ -27,6 +27,7 @@ import { } from '@island.is/judicial-system/auth' import { indictmentCases, + ServiceRequirement, SubpoenaType, type User, } from '@island.is/judicial-system/types' @@ -108,6 +109,17 @@ export class DefendantController { ): Promise { this.logger.debug(`Updating defendant ${defendantId} of case ${caseId}`) + // If the defendant was present at the court hearing, + // then set the verdict view date to the case ruling date + if ( + defendantToUpdate.serviceRequirement === ServiceRequirement.NOT_APPLICABLE + ) { + defendantToUpdate = { + ...defendantToUpdate, + verdictViewDate: theCase.rulingDate, + } + } + return this.defendantService.update( theCase, defendant, diff --git a/apps/judicial-system/backend/src/app/modules/event/event.service.ts b/apps/judicial-system/backend/src/app/modules/event/event.service.ts index ce212f4a386e..e66825a614af 100644 --- a/apps/judicial-system/backend/src/app/modules/event/event.service.ts +++ b/apps/judicial-system/backend/src/app/modules/event/event.service.ts @@ -12,7 +12,10 @@ import { formatDate, readableIndictmentSubtypes, } from '@island.is/judicial-system/formatters' -import { isIndictmentCase } from '@island.is/judicial-system/types' +import { + CaseTransition, + isIndictmentCase, +} from '@island.is/judicial-system/types' import { type Case } from '../case' import { DateLog } from '../case/models/dateLog.model' @@ -37,50 +40,42 @@ const errorEmojis = [ ':x:', ] -const caseEvent = { +const caseEvent: Record = { + [CaseTransition.ACCEPT]: ':white_check_mark: Samþykkt', + [CaseTransition.APPEAL]: ':judge: Kæra', + ARCHIVE: ':file_cabinet: Sett í geymslu', + [CaseTransition.ASK_FOR_CANCELLATION]: ':interrobang: Beðið um aftuköllun', + [CaseTransition.ASK_FOR_CONFIRMATION]: ':question: Beðið um staðfestingu', + [CaseTransition.COMPLETE]: ':white_check_mark: Lokið', + [CaseTransition.COMPLETE_APPEAL]: ':white_check_mark: Kæru lokið', + [CaseTransition.DELETE]: ':fire: Afturkallað', + [CaseTransition.DENY_INDICTMENT]: ':no_entry_sign: Ákæru hafnað', + [CaseTransition.DISMISS]: ':woman-shrugging: Vísað frá', CREATE: ':new: Mál stofnað', CREATE_XRD: ':new: Mál stofnað í gegnum Strauminn', EXTEND: ':recycle: Mál framlengt', - OPEN: ':unlock: Opnað fyrir dómstól', - ASK_FOR_CONFIRMATION: ':question: Beðið um staðfestingu', - SUBMIT: ':mailbox_with_mail: Sent', + [CaseTransition.OPEN]: ':unlock: Opnað fyrir dómstól', + [CaseTransition.RECEIVE]: ':eyes: Móttekið', + [CaseTransition.RECEIVE_APPEAL]: ':eyes: Kæra móttekin', + [CaseTransition.REJECT]: ':negative_squared_cross_mark: Hafnað', + [CaseTransition.REOPEN]: ':construction: Opnað aftur', + [CaseTransition.REOPEN_APPEAL]: ':building_construction: Kæra opnuð aftur', RESUBMIT: ':mailbox_with_mail: Sent aftur', - RECEIVE: ':eyes: Móttekið', - ACCEPT: ':white_check_mark: Samþykkt', - REJECT: ':negative_squared_cross_mark: Hafnað', - DISMISS: ':woman-shrugging: Vísað frá', - COMPLETE: ':white_check_mark: Lokið', - DELETE: ':fire: Afturkallað', + [CaseTransition.RETURN_INDICTMENT]: ':woman-gesturing-no: Ákæru afturkallað', SCHEDULE_COURT_DATE: ':timer_clock: Fyrirtökutíma úthlutað', - ARCHIVE: ':file_cabinet: Sett í geymslu', - REOPEN: ':construction: Opnað aftur', - APPEAL: ':judge: Kæra', - RECEIVE_APPEAL: ':eyes: Kæra móttekin', - COMPLETE_APPEAL: ':white_check_mark: Kæru lokið', - REOPEN_APPEAL: ':building_construction: Kæra opnuð aftur', + [CaseTransition.SUBMIT]: ':mailbox_with_mail: Sent', + [CaseTransition.WITHDRAW_APPEAL]: + ':leftwards_arrow_with_hook: Kæru afturkallað', } -export enum CaseEvent { - CREATE = 'CREATE', - CREATE_XRD = 'CREATE_XRD', - EXTEND = 'EXTEND', - OPEN = 'OPEN', - ASK_FOR_CONFIRMATION = 'ASK_FOR_CONFIRMATION', - SUBMIT = 'SUBMIT', - RESUBMIT = 'RESUBMIT', - RECEIVE = 'RECEIVE', - ACCEPT = 'ACCEPT', - REJECT = 'REJECT', - DELETE = 'DELETE', - SCHEDULE_COURT_DATE = 'SCHEDULE_COURT_DATE', - DISMISS = 'DISMISS', - ARCHIVE = 'ARCHIVE', - REOPEN = 'REOPEN', - APPEAL = 'APPEAL', - RECEIVE_APPEAL = 'RECEIVE_APPEAL', - COMPLETE_APPEAL = 'COMPLETE_APPEAL', - REOPEN_APPEAL = 'REOPEN_APPEAL', -} +export type CaseEvent = + | CaseTransition + | 'ARCHIVE' + | 'CREATE' + | 'CREATE_XRD' + | 'EXTEND' + | 'RESUBMIT' + | 'SCHEDULE_COURT_DATE' @Injectable() export class EventService { @@ -120,7 +115,7 @@ export class EventService { ? `\n>Landsréttur *${theCase.appealCaseNumber}*` : '' const extraText = - event === CaseEvent.SCHEDULE_COURT_DATE + event === 'SCHEDULE_COURT_DATE' ? `\n>Dómari ${ theCase.judge?.name ?? 'er ekki skráður' }\n>Dómritari ${ diff --git a/apps/judicial-system/backend/src/app/modules/file/dto/createFile.dto.ts b/apps/judicial-system/backend/src/app/modules/file/dto/createFile.dto.ts index d96df0d3d238..de31caf5e46b 100644 --- a/apps/judicial-system/backend/src/app/modules/file/dto/createFile.dto.ts +++ b/apps/judicial-system/backend/src/app/modules/file/dto/createFile.dto.ts @@ -58,4 +58,9 @@ export class CreateFileDto { @IsString() @ApiPropertyOptional({ type: String }) readonly policeFileId?: string + + @IsOptional() + @IsString() + @ApiPropertyOptional({ type: String }) + readonly userGeneratedFilename?: string } diff --git a/apps/judicial-system/backend/src/app/modules/file/file.service.ts b/apps/judicial-system/backend/src/app/modules/file/file.service.ts index ddcae8eed39f..5fa7a343528d 100644 --- a/apps/judicial-system/backend/src/app/modules/file/file.service.ts +++ b/apps/judicial-system/backend/src/app/modules/file/file.service.ts @@ -308,7 +308,8 @@ export class FileService { state: CaseFileState.STORED_IN_RVG, caseId: theCase.id, name: fileName, - userGeneratedFilename: fileName.replace(/\.pdf$/, ''), + userGeneratedFilename: + createFile.userGeneratedFilename ?? fileName.replace(/\.pdf$/, ''), submittedBy: user.name, }) diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts b/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts index dd27353157d3..5455ad7976b0 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts @@ -19,4 +19,6 @@ export const defenderCaseFileCategoriesForIndictmentCases = [ CaseFileCategory.CRIMINAL_RECORD, CaseFileCategory.COST_BREAKDOWN, CaseFileCategory.CASE_FILE, + CaseFileCategory.PROSECUTOR_CASE_FILE, + CaseFileCategory.DEFENDANT_CASE_FILE, ] diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessWriteCaseFile.guard.ts b/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessWriteCaseFile.guard.ts index 224cf0ed458d..42fb0abf30b7 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessWriteCaseFile.guard.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessWriteCaseFile.guard.ts @@ -8,16 +8,24 @@ import { import { CaseFileCategory, + isIndictmentCase, User, UserRole, } from '@island.is/judicial-system/types' +import { Case } from '../../case' + @Injectable() export class LimitedAccessWriteCaseFileGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest() const user: User = request.user + const theCase: Case = request.case + + if (!theCase) { + throw new InternalServerErrorException('Missing case') + } if (!user) { throw new InternalServerErrorException('Missing user') @@ -32,17 +40,22 @@ export class LimitedAccessWriteCaseFileGuard implements CanActivate { throw new InternalServerErrorException('Missing case file category') } - if ( - user.role === UserRole.DEFENDER && - [ - CaseFileCategory.DEFENDANT_APPEAL_BRIEF, - CaseFileCategory.DEFENDANT_APPEAL_BRIEF_CASE_FILE, - CaseFileCategory.DEFENDANT_APPEAL_STATEMENT, - CaseFileCategory.DEFENDANT_APPEAL_STATEMENT_CASE_FILE, - CaseFileCategory.DEFENDANT_APPEAL_CASE_FILE, - ].includes(caseFileCategory) - ) { - return true + if (user.role === UserRole.DEFENDER) { + if (isIndictmentCase(theCase.type)) { + if (caseFileCategory === CaseFileCategory.DEFENDANT_CASE_FILE) { + return true + } + } else if ( + [ + CaseFileCategory.DEFENDANT_APPEAL_BRIEF, + CaseFileCategory.DEFENDANT_APPEAL_BRIEF_CASE_FILE, + CaseFileCategory.DEFENDANT_APPEAL_STATEMENT, + CaseFileCategory.DEFENDANT_APPEAL_STATEMENT_CASE_FILE, + CaseFileCategory.DEFENDANT_APPEAL_CASE_FILE, + ].includes(caseFileCategory) + ) { + return true + } } throw new ForbiddenException(`Forbidden for ${user.role}`) diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessViewCaseFileGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessViewCaseFileGuard.spec.ts index e80074e5236a..dd31ac1d7816 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessViewCaseFileGuard.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessViewCaseFileGuard.spec.ts @@ -158,6 +158,8 @@ describe('Limited Access View Case File Guard', () => { CaseFileCategory.CRIMINAL_RECORD, CaseFileCategory.COST_BREAKDOWN, CaseFileCategory.CASE_FILE, + CaseFileCategory.PROSECUTOR_CASE_FILE, + CaseFileCategory.DEFENDANT_CASE_FILE, ] describe.each(allowedCaseFileCategories)( diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessWriteCaseFileGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessWriteCaseFileGuard.spec.ts index b4a05453cfa5..5b5854214b6f 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessWriteCaseFileGuard.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/test/limitedAccessWriteCaseFileGuard.spec.ts @@ -4,7 +4,11 @@ import { InternalServerErrorException, } from '@nestjs/common' -import { CaseFileCategory, UserRole } from '@island.is/judicial-system/types' +import { + CaseFileCategory, + CaseType, + UserRole, +} from '@island.is/judicial-system/types' import { LimitedAccessWriteCaseFileGuard } from '../limitedAccessWriteCaseFile.guard' @@ -52,6 +56,7 @@ describe('LimitedAccess Write Case File Guard', () => { beforeEach(() => { mockRequest.mockImplementationOnce(() => ({ user: { role: UserRole.DEFENDER }, + case: {}, body: { category }, })) @@ -69,6 +74,7 @@ describe('LimitedAccess Write Case File Guard', () => { beforeEach(() => { mockRequest.mockImplementationOnce(() => ({ user: { role: UserRole.DEFENDER }, + case: {}, caseFile: { category }, })) @@ -94,6 +100,7 @@ describe('LimitedAccess Write Case File Guard', () => { beforeEach(() => { mockRequest.mockImplementationOnce(() => ({ user: { role: UserRole.DEFENDER }, + case: {}, body: { category }, })) @@ -112,6 +119,7 @@ describe('LimitedAccess Write Case File Guard', () => { beforeEach(() => { mockRequest.mockImplementationOnce(() => ({ user: { role: UserRole.DEFENDER }, + case: {}, caseFile: { category }, })) @@ -137,6 +145,7 @@ describe('LimitedAccess Write Case File Guard', () => { beforeEach(() => { mockRequest.mockImplementationOnce(() => ({ user: { role }, + case: {}, body: { category }, })) @@ -155,6 +164,7 @@ describe('LimitedAccess Write Case File Guard', () => { beforeEach(() => { mockRequest.mockImplementationOnce(() => ({ user: { role }, + case: {}, caseFile: { category }, })) @@ -174,7 +184,7 @@ describe('LimitedAccess Write Case File Guard', () => { let then: Then beforeEach(() => { - mockRequest.mockImplementationOnce(() => ({})) + mockRequest.mockImplementationOnce(() => ({ case: {} })) then = givenWhenThen() }) @@ -185,6 +195,21 @@ describe('LimitedAccess Write Case File Guard', () => { }) }) + describe('missing case', () => { + let then: Then + + beforeEach(() => { + mockRequest.mockImplementationOnce(() => ({ user: {} })) + + then = givenWhenThen() + }) + + it('should throw InternalServerErrorException', () => { + expect(then.error).toBeInstanceOf(InternalServerErrorException) + expect(then.error.message).toBe('Missing case') + }) + }) + describe('missing case file category', () => { let then: Then @@ -199,4 +224,84 @@ describe('LimitedAccess Write Case File Guard', () => { expect(then.error.message).toBe('Missing case file category') }) }) + + describe('a defender can view DEFENDANT_CASE_FILE in indictment cases', () => { + describe('when creating a case file', () => { + let then: Then + + beforeEach(() => { + mockRequest.mockImplementationOnce(() => ({ + user: { role: UserRole.DEFENDER }, + case: { type: CaseType.INDICTMENT }, + body: { category: CaseFileCategory.DEFENDANT_CASE_FILE }, + })) + + then = givenWhenThen() + }) + + it('should activate', () => { + expect(then.result).toBe(true) + }) + }) + + describe('when deleting a case file', () => { + let then: Then + + beforeEach(() => { + mockRequest.mockImplementationOnce(() => ({ + user: { role: UserRole.DEFENDER }, + case: { type: CaseType.INDICTMENT }, + caseFile: { category: CaseFileCategory.DEFENDANT_CASE_FILE }, + })) + + then = givenWhenThen() + }) + + it('should activate', () => { + expect(then.result).toBe(true) + }) + }) + }) + + describe.each( + Object.keys(CaseType).filter((ct) => ct !== CaseType.INDICTMENT), + )('a defender can not view DEFENDANT_CASE_FILE in %s cases', (caseType) => { + describe('when creating a case file', () => { + let then: Then + + beforeEach(() => { + mockRequest.mockImplementationOnce(() => ({ + user: { role: UserRole.DEFENDER }, + case: { type: caseType }, + body: { category: CaseFileCategory.DEFENDANT_CASE_FILE }, + })) + + then = givenWhenThen() + }) + + it('should throw ForbiddenException', () => { + expect(then.error).toBeInstanceOf(ForbiddenException) + expect(then.error.message).toBe(`Forbidden for DEFENDER`) + }) + }) + + describe('when deleting a case file', () => { + let then: Then + + beforeEach(() => { + mockRequest.mockImplementationOnce(() => ({ + user: { role: UserRole.DEFENDER }, + case: { type: caseType }, + caseFile: { category: CaseFileCategory.DEFENDANT_CASE_FILE }, + })) + + then = givenWhenThen() + }) + + it('should throw ForbiddenException', () => { + expect(then.error).toBeInstanceOf(ForbiddenException) + expect(then.error.message).toBe(`Forbidden for DEFENDER`) + }) + }) + }) }) diff --git a/apps/judicial-system/backend/src/app/modules/file/limitedAccessFile.controller.ts b/apps/judicial-system/backend/src/app/modules/file/limitedAccessFile.controller.ts index 3d8b09f65443..395a9ef78d84 100644 --- a/apps/judicial-system/backend/src/app/modules/file/limitedAccessFile.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/file/limitedAccessFile.controller.ts @@ -21,6 +21,7 @@ import { } from '@island.is/judicial-system/auth' import type { User } from '@island.is/judicial-system/types' import { + indictmentCases, investigationCases, restrictionCases, } from '@island.is/judicial-system/types' @@ -57,9 +58,12 @@ export class LimitedAccessFileController { ) {} @UseGuards( - new CaseTypeGuard([...restrictionCases, ...investigationCases]), + new CaseTypeGuard([ + ...restrictionCases, + ...investigationCases, + ...indictmentCases, + ]), CaseWriteGuard, - CaseCompletedGuard, ) @RolesRules(defenderRule) @Post('file/url') @@ -78,9 +82,12 @@ export class LimitedAccessFileController { } @UseGuards( - new CaseTypeGuard([...restrictionCases, ...investigationCases]), + new CaseTypeGuard([ + ...restrictionCases, + ...investigationCases, + ...indictmentCases, + ]), CaseWriteGuard, - CaseCompletedGuard, LimitedAccessWriteCaseFileGuard, ) @RolesRules(defenderRule) diff --git a/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createCaseFileGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createCaseFileGuards.spec.ts index b18200a5cb3e..7ca557087cb9 100644 --- a/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createCaseFileGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createCaseFileGuards.spec.ts @@ -1,13 +1,10 @@ import { + indictmentCases, investigationCases, restrictionCases, } from '@island.is/judicial-system/types' -import { - CaseCompletedGuard, - CaseTypeGuard, - CaseWriteGuard, -} from '../../../case' +import { CaseTypeGuard, CaseWriteGuard } from '../../../case' import { LimitedAccessWriteCaseFileGuard } from '../../guards/limitedAccessWriteCaseFile.guard' import { LimitedAccessFileController } from '../../limitedAccessFile.controller' @@ -23,13 +20,16 @@ describe('LimitedAccessFileController - Create case file guards', () => { }) it('should have the right guard configuration', () => { - expect(guards).toHaveLength(4) + expect(guards).toHaveLength(3) expect(guards[0]).toBeInstanceOf(CaseTypeGuard) expect(guards[0]).toEqual({ - allowedCaseTypes: [...restrictionCases, ...investigationCases], + allowedCaseTypes: [ + ...restrictionCases, + ...investigationCases, + ...indictmentCases, + ], }) expect(new guards[1]()).toBeInstanceOf(CaseWriteGuard) - expect(new guards[2]()).toBeInstanceOf(CaseCompletedGuard) - expect(new guards[3]()).toBeInstanceOf(LimitedAccessWriteCaseFileGuard) + expect(new guards[2]()).toBeInstanceOf(LimitedAccessWriteCaseFileGuard) }) }) diff --git a/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createPresignedPostGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createPresignedPostGuards.spec.ts index 6483025331da..310e6b9f70c2 100644 --- a/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createPresignedPostGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/file/test/limitedAccessFileController/createPresignedPostGuards.spec.ts @@ -1,4 +1,5 @@ import { + indictmentCases, investigationCases, restrictionCases, } from '@island.is/judicial-system/types' @@ -22,12 +23,15 @@ describe('LimitedAccessFileController - Create presigned post guards', () => { }) it('should have the right guard configuration', () => { - expect(guards).toHaveLength(3) + expect(guards).toHaveLength(2) expect(guards[0]).toBeInstanceOf(CaseTypeGuard) expect(guards[0]).toEqual({ - allowedCaseTypes: [...restrictionCases, ...investigationCases], + allowedCaseTypes: [ + ...restrictionCases, + ...investigationCases, + ...indictmentCases, + ], }) expect(new guards[1]()).toBeInstanceOf(CaseWriteGuard) - expect(new guards[2]()).toBeInstanceOf(CaseCompletedGuard) }) }) diff --git a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts index 85312b0bb572..2c6aa5278d6d 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts @@ -416,7 +416,7 @@ export class InternalNotificationService extends BaseNotificationService { } else if (theCase.state === CaseState.RECEIVED) { promises.push(this.sendResubmittedToCourtSmsNotificationToCourt(theCase)) - this.eventService.postEvent(CaseEvent.RESUBMIT, theCase) + this.eventService.postEvent('RESUBMIT', theCase) } if ( @@ -716,14 +716,6 @@ export class InternalNotificationService extends BaseNotificationService { theCase: Case, user: User, ): Promise[] { - if ( - ExplanatoryComment.postponedIndefinitelyExplanation( - theCase.explanatoryComments, - ) - ) { - return [] - } - const courtDate = DateLog.courtDate(theCase.dateLogs) if (!courtDate) { @@ -776,7 +768,7 @@ export class InternalNotificationService extends BaseNotificationService { theCase: Case, user: User, ): Promise { - this.eventService.postEvent(CaseEvent.SCHEDULE_COURT_DATE, theCase) + this.eventService.postEvent('SCHEDULE_COURT_DATE', theCase) const promises: Promise[] = [] diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts index a93d21be152f..8a9e78671c95 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts @@ -56,11 +56,7 @@ export class NotificationService { break case NotificationType.COURT_DATE: if (eventOnly) { - this.eventService.postEvent( - CaseEvent.SCHEDULE_COURT_DATE, - theCase, - true, - ) + this.eventService.postEvent('SCHEDULE_COURT_DATE', theCase, true) // We still want to send the defender a link to the case even if // the judge chooses not to send a calendar invitation diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCourtDateNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCourtDateNotifications.spec.ts index 43a603c94540..a4cb1495e900 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCourtDateNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCourtDateNotifications.spec.ts @@ -163,7 +163,7 @@ describe('InternalNotificationController - Send court date notifications', () => }) }) - describe('notification sent for postponed indictment', () => { + describe('notification sent for indictment', () => { let then: Then const notificationDto: CaseNotificationDto = { @@ -195,16 +195,16 @@ describe('InternalNotificationController - Send court date notifications', () => expect(mockEmailService.sendEmail).toHaveBeenCalledWith( expect.objectContaining({ to: [{ name: prosecutorName, address: prosecutorEmail }], - subject: `Frestun - nýtt þinghald í máli ${courtCaseNumber}`, - html: `Héraðsdómur Reykjavíkur boðar til þinghalds í máli ${courtCaseNumber}.
Fyrirtaka mun fara fram 2. maí 2024, kl. 14:32.

Dómsalur hefur ekki verið skráður.

Dómari hefur ekki verið skráður.

Hægt er að nálgast gögn málsins á yfirlitssíðu málsins í Réttarvörslugátt.`, + subject: `Nýtt þinghald í máli ${courtCaseNumber}`, + html: `Héraðsdómur Reykjavíkur boðar til þinghalds í máli ${courtCaseNumber}.
Fyrirtaka mun fara fram 2. maí 2024, kl. 14:32.

Tegund þinghalds: Óþekkt.

Dómsalur hefur ekki verið skráður.

Dómari hefur ekki verið skráður.

Hægt er að nálgast gögn málsins á yfirlitssíðu málsins í Réttarvörslugátt.`, }), ) expect(mockEmailService.sendEmail).toHaveBeenCalledWith( expect.objectContaining({ to: [{ name: defenderName, address: defenderEmail }], - subject: `Frestun - nýtt þinghald í máli ${courtCaseNumber}`, - html: `Héraðsdómur Reykjavíkur boðar til þinghalds í máli ${courtCaseNumber}.
Fyrirtaka mun fara fram 2. maí 2024, kl. 14:32.

Dómsalur hefur ekki verið skráður.

Dómari hefur ekki verið skráður.

Hægt er að nálgast gögn málsins hjá Héraðsdómur Reykjavíkur.`, + subject: `Nýtt þinghald í máli ${courtCaseNumber}`, + html: `Héraðsdómur Reykjavíkur boðar til þinghalds í máli ${courtCaseNumber}.
Fyrirtaka mun fara fram 2. maí 2024, kl. 14:32.

Tegund þinghalds: Óþekkt.

Dómsalur hefur ekki verið skráður.

Dómari hefur ekki verið skráður.

Hægt er að nálgast gögn málsins hjá Héraðsdómur Reykjavíkur.`, }), ) diff --git a/apps/judicial-system/web/messages/Core/errors.ts b/apps/judicial-system/web/messages/Core/errors.ts index 806d5db20aec..406e594e9e3e 100644 --- a/apps/judicial-system/web/messages/Core/errors.ts +++ b/apps/judicial-system/web/messages/Core/errors.ts @@ -122,4 +122,9 @@ export const errors = defineMessages({ defaultMessage: 'Upp kom villa við að skrá aðgerð', description: 'Notaður sem villuskilaboð þegar ekki gengur að skrá atburð', }, + openDocument: { + id: 'judicial.system.core:errors.open_document', + defaultMessage: 'Upp kom villa við að opna skjal', + description: 'Notaður sem villuskilaboð þegar ekki gengur að opna skjal', + }, }) diff --git a/apps/judicial-system/web/messages/Core/index.ts b/apps/judicial-system/web/messages/Core/index.ts index 14f4fdeda0d7..f783a0114759 100644 --- a/apps/judicial-system/web/messages/Core/index.ts +++ b/apps/judicial-system/web/messages/Core/index.ts @@ -184,11 +184,6 @@ export const core = defineMessages({ defaultMessage: 'Tími handtöku', description: 'Notað fyrir orðið Tími handtöku í öllum flæðum.', }, - confirmedCourtDate: { - id: 'judicial.system.core:confirmed_court_date', - defaultMessage: 'Staðfestur fyrirtökutími', - description: 'Notað fyrir orðið Staðfestur fyrirtökutími í öllum flæðum.', - }, update: { id: 'judicial.system.core:update', defaultMessage: 'Uppfæra', diff --git a/apps/judicial-system/web/messages/Core/tables.ts b/apps/judicial-system/web/messages/Core/tables.ts index 77d642b8cb3c..66879b64ce6a 100644 --- a/apps/judicial-system/web/messages/Core/tables.ts +++ b/apps/judicial-system/web/messages/Core/tables.ts @@ -119,4 +119,19 @@ export const tables = defineMessages({ description: 'Notaður sem titill fyrir Dómsuppkvaðning dálk í lista yfir mál.', }, + caseFileName: { + id: 'judicial.system.core:tables.case_file_name', + defaultMessage: 'Nafn skjals', + description: 'Notaður sem titill fyrir nafn dálk í lista yfir mál.', + }, + caseFileDate: { + id: 'judicial.system.core:tables.case_file_date', + defaultMessage: 'Dagsetning skjals', + description: 'Notaður sem titill fyrir dagsetningu í lista yfir mál.', + }, + sent: { + id: 'judicial.system.core:tables.sent', + defaultMessage: 'Sent', + description: 'Notaður sem titill fyrir sent dálk í lista yfir mál.', + }, }) diff --git a/apps/judicial-system/web/messages/Core/titles.ts b/apps/judicial-system/web/messages/Core/titles.ts index f44b175d28cc..02b63cf687d5 100644 --- a/apps/judicial-system/web/messages/Core/titles.ts +++ b/apps/judicial-system/web/messages/Core/titles.ts @@ -109,6 +109,12 @@ export const titles = { description: 'Notaður sem titill fyrir Yfirlit ákæru skjá hjá saksóknara í ákærum', }), + addFiles: defineMessage({ + id: 'judicial.system.core:titles.prosecutor.indictments.add_files', + defaultMessage: 'Bæta við gögnum - Réttarvörslugátt', + description: + 'Notaður sem titill fyrir Bæta við gögnum skjá hjá saksóknara í ákærum', + }), }, }, court: { diff --git a/apps/judicial-system/web/pages/_app.tsx b/apps/judicial-system/web/pages/_app.tsx index 0a2b1e799ec4..05e88de3cff4 100644 --- a/apps/judicial-system/web/pages/_app.tsx +++ b/apps/judicial-system/web/pages/_app.tsx @@ -1,4 +1,3 @@ -import React from 'react' import App, { AppContext, AppProps } from 'next/app' import getConfig from 'next/config' import Head from 'next/head' diff --git a/apps/judicial-system/web/pages/_document.tsx b/apps/judicial-system/web/pages/_document.tsx index c920f17d3807..1ffe9864b38a 100644 --- a/apps/judicial-system/web/pages/_document.tsx +++ b/apps/judicial-system/web/pages/_document.tsx @@ -1,4 +1,3 @@ -import React from 'react' import Document, { Head, Html, Main, NextScript } from 'next/document' export default class MyDocument extends Document { diff --git a/apps/judicial-system/web/pages/akaera/gogn/[id].ts b/apps/judicial-system/web/pages/akaera/gogn/[id].ts new file mode 100644 index 000000000000..2efcee1d7471 --- /dev/null +++ b/apps/judicial-system/web/pages/akaera/gogn/[id].ts @@ -0,0 +1,3 @@ +import AddFiles from '@island.is/judicial-system-web/src/routes/Shared/AddFiles/AddFiles' + +export default AddFiles diff --git a/apps/judicial-system/web/pages/verjandi/akaera/gogn/[id].ts b/apps/judicial-system/web/pages/verjandi/akaera/gogn/[id].ts new file mode 100644 index 000000000000..e15aa0356897 --- /dev/null +++ b/apps/judicial-system/web/pages/verjandi/akaera/gogn/[id].ts @@ -0,0 +1,3 @@ +import AddFiles from '@island.is/judicial-system-web/pages/akaera/gogn/[id]' + +export default AddFiles diff --git a/apps/judicial-system/web/src/components/AccordionItems/CaseFilesAccordionItem/CaseFilesAccordionItem.tsx b/apps/judicial-system/web/src/components/AccordionItems/CaseFilesAccordionItem/CaseFilesAccordionItem.tsx index 8d1c564b4f0e..e4573e6bfc86 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/CaseFilesAccordionItem/CaseFilesAccordionItem.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/CaseFilesAccordionItem/CaseFilesAccordionItem.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FC, SetStateAction } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' diff --git a/apps/judicial-system/web/src/components/AccordionItems/CaseFilesAccordionItem/UploadStateMessage.tsx b/apps/judicial-system/web/src/components/AccordionItems/CaseFilesAccordionItem/UploadStateMessage.tsx index aa1050c8fb89..1162e5eec194 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/CaseFilesAccordionItem/UploadStateMessage.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/CaseFilesAccordionItem/UploadStateMessage.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { motion } from 'framer-motion' import { Box, Icon, IconMapIcon, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/AccordionItems/CommentsAccordionItem/CommentsAccordionItem.tsx b/apps/judicial-system/web/src/components/AccordionItems/CommentsAccordionItem/CommentsAccordionItem.tsx index 97cc6b08e1d7..7ab75ca1c479 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/CommentsAccordionItem/CommentsAccordionItem.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/CommentsAccordionItem/CommentsAccordionItem.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AccordionItem, Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/AccordionItems/ConnectedCaseFilesAccordionItem/ConnectedCaseFilesAccordionItem.tsx b/apps/judicial-system/web/src/components/AccordionItems/ConnectedCaseFilesAccordionItem/ConnectedCaseFilesAccordionItem.tsx index cc7ac46083c5..c65bf82aa94c 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/ConnectedCaseFilesAccordionItem/ConnectedCaseFilesAccordionItem.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/ConnectedCaseFilesAccordionItem/ConnectedCaseFilesAccordionItem.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AccordionItem } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/AccordionItems/CourtRecordAccordionItem/CourtRecordAccordionItem.tsx b/apps/judicial-system/web/src/components/AccordionItems/CourtRecordAccordionItem/CourtRecordAccordionItem.tsx index 8d4682aa20ad..033973403713 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/CourtRecordAccordionItem/CourtRecordAccordionItem.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/CourtRecordAccordionItem/CourtRecordAccordionItem.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import isSameDay from 'date-fns/isSameDay' diff --git a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.css.ts b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.css.ts index 0db5e6484397..173b9d5f50d5 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.css.ts +++ b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.css.ts @@ -17,42 +17,5 @@ export const reorderGroup = style({ export const reorderItem = style({ position: 'relative', - borderRadius: theme.border.radius.large, overflow: 'hidden', }) - -export const caseFileWrapper = style({ - display: 'flex', - alignItems: 'center', - background: theme.color.blue100, - paddingRight: `${theme.spacing[2]}px`, -}) - -export const editCaseFileInputContainer = style({ - display: 'flex', - flexDirection: 'row', - flexGrow: 1, - marginRight: theme.spacing[2], -}) - -export const editCaseFileName = style({ - flex: 2, - marginRight: theme.spacing[1], -}) - -export const editCaseFileDisplayDate = style({ - flex: 1, -}) - -export const editCaseFileButton = style({ - width: '32px', - height: '34px', - padding: '4px', - borderRadius: theme.border.radius.large, - transition: 'background-color 0.2s ease-in-out', - selectors: { - '&:hover': { - backgroundColor: theme.color.blue200, - }, - }, -}) diff --git a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.spec.tsx b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.spec.tsx index e5c3cf810811..01540f13263d 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.spec.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.spec.tsx @@ -8,58 +8,6 @@ import { sortedFilesInChapter, } from './IndictmentsCaseFilesAccordionItem' -const items: ReorderableItem[] = [ - { - id: faker.datatype.uuid(), - displayText: faker.lorem.words(2), - isDivider: false, - isHeading: false, - chapter: 0, - orderWithinChapter: 0, - }, - { - id: faker.datatype.uuid(), - displayText: faker.lorem.words(2), - isDivider: false, - isHeading: false, - chapter: 0, - orderWithinChapter: 1, - }, - { - id: faker.datatype.uuid(), - displayText: faker.lorem.words(2), - isDivider: false, - isHeading: true, - chapter: 1, - }, - { - id: faker.datatype.uuid(), - displayText: faker.lorem.words(2), - isDivider: false, - isHeading: false, - chapter: 1, - orderWithinChapter: 0, - }, - { - id: faker.datatype.uuid(), - displayText: faker.lorem.words(2), - isDivider: true, - isHeading: false, - }, - { - id: faker.datatype.uuid(), - displayText: faker.lorem.words(2), - isDivider: false, - isHeading: false, - }, - { - id: faker.datatype.uuid(), - displayText: faker.lorem.words(2), - isDivider: false, - isHeading: false, - }, -] - const caseFiles = [ { id: faker.datatype.uuid(), @@ -117,29 +65,233 @@ describe('getFilesToUpdate', () => { }) it('should return the item if a file is reordered but not put under a chapter', () => { - expect(getFilesToUpdate(items[items.length - 1].id || '', items)).toEqual([ + const items: ReorderableItem[] = [ + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 0, + orderWithinChapter: 0, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 0, + orderWithinChapter: 1, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: true, + chapter: 1, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 1, + orderWithinChapter: 0, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: true, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 2, + orderWithinChapter: 0, + }, + ] + + expect(getFilesToUpdate(items[items.length - 1].id, items)).toEqual([ null, null, [items[items.length - 1]], ]) }) - it('should return the correct chapter, orderWithinChapter and items if a file is reordered under a chapter', () => { - expect(getFilesToUpdate(items[0].id || '', items)).toEqual([ + it('should return the correct chapter, orderWithinChapter and items if a file is reordered as the first item in a chapter', () => { + const items: ReorderableItem[] = [ + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 0, + orderWithinChapter: 1, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: true, + chapter: 1, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 1, + orderWithinChapter: 0, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: true, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + }, + ] + + expect(getFilesToUpdate(items[0].id, items)).toEqual([ 0, 0, [items[0], items[1]], ]) - expect(getFilesToUpdate(items[1].id || '', items)).toEqual([ - 0, - 1, - [items[1]], - ]) - expect(getFilesToUpdate(items[3].id || '', items)).toEqual([ - 1, - 0, - [items[3]], - ]) + }) + + it('should return the correct chapter, orderWithinChapter and items if a file is reordered as the last item in a chapter', () => { + const items: ReorderableItem[] = [ + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 0, + orderWithinChapter: 0, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: true, + chapter: 1, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 1, + orderWithinChapter: 0, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: true, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + }, + ] + + expect(getFilesToUpdate(items[1].id, items)).toEqual([0, 1, [items[1]]]) + }) + + it('should return the correct chapter, orderWithinChapter and items if a file is reordered under a previously empty chapter', () => { + const items: ReorderableItem[] = [ + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 0, + orderWithinChapter: 0, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 0, + orderWithinChapter: 1, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: true, + chapter: 1, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + chapter: 4, + orderWithinChapter: 4, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: true, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + }, + { + id: faker.datatype.uuid(), + displayText: faker.lorem.words(2), + isDivider: false, + isHeading: false, + }, + ] + + expect(getFilesToUpdate(items[3].id, items)).toEqual([1, 0, [items[3]]]) }) }) @@ -151,6 +303,11 @@ describe('sortedFilesInChapter', () => { it('should return an array of files in chapter sorted by orderWithinChapter', () => { expect(sortedFilesInChapter(0, caseFiles)).toEqual([ { + canEdit: true, + category: undefined, + displayDate: undefined, + status: 'done', + userGeneratedFilename: undefined, displayText: caseFiles[1].name, isDivider: false, isHeading: false, @@ -161,6 +318,11 @@ describe('sortedFilesInChapter', () => { canOpen: false, }, { + canEdit: true, + category: undefined, + displayDate: undefined, + status: 'done', + userGeneratedFilename: undefined, displayText: caseFiles[0].name, isDivider: false, isHeading: false, @@ -171,6 +333,11 @@ describe('sortedFilesInChapter', () => { canOpen: false, }, { + canEdit: true, + category: undefined, + displayDate: undefined, + status: 'done', + userGeneratedFilename: undefined, displayText: caseFiles[2].name, isDivider: false, isHeading: false, diff --git a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.strings.ts b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.strings.ts index c180d7a0a40b..13e7cd0862c0 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.strings.ts +++ b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.strings.ts @@ -1,6 +1,6 @@ import { defineMessages } from 'react-intl' -export const indictmentsCaseFilesAccordionItem = defineMessages({ +export const strings = defineMessages({ title: { id: 'judicial.system.core:indictments_case_files_accordion_item.title', defaultMessage: 'Gögn úr LÖKE máli {policeCaseNumber}', @@ -68,32 +68,14 @@ export const indictmentsCaseFilesAccordionItem = defineMessages({ description: 'Notaður sem titill á kafla í fellilista í skjalaskrá skrefi í ákærum.', }, - simpleInputPlaceholder: { - id: 'judicial.system.core:indictments_case_files_accordion_item.simple_input_placeholder', - defaultMessage: 'Skráðu inn heiti á skjali', - description: - 'Notaður sem skýritexti í textasvæði reit til að breyta heiti skjals.', - }, - invalidDateErrorMessage: { - id: 'judicial.system.core:indictments_case_files_accordion_item.invalid_date_error_message', - defaultMessage: - 'Ekki tókst að uppfæra skjal, dagsetning er ekki á réttu formi', - description: - 'Notaður sem villuboð þegar tekst ekki að uppfæra dagsetningu á skjali.', - }, - renameFailedErrorMessage: { - id: 'judicial.system.core:indictments_case_files_accordion_item.rename_failed_error_message', - defaultMessage: 'Ekki tókst að endurnefna skjal', - description: 'Notaður sem villuboð þegar endurnefning á skjali mistókst.', - }, reorderFailedErrorMessage: { id: 'judicial.system.core:indictments_case_files_accordion_item.reorder_failed_error_message', defaultMessage: 'Ekki tókst að endurraða skjölum', description: 'Notaður sem villuboð þegar endurröðun á skjölum mistókst.', }, - removeFailedErrorMessage: { - id: 'judicial.system.core:indictments_case_files_accordion_item.remove_failed_error_message', - defaultMessage: 'Ekki tókst að eyða skjali', - description: 'Notaður sem villuboð þegar eyða skjali mistókst.', + renameFailedErrorMessage: { + id: 'judicial.system.core:indictments_case_files_accordion_item.rename_failed_error_message', + defaultMessage: 'Ekki tókst að endurnefna skjal', + description: 'Notaður sem villuboð þegar endurnefning á skjali mistókst.', }, }) diff --git a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.tsx b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.tsx index f1111ccf06b0..1061882823f0 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsCaseFilesAccordionItem/IndictmentsCaseFilesAccordionItem.tsx @@ -1,9 +1,12 @@ -import React, { FC, PointerEvent, useEffect, useMemo, useState } from 'react' -import InputMask from 'react-input-mask' +import { + Dispatch, + FC, + PointerEvent, + SetStateAction, + useEffect, + useState, +} from 'react' import { useIntl } from 'react-intl' -import { useMeasure } from 'react-use' -import isValid from 'date-fns/isValid' -import parseISO from 'date-fns/parseISO' import { animate, AnimatePresence, @@ -19,12 +22,10 @@ import { AccordionItem, AlertMessage, Box, - Icon, - Input, Text, toast, + UploadFileStatus, } from '@island.is/island-ui/core' -import { formatDate } from '@island.is/judicial-system/formatters' import { CrimeSceneMap, IndictmentSubtypeMap, @@ -40,8 +41,11 @@ import { useS3Upload, } from '@island.is/judicial-system-web/src/utils/hooks' +import EditableCaseFile, { + TEditableCaseFile, +} from '../../EditableCaseFile/EditableCaseFile' import { useUpdateFilesMutation } from './updateFiles.generated' -import { indictmentsCaseFilesAccordionItem as m } from './IndictmentsCaseFilesAccordionItem.strings' +import { strings } from './IndictmentsCaseFilesAccordionItem.strings' import * as styles from './IndictmentsCaseFilesAccordionItem.css' interface Props { @@ -51,27 +55,23 @@ interface Props { shouldStartExpanded: boolean subtypes?: IndictmentSubtypeMap crimeScenes?: CrimeSceneMap + setEditCount: Dispatch> } interface CaseFileProps { caseFile: ReorderableItem onReorder: (id?: string) => void onOpen: (id: string) => void - onRename: (id: string, name?: string, displayDate?: string) => void - onDelete: (id: string) => void + onRename: (id: string, name: string, displayDate: string) => void + onDelete: (file: TUploadFile) => void + setEditCount: Dispatch> } -export interface ReorderableItem { - id: string - displayText?: string | null +export interface ReorderableItem extends TEditableCaseFile { isDivider: boolean isHeading: boolean - created?: string | null chapter?: number | null orderWithinChapter?: number | null - userGeneratedFilename?: string | null - displayDate?: string | null - canOpen?: boolean } const useRaisedShadow = (value: MotionValue) => { @@ -80,7 +80,7 @@ const useRaisedShadow = (value: MotionValue) => { useEffect(() => { let isActive = false - value.onChange((latest) => { + value.on('change', (latest) => { const wasActive = isActive if (latest !== 0) { isActive = true @@ -115,12 +115,25 @@ export const getFilesToUpdate = ( files[index - 1].chapter === null) ) { // The file is not in a chapter - return [null, null, [files[index]]] + return [ + null, + null, + files[index].chapter === undefined || files[index].chapter === null + ? [] + : [files[index]], + ] } const chapter = files[index - 1]?.chapter ?? 0 const orderWithinChapter = (files[index - 1]?.orderWithinChapter ?? -1) + 1 + if ( + files[index].chapter === chapter && + files[index].orderWithinChapter === orderWithinChapter + ) { + return [chapter, orderWithinChapter, []] + } + const filesToUpdate: ReorderableItem[] = [files[index]] while (files[++index].chapter === chapter) { filesToUpdate.push(files[index]) @@ -137,16 +150,19 @@ export const sortedFilesInChapter = ( .filter((file) => file.chapter === chapter) .map((file) => { return { - id: file.id, - displayText: file.name, isDivider: false, isHeading: false, - created: file.created, chapter: file.chapter, orderWithinChapter: file.orderWithinChapter, + id: file.id, + category: file.category, + created: file.created, + displayText: file.name, userGeneratedFilename: file.userGeneratedFilename, displayDate: file.displayDate, canOpen: Boolean(file.key), + status: 'done' as UploadFileStatus, + canEdit: true, } }) .sort((a, b) => { @@ -173,39 +189,12 @@ const renderChapter = (chapter: number, name?: string | null) => ( ) const CaseFile: FC = (props) => { - const { caseFile, onReorder, onOpen, onRename, onDelete } = props - const { formatMessage } = useIntl() + const { caseFile, onReorder, onOpen, onRename, onDelete, setEditCount } = + props const y = useMotionValue(0) const boxShadow = useRaisedShadow(y) const controls = useDragControls() const [isDragging, setIsDragging] = useState(false) - const [isEditing, setIsEditing] = useState(false) - const [editedFilename, setEditedFilename] = useState< - string | undefined | null - >(caseFile.userGeneratedFilename) - const [ref, { width }] = useMeasure() - - const [editedDisplayDate, setEditedDisplayDate] = useState< - string | undefined - >(formatDate(caseFile.displayDate) ?? undefined) - const displayName = caseFile.userGeneratedFilename ?? caseFile.displayText - - const handleEditFileButtonClick = () => { - const trimmedFilename = editedFilename?.trim() - const trimmedDisplayDate = editedDisplayDate?.trim() - - if (trimmedFilename || trimmedDisplayDate) { - onRename(caseFile.id, trimmedFilename, trimmedDisplayDate) - setIsEditing(false) - setEditedDisplayDate(formatDate(caseFile.displayDate) ?? '') - } - - setIsEditing(false) - } - - const displayDate = useMemo(() => { - return formatDate(caseFile.displayDate ?? caseFile.created) - }, [caseFile.displayDate, caseFile.created]) const getCursorStyle = () => { if (caseFile.isDivider || caseFile.isHeading) { @@ -262,132 +251,15 @@ const CaseFile: FC = (props) => { {caseFile.displayText?.split('|')[1]} ) : ( -
- - - - - - {isEditing ? ( - - - - - - setEditedFilename(evt.target.value) - } - /> - - - { - setEditedDisplayDate(evt.target.value) - }} - > - - - - - - - - - - - - - ) : ( - - { - if (caseFile.canOpen && caseFile.id) { - onOpen(caseFile.id) - } - }} - > - - - {displayName} - - - {caseFile.canOpen && ( - - - - )} - - - - {displayDate} - - - - - )} - - -
+ setEditCount((count) => count + 1)} + onStopEditing={() => setEditCount((count) => count - 1)} + /> )} ) @@ -401,13 +273,14 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { shouldStartExpanded, subtypes, crimeScenes, + setEditCount, } = props const { formatMessage } = useIntl() const [updateFilesMutation] = useUpdateFilesMutation() - - const { onOpen, fileNotFound, dismissFileNotFound } = useFileList({ caseId }) + const { onOpen, fileNotFound, dismissFileNotFound } = useFileList({ + caseId, + }) const { handleRemove } = useS3Upload(caseId) - const [reorderableItems, setReorderableItems] = useState( [], ) @@ -417,7 +290,7 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { ...sortedFilesInChapter(0, caseFiles), { id: uuid(), - displayText: formatMessage(m.chapterInvesitgationProcess), + displayText: formatMessage(strings.chapterInvesitgationProcess), chapter: 1, isHeading: true, isDivider: false, @@ -425,7 +298,7 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { ...sortedFilesInChapter(1, caseFiles), { id: uuid(), - displayText: formatMessage(m.chapterWitnesses), + displayText: formatMessage(strings.chapterWitnesses), chapter: 2, isHeading: true, isDivider: false, @@ -433,7 +306,7 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { ...sortedFilesInChapter(2, caseFiles), { id: uuid(), - displayText: formatMessage(m.chapterDefendant), + displayText: formatMessage(strings.chapterDefendant), chapter: 3, isHeading: true, isDivider: false, @@ -441,7 +314,7 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { ...sortedFilesInChapter(3, caseFiles), { id: uuid(), - displayText: formatMessage(m.chapterCaseFiles), + displayText: formatMessage(strings.chapterCaseFiles), chapter: 4, isHeading: true, isDivider: false, @@ -449,7 +322,7 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { ...sortedFilesInChapter(4, caseFiles), { id: uuid(), - displayText: formatMessage(m.chapterElectronicDocuments), + displayText: formatMessage(strings.chapterElectronicDocuments), chapter: 5, isHeading: true, isDivider: false, @@ -457,9 +330,9 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { ...sortedFilesInChapter(5, caseFiles), { id: uuid(), - displayText: `${formatMessage(m.unorderedFilesTitle)}|${formatMessage( - m.unorderedFilesExplanation, - )}`, + displayText: `${formatMessage( + strings.unorderedFilesTitle, + )}|${formatMessage(strings.unorderedFilesExplanation)}`, isHeading: false, isDivider: true, }, @@ -470,14 +343,17 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { ) .map((caseFile) => { return { + isDivider: false, + isHeading: false, id: caseFile.id, + category: caseFile.category, created: caseFile.created, displayText: caseFile.name, userGeneratedFilename: caseFile.userGeneratedFilename, - isDivider: false, - isHeading: false, - canOpen: Boolean(caseFile.key), displayDate: caseFile.displayDate, + canOpen: Boolean(caseFile.key), + status: 'done' as UploadFileStatus, + canEdit: true, } }), ]) @@ -493,6 +369,10 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { reorderableItems, ) + if (filesToUpdate.length === 0) { + return + } + const { errors } = await updateFilesMutation({ variables: { input: { @@ -516,45 +396,27 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { }) if (errors) { - toast.error(formatMessage(m.reorderFailedErrorMessage)) + toast.error(formatMessage(strings.reorderFailedErrorMessage)) } } const handleRename = async ( fileId: string, - newName?: string, - newDisplayDate?: string, + newName: string, + newDisplayDate: string, ) => { - let newDate: Date | null = null - const fileInReorderableItems = reorderableItems.findIndex( - (item) => item.id === fileId, + setReorderableItems((prev) => + prev.map((item) => + item.id === fileId + ? { + ...item, + userGeneratedFilename: newName, + displayDate: newDisplayDate, + } + : item, + ), ) - if (fileInReorderableItems === -1) { - return - } - - if (newDisplayDate) { - const [day, month, year] = newDisplayDate.split('.') - newDate = parseISO(`${year}-${month}-${day}`) - - if (!isValid(newDate)) { - toast.error(formatMessage(m.invalidDateErrorMessage)) - return - } - } - - setReorderableItems((prev) => { - const newReorderableItems = [...prev] - newReorderableItems[fileInReorderableItems].userGeneratedFilename = - newName - newReorderableItems[fileInReorderableItems].displayDate = newDate - ? newDate.toISOString() - : newReorderableItems[fileInReorderableItems].displayDate - - return newReorderableItems - }) - const { errors } = await updateFilesMutation({ variables: { input: { @@ -563,7 +425,7 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { { id: fileId, userGeneratedFilename: newName, - ...(newDate && { displayDate: newDate.toISOString() }), + displayDate: newDisplayDate, }, ], }, @@ -571,12 +433,12 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { }) if (errors) { - toast.error(formatMessage(m.renameFailedErrorMessage)) + toast.error(formatMessage(strings.renameFailedErrorMessage)) } } - const handleDelete = (fileId: string) => { - handleRemove({ id: fileId } as TUploadFile, (file: TUploadFile) => + const handleDelete = (file: TUploadFile) => { + handleRemove({ id: file.id } as TUploadFile, (file) => setReorderableItems((prev) => prev.filter((item) => item.id !== file.id)), ) } @@ -585,7 +447,7 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { <> = (props) => { /> - {formatMessage(m.explanation)} + {formatMessage(strings.explanation)} {/* Render the first chapter here, outside the reorder group because @@ -608,7 +470,7 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { {renderChapter( 0, - formatMessage(m.chapterIndictmentAndAccompanyingDocuments), + formatMessage(strings.chapterIndictmentAndAccompanyingDocuments), )} = (props) => { onOpen={onOpen} onRename={handleRename} onDelete={handleDelete} + setEditCount={setEditCount} /> ) @@ -641,7 +504,7 @@ const IndictmentsCaseFilesAccordionItem: FC = (props) => { > )} diff --git a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsLawsBrokenAccordionItem/IndictmentsLawsBrokenAccordionItem.tsx b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsLawsBrokenAccordionItem/IndictmentsLawsBrokenAccordionItem.tsx index c73d7614713e..eeb6171448c9 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/IndictmentsLawsBrokenAccordionItem/IndictmentsLawsBrokenAccordionItem.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/IndictmentsLawsBrokenAccordionItem/IndictmentsLawsBrokenAccordionItem.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Accordion, AccordionItem, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/AccordionItems/PoliceRequestAccordionItem/PoliceRequestAccordionItem.tsx b/apps/judicial-system/web/src/components/AccordionItems/PoliceRequestAccordionItem/PoliceRequestAccordionItem.tsx index 0f11e927ea77..a569883fca4f 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/PoliceRequestAccordionItem/PoliceRequestAccordionItem.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/PoliceRequestAccordionItem/PoliceRequestAccordionItem.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AccordionItem, Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/AccordionItems/RulingAccordionItem/RulingAccordionItem.tsx b/apps/judicial-system/web/src/components/AccordionItems/RulingAccordionItem/RulingAccordionItem.tsx index 6aa05705adce..7cb74f8274c6 100644 --- a/apps/judicial-system/web/src/components/AccordionItems/RulingAccordionItem/RulingAccordionItem.tsx +++ b/apps/judicial-system/web/src/components/AccordionItems/RulingAccordionItem/RulingAccordionItem.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AccordionItem, Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/AccordionListItem/AccordionListItem.tsx b/apps/judicial-system/web/src/components/AccordionListItem/AccordionListItem.tsx index 4587a8b0c50e..675f50976071 100644 --- a/apps/judicial-system/web/src/components/AccordionListItem/AccordionListItem.tsx +++ b/apps/judicial-system/web/src/components/AccordionListItem/AccordionListItem.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren } from 'react' +import { FC, PropsWithChildren } from 'react' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/AppealCaseFilesOverview/AppealCaseFilesOverview.spec.tsx b/apps/judicial-system/web/src/components/AppealCaseFilesOverview/AppealCaseFilesOverview.spec.tsx index 7706abe7bc19..62c15a47e7c7 100644 --- a/apps/judicial-system/web/src/components/AppealCaseFilesOverview/AppealCaseFilesOverview.spec.tsx +++ b/apps/judicial-system/web/src/components/AppealCaseFilesOverview/AppealCaseFilesOverview.spec.tsx @@ -1,8 +1,6 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import React from 'react' import { IntlProvider } from 'react-intl' import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client' -import { render, screen, within } from '@testing-library/react' +import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { diff --git a/apps/judicial-system/web/src/components/AppealCaseFilesOverview/AppealCaseFilesOverview.tsx b/apps/judicial-system/web/src/components/AppealCaseFilesOverview/AppealCaseFilesOverview.tsx index 893729b7810c..843a2bfdb20f 100644 --- a/apps/judicial-system/web/src/components/AppealCaseFilesOverview/AppealCaseFilesOverview.tsx +++ b/apps/judicial-system/web/src/components/AppealCaseFilesOverview/AppealCaseFilesOverview.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react' +import { useContext, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' import router from 'next/router' diff --git a/apps/judicial-system/web/src/components/BigTextSmallText/BigTextSmallText.tsx b/apps/judicial-system/web/src/components/BigTextSmallText/BigTextSmallText.tsx index f45c5a6fa3f1..8011581eb4ec 100644 --- a/apps/judicial-system/web/src/components/BigTextSmallText/BigTextSmallText.tsx +++ b/apps/judicial-system/web/src/components/BigTextSmallText/BigTextSmallText.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/BlueBox/BlueBox.tsx b/apps/judicial-system/web/src/components/BlueBox/BlueBox.tsx index 3ecb77a88110..362c1c0f42b5 100644 --- a/apps/judicial-system/web/src/components/BlueBox/BlueBox.tsx +++ b/apps/judicial-system/web/src/components/BlueBox/BlueBox.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren } from 'react' +import { FC, PropsWithChildren } from 'react' import cn from 'classnames' import { TestSupport } from '@island.is/island-ui/utils' diff --git a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithIcon.tsx b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithIcon.tsx index 14505fe7f333..2bb8ff000e3d 100644 --- a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithIcon.tsx +++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithIcon.tsx @@ -1,15 +1,15 @@ -import { FC } from 'react' +import { FC, ReactNode } from 'react' import { Box, Icon, IconMapIcon, Text } from '@island.is/island-ui/core' import * as styles from './BlueBoxWithIcon.css' export interface DataSection { - data: { title: string; value?: React.ReactNode }[] + data: { title: string; value?: ReactNode }[] } interface Props { - data: { title: string; value?: React.ReactNode }[] + data: { title: string; value?: ReactNode }[] icon?: IconMapIcon } diff --git a/apps/judicial-system/web/src/components/BlueBoxWithIcon/CaseScheduledCard.tsx b/apps/judicial-system/web/src/components/BlueBoxWithIcon/CaseScheduledCard.tsx index b785c910be7f..57623d2ba20e 100644 --- a/apps/judicial-system/web/src/components/BlueBoxWithIcon/CaseScheduledCard.tsx +++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/CaseScheduledCard.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/BlueBoxWithIcon/IndictmentCaseScheduledCard.tsx b/apps/judicial-system/web/src/components/BlueBoxWithIcon/IndictmentCaseScheduledCard.tsx index 6d6a665d0d49..3cee41edd24f 100644 --- a/apps/judicial-system/web/src/components/BlueBoxWithIcon/IndictmentCaseScheduledCard.tsx +++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/IndictmentCaseScheduledCard.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/CaseDates/CaseDates.spec.tsx b/apps/judicial-system/web/src/components/CaseDates/CaseDates.spec.tsx index bc8e3f583352..caa9d652ce18 100644 --- a/apps/judicial-system/web/src/components/CaseDates/CaseDates.spec.tsx +++ b/apps/judicial-system/web/src/components/CaseDates/CaseDates.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { IntlProvider } from 'react-intl' import { render, screen } from '@testing-library/react' diff --git a/apps/judicial-system/web/src/components/CaseDates/CaseDates.tsx b/apps/judicial-system/web/src/components/CaseDates/CaseDates.tsx index 7c129c9c2deb..c482607142c6 100644 --- a/apps/judicial-system/web/src/components/CaseDates/CaseDates.tsx +++ b/apps/judicial-system/web/src/components/CaseDates/CaseDates.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Box, Button, IconMapIcon, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/CaseFile/CaseFile.tsx b/apps/judicial-system/web/src/components/CaseFile/CaseFile.tsx index 8bf43649ce4c..f6447323b82d 100644 --- a/apps/judicial-system/web/src/components/CaseFile/CaseFile.tsx +++ b/apps/judicial-system/web/src/components/CaseFile/CaseFile.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import cn from 'classnames' import { IconMapIcon } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/CaseFileList/CaseFileList.spec.tsx b/apps/judicial-system/web/src/components/CaseFileList/CaseFileList.spec.tsx index ed642f33188c..a8efaf42faa9 100644 --- a/apps/judicial-system/web/src/components/CaseFileList/CaseFileList.spec.tsx +++ b/apps/judicial-system/web/src/components/CaseFileList/CaseFileList.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { MockedProvider } from '@apollo/client/testing' import { render, screen } from '@testing-library/react' diff --git a/apps/judicial-system/web/src/components/CaseFileList/CaseFileList.tsx b/apps/judicial-system/web/src/components/CaseFileList/CaseFileList.tsx index 975d827db16c..62f36e3935c8 100644 --- a/apps/judicial-system/web/src/components/CaseFileList/CaseFileList.tsx +++ b/apps/judicial-system/web/src/components/CaseFileList/CaseFileList.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' diff --git a/apps/judicial-system/web/src/components/CaseInfo/CaseInfo.tsx b/apps/judicial-system/web/src/components/CaseInfo/CaseInfo.tsx index 2973c84db8f8..d36978236b62 100644 --- a/apps/judicial-system/web/src/components/CaseInfo/CaseInfo.tsx +++ b/apps/judicial-system/web/src/components/CaseInfo/CaseInfo.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { IntlShape, useIntl } from 'react-intl' import flatMap from 'lodash/flatMap' diff --git a/apps/judicial-system/web/src/components/CaseResentExplanation/CaseResentExplanation.tsx b/apps/judicial-system/web/src/components/CaseResentExplanation/CaseResentExplanation.tsx index ecc58f20534e..91d421ed44cd 100644 --- a/apps/judicial-system/web/src/components/CaseResentExplanation/CaseResentExplanation.tsx +++ b/apps/judicial-system/web/src/components/CaseResentExplanation/CaseResentExplanation.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AlertMessage } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/CaseResubmitModal/CaseResubmitModal.tsx b/apps/judicial-system/web/src/components/CaseResubmitModal/CaseResubmitModal.tsx index 3098854ea785..4e3e1b74d16f 100644 --- a/apps/judicial-system/web/src/components/CaseResubmitModal/CaseResubmitModal.tsx +++ b/apps/judicial-system/web/src/components/CaseResubmitModal/CaseResubmitModal.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from 'react' +import { FC, useState } from 'react' import { IntlShape, useIntl } from 'react-intl' import { Box, Input } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/CaseTitleInfoAndTags/CaseTitleInfoAndTags.tsx b/apps/judicial-system/web/src/components/CaseTitleInfoAndTags/CaseTitleInfoAndTags.tsx index 25d4cddba584..1ed0759b7ba3 100644 --- a/apps/judicial-system/web/src/components/CaseTitleInfoAndTags/CaseTitleInfoAndTags.tsx +++ b/apps/judicial-system/web/src/components/CaseTitleInfoAndTags/CaseTitleInfoAndTags.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/CheckboxList/CheckboxList.tsx b/apps/judicial-system/web/src/components/CheckboxList/CheckboxList.tsx index 6dc61c5a3595..842d9717faaf 100644 --- a/apps/judicial-system/web/src/components/CheckboxList/CheckboxList.tsx +++ b/apps/judicial-system/web/src/components/CheckboxList/CheckboxList.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { MessageDescriptor, useIntl } from 'react-intl' import { diff --git a/apps/judicial-system/web/src/components/CommentsInput/CommentsInput.tsx b/apps/judicial-system/web/src/components/CommentsInput/CommentsInput.tsx index dd6d0132b59b..9ef10f22aeae 100644 --- a/apps/judicial-system/web/src/components/CommentsInput/CommentsInput.tsx +++ b/apps/judicial-system/web/src/components/CommentsInput/CommentsInput.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FC, SetStateAction } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { useIntl } from 'react-intl' import { Box, Input, Text, Tooltip } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/components/Conclusion/Conclusion.tsx index 81e43eafa628..7ae86f9ced02 100644 --- a/apps/judicial-system/web/src/components/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/components/Conclusion/Conclusion.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/ConclusionDraft/ConclusionDraft.tsx b/apps/judicial-system/web/src/components/ConclusionDraft/ConclusionDraft.tsx index 6468484af796..85a9d6610b27 100644 --- a/apps/judicial-system/web/src/components/ConclusionDraft/ConclusionDraft.tsx +++ b/apps/judicial-system/web/src/components/ConclusionDraft/ConclusionDraft.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' @@ -14,7 +14,7 @@ import { useCase } from '../../utils/hooks' interface Props { workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> } const ConclusionDraft: FC = ({ workingCase, setWorkingCase }) => { diff --git a/apps/judicial-system/web/src/components/ContextMenu/ContextMenu.spec.tsx b/apps/judicial-system/web/src/components/ContextMenu/ContextMenu.spec.tsx index 9a356a75dafa..17c3b9c80bf1 100644 --- a/apps/judicial-system/web/src/components/ContextMenu/ContextMenu.spec.tsx +++ b/apps/judicial-system/web/src/components/ContextMenu/ContextMenu.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { fireEvent, render, screen } from '@testing-library/react' import ContextMenu from '../ContextMenu/ContextMenu' diff --git a/apps/judicial-system/web/src/components/ContextMenu/ContextMenu.tsx b/apps/judicial-system/web/src/components/ContextMenu/ContextMenu.tsx index b090d518b5c7..93efd303c989 100644 --- a/apps/judicial-system/web/src/components/ContextMenu/ContextMenu.tsx +++ b/apps/judicial-system/web/src/components/ContextMenu/ContextMenu.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, ReactElement } from 'react' +import { cloneElement, forwardRef, ReactElement } from 'react' import { useIntl } from 'react-intl' import cn from 'classnames' import { Menu, MenuButton, MenuItem, useMenuState } from 'reakit/Menu' @@ -98,9 +98,7 @@ const ContextMenu = forwardRef( {...disclosure.props} dataTestId={dataTestId} > - {(disclosureProps) => - React.cloneElement(disclosure, disclosureProps) - } + {(disclosureProps) => cloneElement(disclosure, disclosureProps)} ) : ( diff --git a/apps/judicial-system/web/src/components/ContextMenu/ContextMenuOptions/WithdrawAppealMenuOption.tsx b/apps/judicial-system/web/src/components/ContextMenu/ContextMenuOptions/WithdrawAppealMenuOption.tsx index ed44b1d5279f..541d1dddf7a4 100644 --- a/apps/judicial-system/web/src/components/ContextMenu/ContextMenuOptions/WithdrawAppealMenuOption.tsx +++ b/apps/judicial-system/web/src/components/ContextMenu/ContextMenuOptions/WithdrawAppealMenuOption.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useContext, useState } from 'react' +import { FC, useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { IconMapIcon } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/CourtArrangements/CourtArrangements.tsx b/apps/judicial-system/web/src/components/CourtArrangements/CourtArrangements.tsx index 713b7f0f548f..2e02d96fbad2 100644 --- a/apps/judicial-system/web/src/components/CourtArrangements/CourtArrangements.tsx +++ b/apps/judicial-system/web/src/components/CourtArrangements/CourtArrangements.tsx @@ -1,4 +1,4 @@ -import React, { FC, SetStateAction, useEffect, useState } from 'react' +import { FC, SetStateAction, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import compareAsc from 'date-fns/compareAsc' diff --git a/apps/judicial-system/web/src/components/CourtDocuments/CourtDocuments.tsx b/apps/judicial-system/web/src/components/CourtDocuments/CourtDocuments.tsx index 6b3e782e60c6..4fb1f172cb6a 100644 --- a/apps/judicial-system/web/src/components/CourtDocuments/CourtDocuments.tsx +++ b/apps/judicial-system/web/src/components/CourtDocuments/CourtDocuments.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FC, useState } from 'react' +import { Dispatch, FC, SetStateAction, useState } from 'react' import { useIntl } from 'react-intl' import Select, { ClearIndicatorProps, @@ -27,7 +27,7 @@ import * as styles from './CourtDocuments.css' interface Props { workingCase: Case - setWorkingCase: Dispatch> + setWorkingCase: Dispatch> } const CourtDocuments: FC = ({ workingCase, setWorkingCase }) => { diff --git a/apps/judicial-system/web/src/components/DateTime/DateTime.spec.tsx b/apps/judicial-system/web/src/components/DateTime/DateTime.spec.tsx index 8e7438d3698b..3517db2e6825 100644 --- a/apps/judicial-system/web/src/components/DateTime/DateTime.spec.tsx +++ b/apps/judicial-system/web/src/components/DateTime/DateTime.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { IntlProvider } from 'react-intl' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' diff --git a/apps/judicial-system/web/src/components/DateTime/DateTime.tsx b/apps/judicial-system/web/src/components/DateTime/DateTime.tsx index 51fc33193852..3c6fb416ce9f 100644 --- a/apps/judicial-system/web/src/components/DateTime/DateTime.tsx +++ b/apps/judicial-system/web/src/components/DateTime/DateTime.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useState } from 'react' +import { ChangeEvent, FC, FocusEvent, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { DatePicker, Input } from '@island.is/island-ui/core' @@ -117,7 +117,7 @@ const DateTime: FC = ({ ) } - const onTimeChange = (event: React.ChangeEvent) => { + const onTimeChange = (event: ChangeEvent) => { const newTime = event.target.value setCurrentTime(newTime) @@ -133,7 +133,7 @@ const DateTime: FC = ({ sendToParent(currentDate, newTime) } - const onTimeBlur = (event: React.FocusEvent) => { + const onTimeBlur = (event: FocusEvent) => { const time = event.target.value const validations: Validation[] = ['empty', 'time-format'] diff --git a/apps/judicial-system/web/src/components/Decision/Decision.tsx b/apps/judicial-system/web/src/components/Decision/Decision.tsx index 462cceedc652..52b7c992d278 100644 --- a/apps/judicial-system/web/src/components/Decision/Decision.tsx +++ b/apps/judicial-system/web/src/components/Decision/Decision.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from 'react' +import { FC, useState } from 'react' import { Box, RadioButton } from '@island.is/island-ui/core' import { diff --git a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInfo.tsx b/apps/judicial-system/web/src/components/DefenderInfo/DefenderInfo.tsx index e7173344dac0..4b66b1f1b215 100644 --- a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInfo.tsx +++ b/apps/judicial-system/web/src/components/DefenderInfo/DefenderInfo.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useState } from 'react' +import { Dispatch, FC, SetStateAction, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { Box, RadioButton, Text, Tooltip } from '@island.is/island-ui/core' @@ -24,7 +24,7 @@ import { defenderInfo } from './DefenderInfo.strings' interface Props { workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> } const DefenderInfo: FC = ({ workingCase, setWorkingCase }) => { diff --git a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.tsx b/apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.tsx index a4d29662e43f..bb0b879fc0bc 100644 --- a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.tsx +++ b/apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.tsx @@ -1,4 +1,5 @@ -import React, { +import { + Dispatch, FC, SetStateAction, useCallback, @@ -43,7 +44,7 @@ interface PropertyValidation { validations: Validation[] errorMessageHandler: { errorMessage: string - setErrorMessage: React.Dispatch> + setErrorMessage: Dispatch> } } @@ -167,7 +168,7 @@ const DefenderInput: FC = ({ defendantId: string, property: InputType, value: string, - setWorkingCase: React.Dispatch>, + setWorkingCase: Dispatch>, ) => { let newValue = value const propertyValidation = propertyValidations(property) diff --git a/apps/judicial-system/web/src/components/DefenderInfo/DefenderNotFound.tsx b/apps/judicial-system/web/src/components/DefenderInfo/DefenderNotFound.tsx index fcff56632090..63bd28f2ab6f 100644 --- a/apps/judicial-system/web/src/components/DefenderInfo/DefenderNotFound.tsx +++ b/apps/judicial-system/web/src/components/DefenderInfo/DefenderNotFound.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { useIntl } from 'react-intl' import { AlertMessage, Box } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/EditableCaseFile/EditableCaseFile.css.ts b/apps/judicial-system/web/src/components/EditableCaseFile/EditableCaseFile.css.ts new file mode 100644 index 000000000000..e95d143b936d --- /dev/null +++ b/apps/judicial-system/web/src/components/EditableCaseFile/EditableCaseFile.css.ts @@ -0,0 +1,78 @@ +import { style, styleVariants } from '@vanilla-extract/css' + +import { theme } from '@island.is/island-ui/theme' + +export const caseFileWrapper = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'stretch', + paddingRight: `${theme.spacing[2]}px`, + paddingTop: `${theme.spacing[1]}px`, + paddingBottom: `${theme.spacing[1]}px`, + minHeight: '64px', + borderRadius: theme.border.radius.large, + border: '1px solid', + transition: 'background-color 0.2s ease-in-out', +}) + +export const caseFileWrapperStates = styleVariants({ + error: { + background: theme.color.red100, + borderColor: 'transparent', + }, + done: { + background: theme.color.blue100, + borderColor: 'transparent', + }, + uploading: { + background: 'transparent', + borderColor: theme.color.blue200, + }, +}) + +export const editCaseFileInputContainer = style({ + display: 'flex', + flexDirection: 'row', + flexGrow: 1, + marginRight: theme.spacing[2], +}) + +export const editCaseFileName = style({ + flex: 2, + marginRight: theme.spacing[1], +}) + +export const editCaseFileDisplayDate = style({ + flex: 1, +}) + +export const editCaseFileButton = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '32px', + height: '34px', + padding: '4px', + borderRadius: theme.border.radius.large, + transition: 'background-color 0.2s ease-in-out', + cursor: 'default', +}) + +export const background = styleVariants({ + primary: { + selectors: { + '&:hover': { + backgroundColor: theme.color.blue200, + cursor: 'pointer', + }, + }, + }, + secondary: { + selectors: { + '&:hover': { + backgroundColor: theme.color.red200, + cursor: 'pointer', + }, + }, + }, +}) diff --git a/apps/judicial-system/web/src/components/EditableCaseFile/EditableCaseFile.strings.ts b/apps/judicial-system/web/src/components/EditableCaseFile/EditableCaseFile.strings.ts new file mode 100644 index 000000000000..19d68eb2b320 --- /dev/null +++ b/apps/judicial-system/web/src/components/EditableCaseFile/EditableCaseFile.strings.ts @@ -0,0 +1,23 @@ +import { defineMessage } from 'react-intl' + +export const strings = { + simpleInputPlaceholder: defineMessage({ + id: 'judicial.system.core:editable_case_file.simple_input_placeholder', + defaultMessage: 'Skráðu inn heiti á skjali', + description: + 'Notaður sem skýritexti í textasvæði reit til að breyta heiti skjals.', + }), + invalidFilenameErrorMessage: { + id: 'judicial.system.core:editable_case_file.invalid_filename_error_message', + defaultMessage: 'Ekki tókst að uppfæra skjal, heiti er tómt', + description: + 'Notaður sem villuboð þegar tekst ekki að uppfæra heiti á skjali.', + }, + invalidDateErrorMessage: { + id: 'judicial.system.core:editable_case_file.invalid_date_error_message', + defaultMessage: + 'Ekki tókst að uppfæra skjal, dagsetning er ekki á réttu formi', + description: + 'Notaður sem villuboð þegar tekst ekki að uppfæra dagsetningu á skjali.', + }, +} diff --git a/apps/judicial-system/web/src/components/EditableCaseFile/EditableCaseFile.tsx b/apps/judicial-system/web/src/components/EditableCaseFile/EditableCaseFile.tsx new file mode 100644 index 000000000000..bc8642ef40a0 --- /dev/null +++ b/apps/judicial-system/web/src/components/EditableCaseFile/EditableCaseFile.tsx @@ -0,0 +1,295 @@ +import { FC, useMemo, useState } from 'react' +import InputMask from 'react-input-mask' +import { useIntl } from 'react-intl' +import { useMeasure } from 'react-use' +import cn from 'classnames' +import isValid from 'date-fns/isValid' +import parseISO from 'date-fns/parseISO' +import { AnimatePresence, motion } from 'framer-motion' + +import { + Box, + Icon, + Input, + LoadingDots, + Text, + toast, + UploadFile, + UploadFileStatus, +} from '@island.is/island-ui/core' +import { formatDate } from '@island.is/judicial-system/formatters' + +import { CaseFileCategory } from '../../graphql/schema' +import { TUploadFile } from '../../utils/hooks' +import { strings } from './EditableCaseFile.strings' +import * as styles from './EditableCaseFile.css' + +export interface TEditableCaseFile { + id: string + category?: CaseFileCategory | null + created?: string | null + displayText?: string | null + userGeneratedFilename?: string | null + displayDate?: string | null + canOpen?: boolean + canEdit?: boolean + status?: UploadFileStatus +} + +interface Props { + enableDrag: boolean + caseFile: TEditableCaseFile + onOpen: (id: string) => void + onRename: (id: string, name: string, displayDate: string) => void + onDelete: (file: TUploadFile) => void + onRetry?: (file: TUploadFile) => void + onStartEditing: () => void + onStopEditing: () => void +} + +const EditableCaseFile: FC = (props) => { + const { + caseFile, + enableDrag, + onOpen, + onRename, + onDelete, + onRetry, + onStartEditing, + onStopEditing, + } = props + const { formatMessage } = useIntl() + const [ref, { width }] = useMeasure() + const [isEditing, setIsEditing] = useState(false) + + const [editedFilename, setEditedFilename] = useState< + string | undefined | null + >(caseFile.userGeneratedFilename) + + const [editedDisplayDate, setEditedDisplayDate] = useState< + string | undefined + >(formatDate(caseFile.displayDate ?? caseFile.created) ?? undefined) + const displayName = caseFile.userGeneratedFilename ?? caseFile.displayText + + const handleEditFileButtonClick = () => { + const trimmedFilename = editedFilename?.trim() + const trimmedDisplayDate = editedDisplayDate?.trim() + + if (trimmedFilename === undefined || trimmedFilename.length === 0) { + toast.error(formatMessage(strings.invalidFilenameErrorMessage)) + return + } + + let newDate: Date | undefined + + if (trimmedDisplayDate) { + const [day, month, year] = trimmedDisplayDate.split('.') + newDate = parseISO(`${year}-${month}-${day}`) + } + + if (!newDate || !isValid(newDate)) { + toast.error(formatMessage(strings.invalidDateErrorMessage)) + return + } + + onRename(caseFile.id, trimmedFilename, newDate.toISOString()) + + setIsEditing(false) + onStopEditing() + } + + const displayDate = useMemo(() => { + return formatDate(caseFile.displayDate ?? caseFile.created) + }, [caseFile.displayDate, caseFile.created]) + + return ( +
+ {enableDrag && ( + + + + )} + + + {isEditing ? ( + + + + + setEditedFilename(evt.target.value)} + /> + + + { + setEditedDisplayDate(evt.target.value) + }} + > + + + + + + + + + + + + + ) : ( + + { + if (caseFile.canOpen && caseFile.id) { + onOpen(caseFile.id) + } + }} + > + + + {displayName} + + + {caseFile.canOpen && ( + + + + )} + + + + {displayDate} + + + {caseFile.status === 'uploading' ? ( + + + + ) : caseFile.status === 'error' && onRetry ? ( + + ) : ( + + )} + + + )} + + +
+ ) +} + +export default EditableCaseFile diff --git a/apps/judicial-system/web/src/components/FeatureProvider/FeatureProvider.tsx b/apps/judicial-system/web/src/components/FeatureProvider/FeatureProvider.tsx index eab320d35b0c..1d6bc51a718c 100644 --- a/apps/judicial-system/web/src/components/FeatureProvider/FeatureProvider.tsx +++ b/apps/judicial-system/web/src/components/FeatureProvider/FeatureProvider.tsx @@ -1,4 +1,4 @@ -import React, { +import { createContext, FC, PropsWithChildren, diff --git a/apps/judicial-system/web/src/components/FileNotFoundModal/FileNotFoundModal.tsx b/apps/judicial-system/web/src/components/FileNotFoundModal/FileNotFoundModal.tsx index 9f76a2abbdaf..8a94c61b8cd1 100644 --- a/apps/judicial-system/web/src/components/FileNotFoundModal/FileNotFoundModal.tsx +++ b/apps/judicial-system/web/src/components/FileNotFoundModal/FileNotFoundModal.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Modal } from '@island.is/judicial-system-web/src/components' diff --git a/apps/judicial-system/web/src/components/FormContentContainer/FormContentContainer.tsx b/apps/judicial-system/web/src/components/FormContentContainer/FormContentContainer.tsx index e5a04be58286..4febcea7f59b 100644 --- a/apps/judicial-system/web/src/components/FormContentContainer/FormContentContainer.tsx +++ b/apps/judicial-system/web/src/components/FormContentContainer/FormContentContainer.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren } from 'react' +import { FC, PropsWithChildren } from 'react' import { Box } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/FormFooter/FormFooter.tsx b/apps/judicial-system/web/src/components/FormFooter/FormFooter.tsx index 86da4c6a855e..de5c61141208 100644 --- a/apps/judicial-system/web/src/components/FormFooter/FormFooter.tsx +++ b/apps/judicial-system/web/src/components/FormFooter/FormFooter.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { useWindowSize } from 'react-use' import cn from 'classnames' diff --git a/apps/judicial-system/web/src/components/FormProvider/FormProvider.tsx b/apps/judicial-system/web/src/components/FormProvider/FormProvider.tsx index d52ce2e8ec6f..dcf155032e9d 100644 --- a/apps/judicial-system/web/src/components/FormProvider/FormProvider.tsx +++ b/apps/judicial-system/web/src/components/FormProvider/FormProvider.tsx @@ -1,6 +1,8 @@ -import React, { +import { createContext, + Dispatch, ReactNode, + SetStateAction, useCallback, useContext, useEffect, @@ -35,7 +37,7 @@ type ProviderState = interface FormProvider { workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> isLoadingWorkingCase: boolean caseNotFound: boolean isCaseUpToDate: boolean diff --git a/apps/judicial-system/web/src/components/FormProvider/case.graphql b/apps/judicial-system/web/src/components/FormProvider/case.graphql index 2aff01484b69..ccededfe5708 100644 --- a/apps/judicial-system/web/src/components/FormProvider/case.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/case.graphql @@ -171,6 +171,7 @@ query Case($input: CaseQueryInput!) { userGeneratedFilename displayDate policeFileId + submittedBy } isAppealDeadlineExpired isAppealGracePeriodExpired diff --git a/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql b/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql index 39b75cd6e736..bf9cadbb55d0 100644 --- a/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql @@ -14,6 +14,9 @@ query LimitedAccessCase($input: CaseQueryInput!) { category key policeCaseNumber + userGeneratedFilename + displayDate + submittedBy } defendants { id diff --git a/apps/judicial-system/web/src/components/Header/Header.tsx b/apps/judicial-system/web/src/components/Header/Header.tsx index a43d8b6dfc5e..d2289ad8942a 100644 --- a/apps/judicial-system/web/src/components/Header/Header.tsx +++ b/apps/judicial-system/web/src/components/Header/Header.tsx @@ -1,10 +1,4 @@ -import React, { - FC, - PropsWithChildren, - useContext, - useEffect, - useState, -} from 'react' +import { FC, PropsWithChildren, useContext, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import getConfig from 'next/config' import Link from 'next/link' diff --git a/apps/judicial-system/web/src/components/HideableText/HideableText.tsx b/apps/judicial-system/web/src/components/HideableText/HideableText.tsx index 14291297c412..9e07f195b551 100644 --- a/apps/judicial-system/web/src/components/HideableText/HideableText.tsx +++ b/apps/judicial-system/web/src/components/HideableText/HideableText.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { AnimatePresence, motion } from 'framer-motion' import { Icon, Text, Tooltip } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/IconAndText/IconAndText.tsx b/apps/judicial-system/web/src/components/IconAndText/IconAndText.tsx index c33ae8a068ef..f617ddbc0700 100644 --- a/apps/judicial-system/web/src/components/IconAndText/IconAndText.tsx +++ b/apps/judicial-system/web/src/components/IconAndText/IconAndText.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { Box, Icon, IconMapIcon, Text } from '@island.is/island-ui/core' import { Colors } from '@island.is/island-ui/theme' diff --git a/apps/judicial-system/web/src/components/IconButton/IconButton.tsx b/apps/judicial-system/web/src/components/IconButton/IconButton.tsx index fbb6023dfacf..de9344b94464 100644 --- a/apps/judicial-system/web/src/components/IconButton/IconButton.tsx +++ b/apps/judicial-system/web/src/components/IconButton/IconButton.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef } from 'react' +import { forwardRef, MouseEvent } from 'react' import cn from 'classnames' import { Button } from 'reakit' @@ -9,7 +9,7 @@ import * as styles from './IconButton.css' interface Props { icon: IconMapIcon colorScheme: 'blue' | 'red' | 'transparent' - onClick?: (evt: React.MouseEvent) => void + onClick?: (evt: MouseEvent) => void disabled?: boolean } diff --git a/apps/judicial-system/web/src/components/Icons/Trash.tsx b/apps/judicial-system/web/src/components/Icons/Trash.tsx index 80d54d66bd16..aced519b04bd 100644 --- a/apps/judicial-system/web/src/components/Icons/Trash.tsx +++ b/apps/judicial-system/web/src/components/Icons/Trash.tsx @@ -1,6 +1,6 @@ -import * as React from 'react' +import { SVGProps } from 'react' -const Trash = ({ ...props }: React.SVGProps) => { +const Trash = ({ ...props }: SVGProps) => { return ( { + it('should render court records if there are courtRecord case files', () => { + render( + + + + + , + ) + + expect(screen.queryByTestId('PDFButton')).not.toBeNull() + }) +}) diff --git a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.strings.ts b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.strings.ts index 5760aa5c642a..134cae5bdca9 100644 --- a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.strings.ts +++ b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.strings.ts @@ -22,4 +22,10 @@ export const strings = defineMessages({ defaultMessage: 'Dómar, úrskurðir og þingbók', description: 'Notaður sem titill á dómur hluta á dómskjalaskjá í ákærum.', }, + uploadedCaseFiles: { + id: 'judicial.system.core:court.indictment_case_files_list.uploaded_case_files', + defaultMessage: 'Innsend gögn', + description: + 'Notaður sem titill á innsend gögn hluta á dómskjalaskjá í ákærum.', + }, }) diff --git a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx index 2ca7a2076909..aad0eb781b67 100644 --- a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx +++ b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' @@ -23,6 +23,7 @@ import { import { TempCase as Case } from '@island.is/judicial-system-web/src/types' import { useFileList } from '@island.is/judicial-system-web/src/utils/hooks' +import { CaseFileTable } from '../Table' import { caseFiles } from '../../routes/Prosecutor/Indictments/CaseFiles/CaseFiles.strings' import { strings } from './IndictmentCaseFilesList.strings' @@ -93,6 +94,11 @@ const IndictmentCaseFilesList: FC = ({ const criminalRecordUpdate = cf?.filter( (file) => file.category === CaseFileCategory.CRIMINAL_RECORD_UPDATE, ) + const uploadedCaseFiles = cf?.filter( + (file) => + file.category === CaseFileCategory.PROSECUTOR_CASE_FILE || + file.category === CaseFileCategory.DEFENDANT_CASE_FILE, + ) return ( <> @@ -120,7 +126,7 @@ const IndictmentCaseFilesList: FC = ({ @@ -178,7 +184,6 @@ const IndictmentCaseFilesList: FC = ({ /> )} - {formatMessage(strings.caseFileTitle)} @@ -190,15 +195,14 @@ const IndictmentCaseFilesList: FC = ({ title={formatMessage(strings.caseFileButtonText, { policeCaseNumber, })} - pdfType={'caseFilesRecord'} + pdfType="caseFilesRecord" elementId={policeCaseNumber} renderAs="row" /> ))} - {(isDistrictCourtUser(user) || isCompletedCase(workingCase.state)) && - (courtRecords?.length || rulings?.length) ? ( + {courtRecords?.length || rulings?.length ? ( {formatMessage(strings.rulingAndCourtRecordsTitle)} @@ -210,16 +214,25 @@ const IndictmentCaseFilesList: FC = ({ workingCase={workingCase} /> )} - {rulings && rulings.length > 0 && ( - - )} + {(isDistrictCourtUser(user) || isCompletedCase(workingCase.state)) && + rulings && + rulings.length > 0 && ( + + )} ) : null} - + {uploadedCaseFiles && uploadedCaseFiles.length > 0 && ( + + + {formatMessage(strings.uploadedCaseFiles)} + + + + )} {fileNotFound && } diff --git a/apps/judicial-system/web/src/components/IndictmentInfo/IndictmentInfo.tsx b/apps/judicial-system/web/src/components/IndictmentInfo/IndictmentInfo.tsx index b8867ac15a94..191c6727947f 100644 --- a/apps/judicial-system/web/src/components/IndictmentInfo/IndictmentInfo.tsx +++ b/apps/judicial-system/web/src/components/IndictmentInfo/IndictmentInfo.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/InfoBox/InfoBox.tsx b/apps/judicial-system/web/src/components/InfoBox/InfoBox.tsx index db4e1a06fff9..cee437933641 100644 --- a/apps/judicial-system/web/src/components/InfoBox/InfoBox.tsx +++ b/apps/judicial-system/web/src/components/InfoBox/InfoBox.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import cn from 'classnames' import { Box, Icon, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.spec.ts b/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.spec.ts index 0cc3f55efe69..5333f47cbe61 100644 --- a/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.spec.ts +++ b/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.spec.ts @@ -13,11 +13,28 @@ describe('DefendantInfo', () => { jest.useRealTimers() }) - test('should return the correct string if viewDate is not provided', () => { - const viewDate = undefined - const serviceRequirement = ServiceRequirement.NOT_REQUIRED + test('should return the correct string if serviceRequirement is REQUIRED and verdictAppealDeadline is not provided', () => { + const verdictAppealDeadline = undefined + const serviceRequirement = ServiceRequirement.REQUIRED - const dataSections = getAppealExpirationInfo(viewDate, serviceRequirement) + const dataSections = getAppealExpirationInfo( + verdictAppealDeadline, + serviceRequirement, + ) + + expect(dataSections.message.id).toStrictEqual( + 'judicial.system.core:info_card.defendant_info.appeal_date_not_begun', + ) + }) + + test('should return the correct string if serviceRequirement is NOT_APPLICABLE and verdictAppealDeadline is not provided', () => { + const verdictAppealDeadline = undefined + const serviceRequirement = ServiceRequirement.NOT_APPLICABLE + + const dataSections = getAppealExpirationInfo( + verdictAppealDeadline, + serviceRequirement, + ) expect(dataSections.message.id).toStrictEqual( 'judicial.system.core:info_card.defendant_info.appeal_date_not_begun', @@ -25,49 +42,77 @@ describe('DefendantInfo', () => { }) test('should return the correct string if serviceRequirement is NOT_REQUIRED', () => { - const viewDate = '2024-07-08' + const verdictAppealDeadline = undefined const serviceRequirement = ServiceRequirement.NOT_REQUIRED - const dataSections = getAppealExpirationInfo(viewDate, serviceRequirement) + const dataSections = getAppealExpirationInfo( + verdictAppealDeadline, + serviceRequirement, + ) expect(dataSections.message.id).toStrictEqual( - 'judicial.system.core:info_card.defendant_info.service_requirement_not_required', + 'judicial.system.core:info_card.defendant_info.service_requirement_not_required_v1', ) }) - test('should return the correct string if serviceRequirement is NOT_APPLICABLE', () => { - const viewDate = '2024-07-08' - const serviceRequirement = ServiceRequirement.NOT_APPLICABLE + test('should return the correct string if serviceRequirement is REQUIRED and appeal expiration date is in the future', () => { + const verdictAppealDeadline = '2024-08-05' + const serviceRequirement = ServiceRequirement.REQUIRED - const dataSections = getAppealExpirationInfo(viewDate, serviceRequirement) + const dataSections = getAppealExpirationInfo( + verdictAppealDeadline, + serviceRequirement, + ) expect(dataSections.message.id).toStrictEqual( - 'judicial.system.core:info_card.defendant_info.service_requirement_not_applicable', + 'judicial.system.core:info_card.defendant_info.appeal_expiration_date', ) + expect(dataSections.date).toStrictEqual('05.08.2024') }) - test('should return the correct string if appeal expiration date is in the future', () => { - const viewDate = '2024-07-08' - const serviceRequirement = ServiceRequirement.REQUIRED + test('should return the correct string if serviceRequirement is NOT_APPLICABLE and appeal expiration date is in the future', () => { + const verdictAppealDeadline = '2024-08-05' + const serviceRequirement = ServiceRequirement.NOT_APPLICABLE - const dataSections = getAppealExpirationInfo(viewDate, serviceRequirement) + const dataSections = getAppealExpirationInfo( + verdictAppealDeadline, + serviceRequirement, + ) expect(dataSections.message.id).toStrictEqual( 'judicial.system.core:info_card.defendant_info.appeal_expiration_date', ) - expect(dataSections.data).toStrictEqual('05.08.2024') + expect(dataSections.date).toStrictEqual('05.08.2024') }) - test('should return the correct string if appeal expiration date is in the past', () => { - const viewDate = '2024-06-09' + test('should return the correct string if serviceRequirement is REQUIRED and appeal expiration date is in the past', () => { + const verdictAppealDeadline = '2024-07-07' const serviceRequirement = ServiceRequirement.REQUIRED - const dataSections = getAppealExpirationInfo(viewDate, serviceRequirement) + const dataSections = getAppealExpirationInfo( + verdictAppealDeadline, + serviceRequirement, + ) + + expect(dataSections.message.id).toStrictEqual( + 'judicial.system.core:info_card.defendant_info.appeal_date_expired', + ) + expect(dataSections.date).toStrictEqual('07.07.2024') + }) + + test('should return the correct string if serviceRequirement is NOT_APPLICABLE and appeal expiration date is in the past', () => { + const verdictAppealDeadline = '2024-07-07' + const serviceRequirement = ServiceRequirement.NOT_APPLICABLE + + const dataSections = getAppealExpirationInfo( + verdictAppealDeadline, + serviceRequirement, + ) expect(dataSections.message.id).toStrictEqual( 'judicial.system.core:info_card.defendant_info.appeal_date_expired', ) - expect(dataSections.data).toStrictEqual('07.07.2024') + expect(dataSections.date).toStrictEqual('07.07.2024') }) }) }) diff --git a/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.strings.ts b/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.strings.ts index d2ba1afbfc85..3bd4c5af473a 100644 --- a/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.strings.ts +++ b/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.strings.ts @@ -19,12 +19,7 @@ export const strings = defineMessages({ 'Notaður til að láta vita að áfrýjunarfrestur dómfellda er ekki hafinn.', }, serviceRequirementNotRequired: { - id: 'judicial.system.core:info_card.defendant_info.service_requirement_not_required', - defaultMessage: 'Dómfelldi var viðstaddur dómþing', - description: 'Notað til að láta vita birting dóms er ekki nauðsynleg.', - }, - serviceRequirementNotApplicable: { - id: 'judicial.system.core:info_card.defendant_info.service_requirement_not_applicable', + id: 'judicial.system.core:info_card.defendant_info.service_requirement_not_required_v1', defaultMessage: 'Birting dóms ekki þörf', description: 'Notað til að láta vita birting dóms er ekki nauðsynleg.', }, diff --git a/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.tsx b/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.tsx index a21584d0ec62..1666858b4342 100644 --- a/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.tsx +++ b/apps/judicial-system/web/src/components/InfoCard/DefendantInfo/DefendantInfo.tsx @@ -37,31 +37,27 @@ interface DefendantInfoProps { } export const getAppealExpirationInfo = ( - viewDate?: string | null, + verdictAppealDeadline?: string | null, serviceRequirement?: ServiceRequirement | null, ) => { - if (!viewDate) { - return { message: strings.appealDateNotBegun, data: null } - } - if (serviceRequirement === ServiceRequirement.NOT_REQUIRED) { return { message: strings.serviceRequirementNotRequired, data: null } } - if (serviceRequirement === ServiceRequirement.NOT_APPLICABLE) { - return { message: strings.serviceRequirementNotApplicable, data: null } + if (!verdictAppealDeadline) { + return { message: strings.appealDateNotBegun, date: null } } + // TODO: Move to the server as today may not be accurate in the client const today = new Date() - const expiryDate = new Date(viewDate) - expiryDate.setDate(expiryDate.getDate() + 28) + const expiryDate = new Date(verdictAppealDeadline) const message = today < expiryDate ? strings.appealExpirationDate : strings.appealDateExpired - return { message, data: formatDate(expiryDate) } + return { message, date: formatDate(expiryDate) } } export const DefendantInfo: FC = (props) => { @@ -75,7 +71,7 @@ export const DefendantInfo: FC = (props) => { const { formatMessage } = useIntl() const appealExpirationInfo = getAppealExpirationInfo( - defendant.verdictViewDate, + defendant.verdictAppealDeadline, defendant.serviceRequirement, ) @@ -123,7 +119,7 @@ export const DefendantInfo: FC = (props) => { {formatMessage(appealExpirationInfo.message, { - appealExpirationDate: appealExpirationInfo.data, + appealExpirationDate: appealExpirationInfo.date, })} diff --git a/apps/judicial-system/web/src/components/InfoCard/InfoCard.spec.tsx b/apps/judicial-system/web/src/components/InfoCard/InfoCard.spec.tsx index c0553cf85115..4555f3428f19 100644 --- a/apps/judicial-system/web/src/components/InfoCard/InfoCard.spec.tsx +++ b/apps/judicial-system/web/src/components/InfoCard/InfoCard.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { MockedProvider } from '@apollo/client/testing' import { render, screen } from '@testing-library/react' diff --git a/apps/judicial-system/web/src/components/InfoCard/InfoCardActiveIndictment.tsx b/apps/judicial-system/web/src/components/InfoCard/InfoCardActiveIndictment.tsx index a46241348090..f24fec7cc266 100644 --- a/apps/judicial-system/web/src/components/InfoCard/InfoCardActiveIndictment.tsx +++ b/apps/judicial-system/web/src/components/InfoCard/InfoCardActiveIndictment.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react' +import { useContext } from 'react' import { FormContext } from '../FormProvider/FormProvider' import InfoCard from './InfoCard' diff --git a/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.tsx b/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.tsx index 9289a3d1ac62..a03a766414c7 100644 --- a/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.tsx +++ b/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.tsx @@ -184,19 +184,6 @@ const useInfoCardItems = () => { ], } - const confirmedCourtDate: Item = { - id: 'confirmed-court-date-item', - title: formatMessage(core.confirmedCourtDate), - values: [ - `${capitalize( - formatDate(workingCase.arraignmentDate?.date, 'PPPP', true) ?? '', - )} kl. ${formatDate( - workingCase.arraignmentDate?.date, - constants.TIME_FORMAT, - )}`, - ], - } - const mergeCase: Item = { id: 'merge-case-item', title: formatMessage(strings.indictmentMergedTitle), @@ -324,7 +311,6 @@ const useInfoCardItems = () => { registrar, offence, requestedCourtDate, - confirmedCourtDate, mergeCase, mergedCasePoliceCaseNumbers, mergedCaseCourtCaseNumber, diff --git a/apps/judicial-system/web/src/components/Loading/Loading.tsx b/apps/judicial-system/web/src/components/Loading/Loading.tsx index fadda8dcad5b..bb8e13e0ae8a 100644 --- a/apps/judicial-system/web/src/components/Loading/Loading.tsx +++ b/apps/judicial-system/web/src/components/Loading/Loading.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import { Box, Text } from '@island.is/island-ui/core' import * as styles from './Loading.css' diff --git a/apps/judicial-system/web/src/components/Logo/LandWightsLogo.tsx b/apps/judicial-system/web/src/components/Logo/LandWightsLogo.tsx index afc222ad1bf4..ee05b76137e8 100644 --- a/apps/judicial-system/web/src/components/Logo/LandWightsLogo.tsx +++ b/apps/judicial-system/web/src/components/Logo/LandWightsLogo.tsx @@ -1,5 +1,3 @@ -import React from 'react' - const LandWightsLogo = () => ( ( > = ({ { // Check if text is a string or Element - React.isValidElement(text) ? text : {text} + isValidElement(text) ? text : {text} } )} diff --git a/apps/judicial-system/web/src/components/MultipleValueList/MultipleValueList.tsx b/apps/judicial-system/web/src/components/MultipleValueList/MultipleValueList.tsx index f171b06cbc83..b8e505c97346 100644 --- a/apps/judicial-system/web/src/components/MultipleValueList/MultipleValueList.tsx +++ b/apps/judicial-system/web/src/components/MultipleValueList/MultipleValueList.tsx @@ -1,4 +1,11 @@ -import React, { FC, PropsWithChildren, useRef, useState } from 'react' +import { + FC, + FocusEvent, + KeyboardEvent, + PropsWithChildren, + useRef, + useState, +} from 'react' import InputMask from 'react-input-mask' import { Button, Input } from '@island.is/island-ui/core' @@ -16,9 +23,7 @@ interface MultipleValueListProps { isDisabled: (value?: string) => boolean hasError?: boolean errorMessage?: string - onBlur?: ( - event: React.FocusEvent, - ) => void + onBlur?: (event: FocusEvent) => void } const MultipleValueList: FC> = ({ @@ -47,7 +52,7 @@ const MultipleValueList: FC> = ({ } const handleEnter = ( - event: React.KeyboardEvent, + event: KeyboardEvent, ) => { if (event.key === 'Enter' && !isDisabled(value)) { onAddValue(value) diff --git a/apps/judicial-system/web/src/components/OverviewHeader/OverviewHeader.tsx b/apps/judicial-system/web/src/components/OverviewHeader/OverviewHeader.tsx index 2b0a9486d554..4944c9f000cd 100644 --- a/apps/judicial-system/web/src/components/OverviewHeader/OverviewHeader.tsx +++ b/apps/judicial-system/web/src/components/OverviewHeader/OverviewHeader.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/PageHeader/PageHeader.tsx b/apps/judicial-system/web/src/components/PageHeader/PageHeader.tsx index ab24dcf1d74b..023f10edce83 100644 --- a/apps/judicial-system/web/src/components/PageHeader/PageHeader.tsx +++ b/apps/judicial-system/web/src/components/PageHeader/PageHeader.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import Head from 'next/head' interface Props { diff --git a/apps/judicial-system/web/src/components/PageLayout/PageLayout.tsx b/apps/judicial-system/web/src/components/PageLayout/PageLayout.tsx index f73e25d8bbd5..3b535b0a5b69 100644 --- a/apps/judicial-system/web/src/components/PageLayout/PageLayout.tsx +++ b/apps/judicial-system/web/src/components/PageLayout/PageLayout.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren, ReactNode, useContext } from 'react' +import { FC, PropsWithChildren, ReactNode, useContext } from 'react' import { useIntl } from 'react-intl' import cn from 'classnames' diff --git a/apps/judicial-system/web/src/components/PageLayout/utils/index.spec.tsx b/apps/judicial-system/web/src/components/PageLayout/utils/index.spec.tsx index 273e17f56f29..6a8ec89ce6a1 100644 --- a/apps/judicial-system/web/src/components/PageLayout/utils/index.spec.tsx +++ b/apps/judicial-system/web/src/components/PageLayout/utils/index.spec.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { IntlFormatters, useIntl } from 'react-intl' import { MockedProvider } from '@apollo/client/testing' import { render, screen } from '@testing-library/react' diff --git a/apps/judicial-system/web/src/components/PageTitle/PageTitle.tsx b/apps/judicial-system/web/src/components/PageTitle/PageTitle.tsx index 703b4353151d..d9882f66e792 100644 --- a/apps/judicial-system/web/src/components/PageTitle/PageTitle.tsx +++ b/apps/judicial-system/web/src/components/PageTitle/PageTitle.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren } from 'react' +import { FC, PropsWithChildren } from 'react' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/ParentCaseFiles/ParentCaseFiles.tsx b/apps/judicial-system/web/src/components/ParentCaseFiles/ParentCaseFiles.tsx index 1afdc1a39536..6e46cea8cb13 100644 --- a/apps/judicial-system/web/src/components/ParentCaseFiles/ParentCaseFiles.tsx +++ b/apps/judicial-system/web/src/components/ParentCaseFiles/ParentCaseFiles.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { diff --git a/apps/judicial-system/web/src/components/PdfButton/PdfButton.tsx b/apps/judicial-system/web/src/components/PdfButton/PdfButton.tsx index a1ddcff825c1..93f455330ffa 100644 --- a/apps/judicial-system/web/src/components/PdfButton/PdfButton.tsx +++ b/apps/judicial-system/web/src/components/PdfButton/PdfButton.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren, useContext } from 'react' +import { FC, PropsWithChildren, useContext } from 'react' import { Box, Button, Text } from '@island.is/island-ui/core' import { api } from '@island.is/judicial-system-web/src/services' diff --git a/apps/judicial-system/web/src/components/ProsecutorSelection/ProsecutorSelection.tsx b/apps/judicial-system/web/src/components/ProsecutorSelection/ProsecutorSelection.tsx index 3a3347dbee24..5c409e68e803 100644 --- a/apps/judicial-system/web/src/components/ProsecutorSelection/ProsecutorSelection.tsx +++ b/apps/judicial-system/web/src/components/ProsecutorSelection/ProsecutorSelection.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useMemo } from 'react' +import { FC, useContext, useMemo } from 'react' import { useIntl } from 'react-intl' import { Option, Select } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/RequestAppealRulingNotToBePublishedCheckbox/RequestAppealRulingNotToBePublishedCheckbox.tsx b/apps/judicial-system/web/src/components/RequestAppealRulingNotToBePublishedCheckbox/RequestAppealRulingNotToBePublishedCheckbox.tsx index 9f47e955be5b..f020ff7d2377 100644 --- a/apps/judicial-system/web/src/components/RequestAppealRulingNotToBePublishedCheckbox/RequestAppealRulingNotToBePublishedCheckbox.tsx +++ b/apps/judicial-system/web/src/components/RequestAppealRulingNotToBePublishedCheckbox/RequestAppealRulingNotToBePublishedCheckbox.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { Checkbox } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/RequiredStar/RequiredStar.tsx b/apps/judicial-system/web/src/components/RequiredStar/RequiredStar.tsx index 44d7d49a81ba..b727ac7b3c5a 100644 --- a/apps/judicial-system/web/src/components/RequiredStar/RequiredStar.tsx +++ b/apps/judicial-system/web/src/components/RequiredStar/RequiredStar.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import * as styles from './RequiredStar.css' const RequiredStar = () => { diff --git a/apps/judicial-system/web/src/components/RestrictionLength/RestrictionLength.tsx b/apps/judicial-system/web/src/components/RestrictionLength/RestrictionLength.tsx index cfc67edc69ff..e2813a38a390 100644 --- a/apps/judicial-system/web/src/components/RestrictionLength/RestrictionLength.tsx +++ b/apps/judicial-system/web/src/components/RestrictionLength/RestrictionLength.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { ChangeEvent, FC, useContext } from 'react' import { useIntl } from 'react-intl' import { Box, Checkbox, Text } from '@island.is/island-ui/core' @@ -20,7 +20,7 @@ import { restrictionLength as strings } from './RestrictionLength.strings' interface Props { workingCase: Case - handleIsolationChange: (event: React.ChangeEvent) => void + handleIsolationChange: (event: ChangeEvent) => void handleIsolationDateChange: (date: Date | undefined, valid: boolean) => void handleValidToDateChange: (date: Date | undefined, valid: boolean) => void } diff --git a/apps/judicial-system/web/src/components/RestrictionTags/RestrictionTags.spec.tsx b/apps/judicial-system/web/src/components/RestrictionTags/RestrictionTags.spec.tsx index bc2e22bc7a91..46bb543df5a3 100644 --- a/apps/judicial-system/web/src/components/RestrictionTags/RestrictionTags.spec.tsx +++ b/apps/judicial-system/web/src/components/RestrictionTags/RestrictionTags.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { render, screen } from '@testing-library/react' import { diff --git a/apps/judicial-system/web/src/components/RestrictionTags/RestrictionTags.tsx b/apps/judicial-system/web/src/components/RestrictionTags/RestrictionTags.tsx index 5d3fcdd8017f..244e40b50256 100644 --- a/apps/judicial-system/web/src/components/RestrictionTags/RestrictionTags.tsx +++ b/apps/judicial-system/web/src/components/RestrictionTags/RestrictionTags.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { Box, Tag } from '@island.is/island-ui/core' import { getShortRestrictionByValue } from '@island.is/judicial-system/formatters' diff --git a/apps/judicial-system/web/src/components/RulingDateLabel/RulingDateLabel.tsx b/apps/judicial-system/web/src/components/RulingDateLabel/RulingDateLabel.tsx index 9fd159474178..18f71783856c 100644 --- a/apps/judicial-system/web/src/components/RulingDateLabel/RulingDateLabel.tsx +++ b/apps/judicial-system/web/src/components/RulingDateLabel/RulingDateLabel.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/RulingInput/RulingInput.tsx b/apps/judicial-system/web/src/components/RulingInput/RulingInput.tsx index 4432a4abe06e..d2690abd421b 100644 --- a/apps/judicial-system/web/src/components/RulingInput/RulingInput.tsx +++ b/apps/judicial-system/web/src/components/RulingInput/RulingInput.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { useIntl } from 'react-intl' import { Input } from '@island.is/island-ui/core' @@ -12,7 +12,7 @@ import { useCase, useDeb } from '@island.is/judicial-system-web/src/utils/hooks' interface Props { workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> rows?: number } diff --git a/apps/judicial-system/web/src/components/SectionHeading/SectionHeading.tsx b/apps/judicial-system/web/src/components/SectionHeading/SectionHeading.tsx index e4da9defa84a..cde8643dd52b 100644 --- a/apps/judicial-system/web/src/components/SectionHeading/SectionHeading.tsx +++ b/apps/judicial-system/web/src/components/SectionHeading/SectionHeading.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC, ReactNode } from 'react' import { Box, ResponsiveProp, Space, Text } from '@island.is/island-ui/core' @@ -7,8 +7,8 @@ import RequiredStar from '../RequiredStar/RequiredStar' interface Props { title: string required?: boolean - tooltip?: React.ReactNode - description?: React.ReactNode + tooltip?: ReactNode + description?: ReactNode marginBottom?: ResponsiveProp heading?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' } diff --git a/apps/judicial-system/web/src/components/SelectableList/SelectableList.tsx b/apps/judicial-system/web/src/components/SelectableList/SelectableList.tsx index 59352fb3e760..fa28286c41a1 100644 --- a/apps/judicial-system/web/src/components/SelectableList/SelectableList.tsx +++ b/apps/judicial-system/web/src/components/SelectableList/SelectableList.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren, useEffect, useState } from 'react' +import { FC, PropsWithChildren, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence, motion } from 'framer-motion' diff --git a/apps/judicial-system/web/src/components/ServiceInterruptionBanner/ServiceInterruptionBanner.tsx b/apps/judicial-system/web/src/components/ServiceInterruptionBanner/ServiceInterruptionBanner.tsx index 106221185062..9208696682b5 100644 --- a/apps/judicial-system/web/src/components/ServiceInterruptionBanner/ServiceInterruptionBanner.tsx +++ b/apps/judicial-system/web/src/components/ServiceInterruptionBanner/ServiceInterruptionBanner.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react' +import { useContext } from 'react' import { useIntl } from 'react-intl' import { AlertBanner } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/SharedPageLayout/SharedPageLayout.tsx b/apps/judicial-system/web/src/components/SharedPageLayout/SharedPageLayout.tsx index 726f774f3976..c564e0f3a3b5 100644 --- a/apps/judicial-system/web/src/components/SharedPageLayout/SharedPageLayout.tsx +++ b/apps/judicial-system/web/src/components/SharedPageLayout/SharedPageLayout.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren } from 'react' +import { FC, PropsWithChildren } from 'react' import { Box } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/SignedDocument/SignedDocument.tsx b/apps/judicial-system/web/src/components/SignedDocument/SignedDocument.tsx index 57f8f38b5e5f..84beb4ab7039 100644 --- a/apps/judicial-system/web/src/components/SignedDocument/SignedDocument.tsx +++ b/apps/judicial-system/web/src/components/SignedDocument/SignedDocument.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { useIntl } from 'react-intl' import { Icon, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/SigningModal/SigningModal.tsx b/apps/judicial-system/web/src/components/SigningModal/SigningModal.tsx index c049ca497c99..441d7b450b20 100644 --- a/apps/judicial-system/web/src/components/SigningModal/SigningModal.tsx +++ b/apps/judicial-system/web/src/components/SigningModal/SigningModal.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' import { diff --git a/apps/judicial-system/web/src/components/Skeleton/Skeleton.tsx b/apps/judicial-system/web/src/components/Skeleton/Skeleton.tsx index 33abca12eb80..6677aec4e6e4 100644 --- a/apps/judicial-system/web/src/components/Skeleton/Skeleton.tsx +++ b/apps/judicial-system/web/src/components/Skeleton/Skeleton.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import { Box, SkeletonLoader } from '@island.is/island-ui/core' import * as styles from './Skeleton.css' diff --git a/apps/judicial-system/web/src/components/Table/AppealCasesTable/AppealCasesTable.tsx b/apps/judicial-system/web/src/components/Table/AppealCasesTable/AppealCasesTable.tsx index e76e201af0e7..1b652d13814e 100644 --- a/apps/judicial-system/web/src/components/Table/AppealCasesTable/AppealCasesTable.tsx +++ b/apps/judicial-system/web/src/components/Table/AppealCasesTable/AppealCasesTable.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo } from 'react' +import { FC, useMemo } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' @@ -17,7 +17,6 @@ import { import { CaseListEntry } from '@island.is/judicial-system-web/src/graphql/schema' import { useCaseList, - useSortAppealCases, useViewport, } from '@island.is/judicial-system-web/src/utils/hooks' @@ -35,8 +34,8 @@ const AppealCasesTable: FC = (props) => { const { cases, loading, showingCompletedCases } = props const { formatMessage } = useIntl() const { isOpeningCaseId, handleOpenCase, showLoading } = useCaseList() - const { sortedData } = useSortAppealCases('appealedDate', 'descending', cases) const { openCaseInNewTabMenuItem } = useContextMenu() + const activeCasesData = useMemo( () => cases.sort((a: CaseListEntry, b: CaseListEntry) => @@ -92,7 +91,7 @@ const AppealCasesTable: FC = (props) => { : { isSortable: true, key: 'appealedDate' }, }, ]} - data={sortedData} + data={activeCasesData} generateContextMenuItems={(row) => { return [openCaseInNewTabMenuItem(row.id)] }} diff --git a/apps/judicial-system/web/src/components/Table/AppealCasesTable/MobileAppealCase.tsx b/apps/judicial-system/web/src/components/Table/AppealCasesTable/MobileAppealCase.tsx index ba0c2ff5f17d..16322b9078dc 100644 --- a/apps/judicial-system/web/src/components/Table/AppealCasesTable/MobileAppealCase.tsx +++ b/apps/judicial-system/web/src/components/Table/AppealCasesTable/MobileAppealCase.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC, ReactNode } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' @@ -12,7 +12,7 @@ import { CategoryCard } from '@island.is/judicial-system-web/src/routes/Shared/C import { displayCaseType } from '@island.is/judicial-system-web/src/routes/Shared/Cases/utils' interface Props { - children: React.ReactNode + children: ReactNode theCase: CaseListEntry onClick: () => void isLoading?: boolean diff --git a/apps/judicial-system/web/src/components/Table/CaseFileTable/CaseFileTable.css.ts b/apps/judicial-system/web/src/components/Table/CaseFileTable/CaseFileTable.css.ts new file mode 100644 index 000000000000..0dc6502b5e2e --- /dev/null +++ b/apps/judicial-system/web/src/components/Table/CaseFileTable/CaseFileTable.css.ts @@ -0,0 +1,13 @@ +import { style } from '@vanilla-extract/css' + +import { theme } from '@island.is/island-ui/theme' + +export const linkButton = style({ cursor: 'pointer' }) + +export const noWrapColumn = style({ + '@media': { + [`screen and (min-width: ${theme.breakpoints.lg}px)`]: { + whiteSpace: 'nowrap', + }, + }, +}) diff --git a/apps/judicial-system/web/src/components/Table/CaseFileTable/CaseFileTable.strings.ts b/apps/judicial-system/web/src/components/Table/CaseFileTable/CaseFileTable.strings.ts new file mode 100644 index 000000000000..a2e4e209292c --- /dev/null +++ b/apps/judicial-system/web/src/components/Table/CaseFileTable/CaseFileTable.strings.ts @@ -0,0 +1,10 @@ +import { defineMessages } from 'react-intl' + +export const strings = defineMessages({ + submittedBy: { + id: 'judicial.system.core:table.case_file_table.submitted_by', + defaultMessage: + '{category, select, PROSECUTOR_CASE_FILE {Sækjandi} other {Verjandi}} {initials, select, undefined {} other {({initials})}} lagði fram', + description: 'Notaður sem titill fyrir dagsetningu í töflum.', + }, +}) diff --git a/apps/judicial-system/web/src/components/Table/CaseFileTable/CaseFileTable.tsx b/apps/judicial-system/web/src/components/Table/CaseFileTable/CaseFileTable.tsx new file mode 100644 index 000000000000..50912848c34a --- /dev/null +++ b/apps/judicial-system/web/src/components/Table/CaseFileTable/CaseFileTable.tsx @@ -0,0 +1,107 @@ +import { FC } from 'react' +import { useIntl } from 'react-intl' + +import { Box, Text } from '@island.is/island-ui/core' +import { + capitalize, + formatDate, + getInitials, +} from '@island.is/judicial-system/formatters' +import { tables } from '@island.is/judicial-system-web/messages' +import { + CreatedDate, + SortButton, + TableContainer, + TableHeaderText, +} from '@island.is/judicial-system-web/src/components/Table' +import { CaseFile } from '@island.is/judicial-system-web/src/graphql/schema' +import { useSort } from '@island.is/judicial-system-web/src/utils/hooks' + +import { strings } from './CaseFileTable.strings' +import * as tableStyles from '../Table.css' +import * as styles from './CaseFileTable.css' + +interface Props { + caseFiles: CaseFile[] + loading?: boolean + onOpenFile?: (fileId: string) => void +} + +const CaseFileTable: FC = ({ + caseFiles, + loading = false, + onOpenFile, +}) => { + const { formatMessage } = useIntl() + + const { sortedData, requestSort, getClassNamesFor, isActiveColumn } = useSort( + 'name', + 'ascending', + caseFiles, + (entry, column) => entry[column] as string | null | undefined, + ) + + const createSortProps = (columnTitle: string, column: keyof CaseFile) => ({ + title: capitalize(columnTitle), + onClick: () => requestSort(column), + sortAsc: getClassNamesFor(column) === 'ascending', + sortDes: getClassNamesFor(column) === 'descending', + isActive: isActiveColumn(column), + }) + + return ( + + + + + + + + + + } + > + {sortedData.map((file) => { + return ( +
+ + onOpenFile?.(file.id)} + className={styles.linkButton} + > + + {file.userGeneratedFilename} + + + + + + + + + + {formatDate(file.created, "dd.MM.yyyy 'kl.' HH:mm")} + + + {formatMessage(strings.submittedBy, { + category: file.category, + initials: getInitials(file.submittedBy), + })} + + + +
+ ) + })} +
+ ) +} + +export default CaseFileTable diff --git a/apps/judicial-system/web/src/components/Table/ColumnCaseType/ColumnCaseType.tsx b/apps/judicial-system/web/src/components/Table/ColumnCaseType/ColumnCaseType.tsx index 75bc9ceabd4e..84dd283329f3 100644 --- a/apps/judicial-system/web/src/components/Table/ColumnCaseType/ColumnCaseType.tsx +++ b/apps/judicial-system/web/src/components/Table/ColumnCaseType/ColumnCaseType.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/Table/CourtCaseNumber/CourtCaseNumber.tsx b/apps/judicial-system/web/src/components/Table/CourtCaseNumber/CourtCaseNumber.tsx index e53dfa467c47..3c9572470bf6 100644 --- a/apps/judicial-system/web/src/components/Table/CourtCaseNumber/CourtCaseNumber.tsx +++ b/apps/judicial-system/web/src/components/Table/CourtCaseNumber/CourtCaseNumber.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { Box, Text } from '@island.is/island-ui/core' import { displayFirstPlusRemaining } from '@island.is/judicial-system/formatters' diff --git a/apps/judicial-system/web/src/components/Table/DefendantInfo/DefendantInfo.tsx b/apps/judicial-system/web/src/components/Table/DefendantInfo/DefendantInfo.tsx index 845b54f75f25..330251cf961e 100644 --- a/apps/judicial-system/web/src/components/Table/DefendantInfo/DefendantInfo.tsx +++ b/apps/judicial-system/web/src/components/Table/DefendantInfo/DefendantInfo.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { Box, Text } from '@island.is/island-ui/core' import { formatDOB } from '@island.is/judicial-system/formatters' diff --git a/apps/judicial-system/web/src/components/Table/PastCasesTable/MobilePastCase.tsx b/apps/judicial-system/web/src/components/Table/PastCasesTable/MobilePastCase.tsx index 5e3bdd10ae60..c7170fd5f139 100644 --- a/apps/judicial-system/web/src/components/Table/PastCasesTable/MobilePastCase.tsx +++ b/apps/judicial-system/web/src/components/Table/PastCasesTable/MobilePastCase.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC, ReactNode } from 'react' import { useIntl } from 'react-intl' import format from 'date-fns/format' import parseISO from 'date-fns/parseISO' @@ -18,7 +18,7 @@ interface Props { theCase: CaseListEntry onClick: () => void isCourtRole: boolean - children: React.ReactNode + children: ReactNode isLoading?: boolean } diff --git a/apps/judicial-system/web/src/components/Table/PastCasesTable/PastCasesTable.tsx b/apps/judicial-system/web/src/components/Table/PastCasesTable/PastCasesTable.tsx index c0fb189f5ba9..b171d9d90953 100644 --- a/apps/judicial-system/web/src/components/Table/PastCasesTable/PastCasesTable.tsx +++ b/apps/judicial-system/web/src/components/Table/PastCasesTable/PastCasesTable.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useMemo } from 'react' +import { FC, useContext, useMemo } from 'react' import { useIntl } from 'react-intl' import cn from 'classnames' import { AnimatePresence } from 'framer-motion' @@ -36,7 +36,7 @@ import { } from '@island.is/judicial-system-web/src/graphql/schema' import { useCaseList, - useSortCases, + useSort, useViewport, } from '@island.is/judicial-system-web/src/utils/hooks' @@ -59,8 +59,27 @@ const PastCasesTable: FC = ({ cases, loading = false, testid }) => { const { user } = useContext(UserContext) const { isOpeningCaseId, handleOpenCase, LoadingIndicator, showLoading } = useCaseList() - const { sortedData, requestSort, getClassNamesFor, isActiveColumn } = - useSortCases('created', 'descending', cases) + + const getColumnValue = ( + entry: CaseListEntry, + column: keyof CaseListEntry, + ) => { + if ( + column === 'defendants' && + entry.defendants && + entry.defendants.length > 0 + ) { + return entry.defendants[0].name ?? '' + } + return entry.created + } + + const { sortedData, requestSort, getClassNamesFor, isActiveColumn } = useSort( + 'created', + 'descending', + cases, + getColumnValue, + ) const { withdrawAppealMenuOption, diff --git a/apps/judicial-system/web/src/components/Table/SortButton/SortButton.tsx b/apps/judicial-system/web/src/components/Table/SortButton/SortButton.tsx index a3b054e12449..06c33bbc0545 100644 --- a/apps/judicial-system/web/src/components/Table/SortButton/SortButton.tsx +++ b/apps/judicial-system/web/src/components/Table/SortButton/SortButton.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import cn from 'classnames' import { Box, Icon, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/Table/Table.tsx b/apps/judicial-system/web/src/components/Table/Table.tsx index eefb4522490c..cb9450da3f3e 100644 --- a/apps/judicial-system/web/src/components/Table/Table.tsx +++ b/apps/judicial-system/web/src/components/Table/Table.tsx @@ -1,10 +1,4 @@ -import React, { - FC, - PropsWithChildren, - ReactNode, - useContext, - useMemo, -} from 'react' +import { FC, PropsWithChildren, ReactNode, useContext, useMemo } from 'react' import { useIntl } from 'react-intl' import { useLocalStorage } from 'react-use' import parseISO from 'date-fns/parseISO' diff --git a/apps/judicial-system/web/src/components/Table/TableContainer/TableContainer.tsx b/apps/judicial-system/web/src/components/Table/TableContainer/TableContainer.tsx index 0a3e79d94066..aea24f2ad3be 100644 --- a/apps/judicial-system/web/src/components/Table/TableContainer/TableContainer.tsx +++ b/apps/judicial-system/web/src/components/Table/TableContainer/TableContainer.tsx @@ -1,12 +1,12 @@ -import React, { FC } from 'react' +import { FC, ReactNode } from 'react' import { TableSkeleton } from '@island.is/judicial-system-web/src/components/Table' import * as styles from '../Table.css' interface Props { - tableHeader: React.ReactNode - children: React.ReactNode + tableHeader: ReactNode + children: ReactNode loading: boolean testid?: string } diff --git a/apps/judicial-system/web/src/components/Table/TableHeaderText/TableHeaderText.tsx b/apps/judicial-system/web/src/components/Table/TableHeaderText/TableHeaderText.tsx index 8e0353d102b9..4666a5700639 100644 --- a/apps/judicial-system/web/src/components/Table/TableHeaderText/TableHeaderText.tsx +++ b/apps/judicial-system/web/src/components/Table/TableHeaderText/TableHeaderText.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/Table/TableSkeleton/TableSkeleton.tsx b/apps/judicial-system/web/src/components/Table/TableSkeleton/TableSkeleton.tsx index 4c850a5c28da..c9d940bc8979 100644 --- a/apps/judicial-system/web/src/components/Table/TableSkeleton/TableSkeleton.tsx +++ b/apps/judicial-system/web/src/components/Table/TableSkeleton/TableSkeleton.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import { Box, SkeletonLoader } from '@island.is/island-ui/core' import { theme } from '@island.is/island-ui/theme' import { useViewport } from '@island.is/judicial-system-web/src/utils/hooks' diff --git a/apps/judicial-system/web/src/components/Table/index.ts b/apps/judicial-system/web/src/components/Table/index.ts index 72f23966d38c..c2845d109c1a 100644 --- a/apps/judicial-system/web/src/components/Table/index.ts +++ b/apps/judicial-system/web/src/components/Table/index.ts @@ -13,3 +13,4 @@ export { default as CreatedDate } from './CreatedDate/CreatedDate' export { default as AppealCasesTable } from './AppealCasesTable/AppealCasesTable' export { default as PastCasesTable } from './PastCasesTable/PastCasesTable' export { default as CourtDate } from './CourtDate/CourtDate' +export { default as CaseFileTable } from './CaseFileTable/CaseFileTable' diff --git a/apps/judicial-system/web/src/components/TagAppealState/TagAppealState.tsx b/apps/judicial-system/web/src/components/TagAppealState/TagAppealState.tsx index b16228d35c78..94cd21dc13d4 100644 --- a/apps/judicial-system/web/src/components/TagAppealState/TagAppealState.tsx +++ b/apps/judicial-system/web/src/components/TagAppealState/TagAppealState.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { Tag, TagVariant } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx index 03c01d1b4c9f..3e5445189f72 100644 --- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx +++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { IntlShape, useIntl } from 'react-intl' import { Tag, TagVariant } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/Tags/CaseTag.tsx b/apps/judicial-system/web/src/components/Tags/CaseTag.tsx index 6e7f56577979..73fc906c9c41 100644 --- a/apps/judicial-system/web/src/components/Tags/CaseTag.tsx +++ b/apps/judicial-system/web/src/components/Tags/CaseTag.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { Tag, TagVariant } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/components/TimeInputField/TimeInputField.spec.tsx b/apps/judicial-system/web/src/components/TimeInputField/TimeInputField.spec.tsx index 3c0fb9941aa8..ed33fa515dde 100644 --- a/apps/judicial-system/web/src/components/TimeInputField/TimeInputField.spec.tsx +++ b/apps/judicial-system/web/src/components/TimeInputField/TimeInputField.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' diff --git a/apps/judicial-system/web/src/components/TimeInputField/TimeInputField.tsx b/apps/judicial-system/web/src/components/TimeInputField/TimeInputField.tsx index 0f3d3a715425..7afa34379d9e 100644 --- a/apps/judicial-system/web/src/components/TimeInputField/TimeInputField.tsx +++ b/apps/judicial-system/web/src/components/TimeInputField/TimeInputField.tsx @@ -1,12 +1,12 @@ -import React, { FC, PropsWithChildren } from 'react' +import { ChangeEvent, FC, FocusEvent, PropsWithChildren } from 'react' import InputMask from 'react-input-mask' interface Props { disabled?: boolean value?: string - onBlur?: (event: React.FocusEvent) => void - onFocus?: (event: React.FocusEvent) => void - onChange?: (event: React.ChangeEvent) => void + onBlur?: (event: FocusEvent) => void + onFocus?: (event: FocusEvent) => void + onChange?: (event: ChangeEvent) => void } const TimeInputField: FC> = ({ diff --git a/apps/judicial-system/web/src/components/UploadFiles/UploadFiles.css.ts b/apps/judicial-system/web/src/components/UploadFiles/UploadFiles.css.ts new file mode 100644 index 000000000000..28ac965ebff0 --- /dev/null +++ b/apps/judicial-system/web/src/components/UploadFiles/UploadFiles.css.ts @@ -0,0 +1,22 @@ +import { style } from '@vanilla-extract/css' + +import { theme } from '@island.is/island-ui/theme' + +export const container = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + + border: `1px dashed ${theme.color.blue200}`, + borderRadius: theme.border.radius.large, + + padding: `${theme.spacing[10]}px`, + marginBottom: `${theme.spacing[10]}px`, + transition: 'border-color 0.2s ease-in-out', + + selectors: { + '&:hover': { + borderColor: theme.color.blue400, + }, + }, +}) diff --git a/apps/judicial-system/web/src/components/UploadFiles/UploadFiles.strings.ts b/apps/judicial-system/web/src/components/UploadFiles/UploadFiles.strings.ts new file mode 100644 index 000000000000..16b815fa7d44 --- /dev/null +++ b/apps/judicial-system/web/src/components/UploadFiles/UploadFiles.strings.ts @@ -0,0 +1,21 @@ +import { defineMessages } from 'react-intl' + +export const strings = defineMessages({ + heading: { + id: 'judicial.system.core:upload_files.heading', + defaultMessage: 'Dragðu skjöl hingað til að hlaða upp', + description: + 'Notaður sem titill í hlaða upp skjölum hluta á Gögn síðu í ákærum.', + }, + acceptFiles: { + id: 'judicial.system.core:upload_files.accept_files', + defaultMessage: 'Tekið er við skjölum með endingu: .pdf', + description: + 'Notaður sem texti til að tilkynna að aðeins .pdf skjöl eru leyfð.', + }, + buttonText: { + id: 'judicial.system.core:upload_files.button_text', + defaultMessage: 'Velja skjöl til að hlaða upp', + description: 'Notaður sem texti á takka til að velja skjölum.', + }, +}) diff --git a/apps/judicial-system/web/src/components/UploadFiles/UploadFiles.tsx b/apps/judicial-system/web/src/components/UploadFiles/UploadFiles.tsx new file mode 100644 index 000000000000..35a2ff34368d --- /dev/null +++ b/apps/judicial-system/web/src/components/UploadFiles/UploadFiles.tsx @@ -0,0 +1,84 @@ +import { Dispatch, FC, SetStateAction, useCallback, useContext } from 'react' +import { useDropzone } from 'react-dropzone' +import { useIntl } from 'react-intl' + +import { Box, Button, Text } from '@island.is/island-ui/core' + +import { TUploadFile, useFileList } from '../../utils/hooks' +import EditableCaseFile from '../EditableCaseFile/EditableCaseFile' +import { FormContext } from '../FormProvider/FormProvider' +import { strings } from './UploadFiles.strings' +import * as styles from './UploadFiles.css' + +interface Props { + files: TUploadFile[] + onChange: (files: File[]) => void + onRetry?: (file: TUploadFile) => void + onDelete: (file: TUploadFile) => void + onRename: (fileId: string, newName: string, newDisplayDate: string) => void + setEditCount: Dispatch> +} + +const UploadFiles: FC = (props) => { + const { files, onChange, onRetry, onDelete, onRename, setEditCount } = props + const { workingCase } = useContext(FormContext) + const { formatMessage } = useIntl() + + const { onOpen } = useFileList({ caseId: workingCase.id }) + + const onDrop = useCallback( + (acceptedFiles: File[]) => { + onChange(acceptedFiles) + }, + [onChange], + ) + + const { getRootProps, getInputProps } = useDropzone({ + accept: 'application/pdf', + onDrop, + }) + + return ( +
+ + + {formatMessage(strings.heading)} + + + + {formatMessage(strings.acceptFiles)} + + + + + {files.map((file) => ( + event.stopPropagation()} + > + setEditCount((count) => count + 1)} + onStopEditing={() => setEditCount((count) => count - 1)} + /> + + ))} + +
+ ) +} + +export default UploadFiles diff --git a/apps/judicial-system/web/src/components/UserProvider/UserProvider.spec.tsx b/apps/judicial-system/web/src/components/UserProvider/UserProvider.spec.tsx index 473487bdf741..6d5bd595752a 100644 --- a/apps/judicial-system/web/src/components/UserProvider/UserProvider.spec.tsx +++ b/apps/judicial-system/web/src/components/UserProvider/UserProvider.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { MockedProvider } from '@apollo/client/testing' import { render, screen } from '@testing-library/react' diff --git a/apps/judicial-system/web/src/components/UserProvider/UserProvider.tsx b/apps/judicial-system/web/src/components/UserProvider/UserProvider.tsx index 2613a6c936a9..147dc8848d89 100644 --- a/apps/judicial-system/web/src/components/UserProvider/UserProvider.tsx +++ b/apps/judicial-system/web/src/components/UserProvider/UserProvider.tsx @@ -1,4 +1,4 @@ -import React, { +import { createContext, FC, PropsWithChildren, diff --git a/apps/judicial-system/web/src/components/ViewportProvider/ViewportProvider.tsx b/apps/judicial-system/web/src/components/ViewportProvider/ViewportProvider.tsx index 4defad6eb97a..af15a1c270b6 100644 --- a/apps/judicial-system/web/src/components/ViewportProvider/ViewportProvider.tsx +++ b/apps/judicial-system/web/src/components/ViewportProvider/ViewportProvider.tsx @@ -1,4 +1,10 @@ -import React, { FC, PropsWithChildren } from 'react' +import { + createContext, + FC, + PropsWithChildren, + useEffect, + useState, +} from 'react' export type Rect = { width: number; height: number } @@ -19,21 +25,21 @@ const getCurrentViewport = () => { } } -export const ViewportContext = React.createContext(getCurrentViewport()) +export const ViewportContext = createContext(getCurrentViewport()) export const ViewportProvider: FC = ({ children }) => { - const [state, setState] = React.useState(getCurrentViewport()) + const [state, setState] = useState(getCurrentViewport()) const handleWindowResize = () => { const rect = getCurrentViewport() setState(rect) } - React.useEffect(() => { + useEffect(() => { handleWindowResize() }, []) - React.useEffect(() => { + useEffect(() => { window.addEventListener('resize', handleWindowResize) return () => window.removeEventListener('resize', handleWindowResize) }, []) diff --git a/apps/judicial-system/web/src/routes/Admin/ChangeUser/ChangeUser.tsx b/apps/judicial-system/web/src/routes/Admin/ChangeUser/ChangeUser.tsx index fbb2a385246f..90fef63d46e9 100644 --- a/apps/judicial-system/web/src/routes/Admin/ChangeUser/ChangeUser.tsx +++ b/apps/judicial-system/web/src/routes/Admin/ChangeUser/ChangeUser.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Admin/NewUser/NewUser.tsx b/apps/judicial-system/web/src/routes/Admin/NewUser/NewUser.tsx index 986965410cf4..09b4d419c07f 100644 --- a/apps/judicial-system/web/src/routes/Admin/NewUser/NewUser.tsx +++ b/apps/judicial-system/web/src/routes/Admin/NewUser/NewUser.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Admin/UserForm/UserForm.tsx b/apps/judicial-system/web/src/routes/Admin/UserForm/UserForm.tsx index a7f3eb920d41..6675bfc4fa7e 100644 --- a/apps/judicial-system/web/src/routes/Admin/UserForm/UserForm.tsx +++ b/apps/judicial-system/web/src/routes/Admin/UserForm/UserForm.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useEffect, useState } from 'react' +import { FC, SetStateAction, useCallback, useEffect, useState } from 'react' import InputMask from 'react-input-mask' import { @@ -100,7 +100,7 @@ export const UserForm: FC = ({ field: string, value: string, validations: Validation[], - setErrorMessage: (value: React.SetStateAction) => void, + setErrorMessage: (value: SetStateAction) => void, ) => { setUser({ ...user, @@ -115,7 +115,7 @@ export const UserForm: FC = ({ const validateAndSetError = ( value: string, validations: Validation[], - setErrorMessage: (value: React.SetStateAction) => void, + setErrorMessage: (value: SetStateAction) => void, ) => { const validation = validate([[value, validations]]) diff --git a/apps/judicial-system/web/src/routes/Admin/Users/Users.tsx b/apps/judicial-system/web/src/routes/Admin/Users/Users.tsx index 65c9bcc883b9..d89f1e6cd27c 100644 --- a/apps/judicial-system/web/src/routes/Admin/Users/Users.tsx +++ b/apps/judicial-system/web/src/routes/Admin/Users/Users.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import { useState } from 'react' import { useIntl } from 'react-intl' import cn from 'classnames' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx index fbf3b1177c60..4c407547d670 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useContext, useState } from 'react' +import { FC, useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' @@ -57,7 +57,7 @@ const Completed: FC = () => { const handleNextButtonClick = useCallback(async () => { const allSucceeded = await handleUpload( - uploadFiles.filter((file) => !file.key), + uploadFiles.filter((file) => file.percent === 0), updateUploadFile, ) if (!allSucceeded) { @@ -93,8 +93,11 @@ const Completed: FC = () => { ) const handleCriminalRecordUpdateUpload = useCallback( - (files: File[], type: CaseFileCategory) => { - addUploadFiles(files, type, 'done') + (files: File[]) => { + addUploadFiles(files, { + category: CaseFileCategory.CRIMINAL_RECORD_UPDATE, + status: 'done', + }) }, [addUploadFiles], ) @@ -111,6 +114,9 @@ const Completed: FC = () => { CaseIndictmentRulingDecision.FINE, ].includes(workingCase.indictmentRulingDecision) + const isRuling = + workingCase.indictmentRulingDecision === CaseIndictmentRulingDecision.RULING + const stepIsValid = () => workingCase.indictmentRulingDecision === CaseIndictmentRulingDecision.RULING ? workingCase.defendants?.every( @@ -147,139 +153,127 @@ const Completed: FC = () => { - {!sentToPublicProsecutor && ( - <> - {isRulingOrFine && ( - - - - file.category === CaseFileCategory.CRIMINAL_RECORD_UPDATE, - )} - accept="application/pdf" - header={formatMessage(core.uploadBoxTitle)} - buttonLabel={formatMessage(core.uploadBoxButtonLabel)} - description={formatMessage(core.uploadBoxDescription, { - fileEndings: '.pdf', - })} - onChange={(files) => - handleCriminalRecordUpdateUpload( - files, - CaseFileCategory.CRIMINAL_RECORD_UPDATE, - ) - } - onRemove={(file) => handleRemoveFile(file)} - /> - - )} - {workingCase.indictmentRulingDecision === - CaseIndictmentRulingDecision.RULING && ( - - - {workingCase.defendants?.map((defendant, index) => ( - - - - - { - setAndSendDefendantToServer( - { - defendantId: defendant.id, - caseId: workingCase.id, - serviceRequirement: - ServiceRequirement.NOT_APPLICABLE, - }, - setWorkingCase, - ) - }} - large - backgroundColor="white" - label={formatMessage( - strings.serviceRequirementNotApplicable, - )} - /> - - - { - setAndSendDefendantToServer( - { - defendantId: defendant.id, - caseId: workingCase.id, - serviceRequirement: ServiceRequirement.REQUIRED, - }, - setWorkingCase, - ) - }} - large - backgroundColor="white" - label={formatMessage( - strings.serviceRequirementRequired, - )} - /> - - { - setAndSendDefendantToServer( - { - defendantId: defendant.id, - caseId: workingCase.id, - serviceRequirement: - ServiceRequirement.NOT_REQUIRED, - }, - setWorkingCase, - ) - }} - large - backgroundColor="white" - label={formatMessage( - strings.serviceRequirementNotRequired, - )} - /> - + {!sentToPublicProsecutor && isRulingOrFine && ( + + + + file.category === CaseFileCategory.CRIMINAL_RECORD_UPDATE, + )} + accept="application/pdf" + header={formatMessage(core.uploadBoxTitle)} + buttonLabel={formatMessage(core.uploadBoxButtonLabel)} + description={formatMessage(core.uploadBoxDescription, { + fileEndings: '.pdf', + })} + onChange={handleCriminalRecordUpdateUpload} + onRemove={handleRemoveFile} + /> + + )} + {isRuling && ( + + + {workingCase.defendants?.map((defendant, index) => ( + + + + + { + setAndSendDefendantToServer( + { + defendantId: defendant.id, + caseId: workingCase.id, + serviceRequirement: + ServiceRequirement.NOT_APPLICABLE, + }, + setWorkingCase, + ) + }} + large + backgroundColor="white" + label={formatMessage( + strings.serviceRequirementNotApplicable, + )} + /> + + + { + setAndSendDefendantToServer( + { + defendantId: defendant.id, + caseId: workingCase.id, + serviceRequirement: ServiceRequirement.REQUIRED, + }, + setWorkingCase, + ) + }} + large + backgroundColor="white" + label={formatMessage(strings.serviceRequirementRequired)} + /> - ))} + { + setAndSendDefendantToServer( + { + defendantId: defendant.id, + caseId: workingCase.id, + serviceRequirement: ServiceRequirement.NOT_REQUIRED, + }, + setWorkingCase, + ) + }} + large + backgroundColor="white" + label={formatMessage(strings.serviceRequirementNotRequired)} + /> + - )} - + ))} + )} diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 5df2c6e08162..b33c38ded1c6 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useContext, useEffect, useState } from 'react' +import { FC, useCallback, useContext, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' @@ -516,7 +516,9 @@ const Conclusion: FC = () => { buttonLabel={formatMessage(strings.uploadButtonText)} onChange={(files) => { handleUpload( - addUploadFiles(files, CaseFileCategory.COURT_RECORD), + addUploadFiles(files, { + category: CaseFileCategory.COURT_RECORD, + }), updateUploadFile, ) }} @@ -549,7 +551,9 @@ const Conclusion: FC = () => { buttonLabel={formatMessage(strings.uploadButtonText)} onChange={(files) => { handleUpload( - addUploadFiles(files, CaseFileCategory.RULING), + addUploadFiles(files, { + category: CaseFileCategory.RULING, + }), updateUploadFile, ) }} diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/SelectConnectedCase.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/SelectConnectedCase.tsx index 6dd92b2c73f0..40f108d5d000 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/SelectConnectedCase.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/SelectConnectedCase.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FC, SetStateAction } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { useIntl } from 'react-intl' import { diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Defender/Defender.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Defender/Defender.tsx index ad7e9f8ca00a..a629abf8760a 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Defender/Defender.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Defender/Defender.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext } from 'react' +import { useCallback, useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Defender/SelectDefender.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Defender/SelectDefender.tsx index 0b8e8d319006..411d8654c9c6 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Defender/SelectDefender.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Defender/SelectDefender.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useContext, useState } from 'react' +import { ChangeEvent, FC, useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { Box, Checkbox, Text } from '@island.is/island-ui/core' @@ -85,7 +85,7 @@ const SelectDefender: FC = ({ defendant }) => { }), )} checked={Boolean(defendant.defenderChoice === DefenderChoice.WAIVE)} - onChange={(event: React.ChangeEvent) => { + onChange={(event: ChangeEvent) => { toggleDefendantWaivesRightToCounsel( workingCase.id, defendant, diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx index d6167467b8fc..62ba4388fa9d 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/ReturnIndictmentCaseModal/ReturnIndictmentCaseModal.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/ReturnIndictmentCaseModal/ReturnIndictmentCaseModal.tsx index 736d236bec2d..a8bb0bde8a2a 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/ReturnIndictmentCaseModal/ReturnIndictmentCaseModal.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/ReturnIndictmentCaseModal/ReturnIndictmentCaseModal.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useState } from 'react' +import { Dispatch, FC, SetStateAction, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { Box, Input } from '@island.is/island-ui/core' @@ -18,7 +18,7 @@ import { strings } from './ReturnIndictmentCaseModal.strings' interface Props { workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> onClose: () => void onComplete: () => void } @@ -54,6 +54,8 @@ const ReturnIndictmentModal: FC = ({ return } + // TODO: Generally, we cannot trust the client date so we should let the server handle this instead. + // There is already precedent for adding eplanatory strings on the server side when transitioning cases. const now = new Date() const prependedReturnedExplanation = `${formatMessage( strings.prependedReturnedExplanation, diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx index 088cd7d1bc14..d90022cc8596 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useContext, useState } from 'react' +import { FC, useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx index b377efc7851a..f6128bdd3cfe 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useState } from 'react' +import { FC, useContext, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/InvestigationCase/Confirmation/Confirmation.tsx b/apps/judicial-system/web/src/routes/Court/InvestigationCase/Confirmation/Confirmation.tsx index 63ecfaf6e0f8..b7b764cec99f 100644 --- a/apps/judicial-system/web/src/routes/Court/InvestigationCase/Confirmation/Confirmation.tsx +++ b/apps/judicial-system/web/src/routes/Court/InvestigationCase/Confirmation/Confirmation.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useState } from 'react' +import { FC, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/InvestigationCase/CourtRecord/CourtRecord.tsx b/apps/judicial-system/web/src/routes/Court/InvestigationCase/CourtRecord/CourtRecord.tsx index 4689506fdbe9..fb14b1171179 100644 --- a/apps/judicial-system/web/src/routes/Court/InvestigationCase/CourtRecord/CourtRecord.tsx +++ b/apps/judicial-system/web/src/routes/Court/InvestigationCase/CourtRecord/CourtRecord.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useContext, useState } from 'react' +import { FC, useCallback, useContext, useState } from 'react' import { IntlShape, useIntl } from 'react-intl' import router from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/InvestigationCase/HearingArrangements/HearingArrangements.tsx b/apps/judicial-system/web/src/routes/Court/InvestigationCase/HearingArrangements/HearingArrangements.tsx index 913df319cd19..3d5e89fa1bf8 100644 --- a/apps/judicial-system/web/src/routes/Court/InvestigationCase/HearingArrangements/HearingArrangements.tsx +++ b/apps/judicial-system/web/src/routes/Court/InvestigationCase/HearingArrangements/HearingArrangements.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/InvestigationCase/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Court/InvestigationCase/Overview/Overview.tsx index 60c7a012ecbf..42e69b18d72e 100644 --- a/apps/judicial-system/web/src/routes/Court/InvestigationCase/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Court/InvestigationCase/Overview/Overview.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' @@ -129,10 +129,10 @@ const Overview = () => { id: 'case-info-section', items: [ policeCaseNumbers, - prosecutorsOffice, requestedCourtDate, - prosecutor(workingCase.type), + prosecutorsOffice, caseType, + prosecutor(workingCase.type), ], columns: 2, }, diff --git a/apps/judicial-system/web/src/routes/Court/InvestigationCase/Ruling/Ruling.tsx b/apps/judicial-system/web/src/routes/Court/InvestigationCase/Ruling/Ruling.tsx index 515d2b03cc74..51e80fa56c8c 100644 --- a/apps/judicial-system/web/src/routes/Court/InvestigationCase/Ruling/Ruling.tsx +++ b/apps/judicial-system/web/src/routes/Court/InvestigationCase/Ruling/Ruling.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/RestrictionCase/Confirmation/Confirmation.tsx b/apps/judicial-system/web/src/routes/Court/RestrictionCase/Confirmation/Confirmation.tsx index 0858eb93ab52..7c3016906d9f 100644 --- a/apps/judicial-system/web/src/routes/Court/RestrictionCase/Confirmation/Confirmation.tsx +++ b/apps/judicial-system/web/src/routes/Court/RestrictionCase/Confirmation/Confirmation.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useState } from 'react' +import { FC, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/RestrictionCase/CourtRecord/CourtRecord.tsx b/apps/judicial-system/web/src/routes/Court/RestrictionCase/CourtRecord/CourtRecord.tsx index c1d0240d67a3..d8a531715f3c 100644 --- a/apps/judicial-system/web/src/routes/Court/RestrictionCase/CourtRecord/CourtRecord.tsx +++ b/apps/judicial-system/web/src/routes/Court/RestrictionCase/CourtRecord/CourtRecord.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useContext, useState } from 'react' +import { FC, useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/RestrictionCase/HearingArrangements/HearingArrangements.tsx b/apps/judicial-system/web/src/routes/Court/RestrictionCase/HearingArrangements/HearingArrangements.tsx index f76f4d5002e6..2951a87b42c2 100644 --- a/apps/judicial-system/web/src/routes/Court/RestrictionCase/HearingArrangements/HearingArrangements.tsx +++ b/apps/judicial-system/web/src/routes/Court/RestrictionCase/HearingArrangements/HearingArrangements.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/RestrictionCase/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Court/RestrictionCase/Overview/Overview.tsx index c4d5062da767..9ab9d322935a 100644 --- a/apps/judicial-system/web/src/routes/Court/RestrictionCase/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Court/RestrictionCase/Overview/Overview.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -60,6 +60,7 @@ export const JudgeOverview = () => { const { uploadState } = useCourtUpload(workingCase, setWorkingCase) const { defendants, + policeCaseNumbers, prosecutor, prosecutorsOffice, requestedCourtDate, @@ -136,10 +137,11 @@ export const JudgeOverview = () => { { id: 'case-info-section', items: [ - prosecutorsOffice, + policeCaseNumbers, requestedCourtDate, - prosecutor(workingCase.type), + prosecutorsOffice, parentCaseValidToDate, + prosecutor(workingCase.type), ], columns: 2, }, diff --git a/apps/judicial-system/web/src/routes/Court/RestrictionCase/Ruling/Ruling.tsx b/apps/judicial-system/web/src/routes/Court/RestrictionCase/Ruling/Ruling.tsx index 2803fc648e8b..53274729dcd5 100644 --- a/apps/judicial-system/web/src/routes/Court/RestrictionCase/Ruling/Ruling.tsx +++ b/apps/judicial-system/web/src/routes/Court/RestrictionCase/Ruling/Ruling.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { IntlShape, useIntl } from 'react-intl' import formatISO from 'date-fns/formatISO' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/components/AppealSections/AppealSections.tsx b/apps/judicial-system/web/src/routes/Court/components/AppealSections/AppealSections.tsx index 8610faf44983..88714f13a5e1 100644 --- a/apps/judicial-system/web/src/routes/Court/components/AppealSections/AppealSections.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/AppealSections/AppealSections.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from 'react' +import { Dispatch, FC, SetStateAction, useState } from 'react' import { useIntl } from 'react-intl' import { Box, Input, RadioButton, Text } from '@island.is/island-ui/core' @@ -24,7 +24,7 @@ import * as styles from './AppealSections.css' interface Props { workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> } const AppealSections: FC = ({ workingCase, setWorkingCase }) => { diff --git a/apps/judicial-system/web/src/routes/Court/components/CasesAwaitingAssignmentTable/CasesAwaitingAssignmentTable.tsx b/apps/judicial-system/web/src/routes/Court/components/CasesAwaitingAssignmentTable/CasesAwaitingAssignmentTable.tsx index 9eee4c8a7e28..2ed9996d4dd0 100644 --- a/apps/judicial-system/web/src/routes/Court/components/CasesAwaitingAssignmentTable/CasesAwaitingAssignmentTable.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/CasesAwaitingAssignmentTable/CasesAwaitingAssignmentTable.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' diff --git a/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.tsx b/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.tsx index 974fde5bc010..d80fb2d81362 100644 --- a/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.tsx @@ -1,4 +1,4 @@ -import React, { +import { Dispatch, FC, SetStateAction, diff --git a/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumber.tsx b/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumber.tsx index 7589489225b4..0b356143b085 100644 --- a/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumber.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumber.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumberInput.tsx b/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumberInput.tsx index 61360b0b75eb..e3fc886e9933 100644 --- a/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumberInput.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumberInput.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FC, SetStateAction, useState } from 'react' +import { Dispatch, FC, SetStateAction, useState } from 'react' import { useIntl } from 'react-intl' import { Box, Button, Input } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Court/components/DraftConclusionModal/DraftConclusionModal.tsx b/apps/judicial-system/web/src/routes/Court/components/DraftConclusionModal/DraftConclusionModal.tsx index 43b04dd162ac..7f4b9567b848 100644 --- a/apps/judicial-system/web/src/routes/Court/components/DraftConclusionModal/DraftConclusionModal.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/DraftConclusionModal/DraftConclusionModal.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' @@ -11,11 +11,9 @@ import { TempCase as Case } from '@island.is/judicial-system-web/src/types' interface Props { workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> isDraftingConclusion: boolean | undefined - setIsDraftingConclusion: React.Dispatch< - React.SetStateAction - > + setIsDraftingConclusion: Dispatch> } const DraftConclusionModal: FC = ({ diff --git a/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/ReceptionAndAssignment.tsx b/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/ReceptionAndAssignment.tsx index bb53202904a7..a43b6c24c4cb 100644 --- a/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/ReceptionAndAssignment.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/ReceptionAndAssignment.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext } from 'react' +import { useCallback, useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/SelectCourtOfficials/SelectCourtOfficials.tsx b/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/SelectCourtOfficials/SelectCourtOfficials.tsx index 10ab8411b8b9..05c1f7227e2b 100644 --- a/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/SelectCourtOfficials/SelectCourtOfficials.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/SelectCourtOfficials/SelectCourtOfficials.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react' +import { useContext } from 'react' import { useIntl } from 'react-intl' import { Box, Select, Tooltip } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Court/components/SubpoenaType/SubpoenaType.tsx b/apps/judicial-system/web/src/routes/Court/components/SubpoenaType/SubpoenaType.tsx index c112327403ba..e30edd9bd129 100644 --- a/apps/judicial-system/web/src/routes/Court/components/SubpoenaType/SubpoenaType.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/SubpoenaType/SubpoenaType.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FC, SetStateAction } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { useIntl } from 'react-intl' import { Box, RadioButton, Text } from '@island.is/island-ui/core' @@ -22,7 +22,7 @@ interface SubpoenaTypeProps { setWorkingCase: Dispatch> updateDefendantState: ( update: UpdateDefendantInput, - setWorkingCase: React.Dispatch>, + setWorkingCase: Dispatch>, ) => void required?: boolean } diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/AppealCase/AppealCase.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/AppealCase/AppealCase.tsx index 2f535a119e64..034a368283de 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/AppealCase/AppealCase.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/AppealCase/AppealCase.tsx @@ -1,4 +1,4 @@ -import { useContext, useState } from 'react' +import { FC, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' import { useRouter } from 'next/router' @@ -38,7 +38,7 @@ import { appealCase as strings } from './AppealCase.strings' type JudgeSelectOption = ReactSelectOption & { judge: User } type AssistantSelectOption = ReactSelectOption & { assistant: User } -const AppealCase = () => { +const AppealCase: FC = () => { const { workingCase, setWorkingCase } = useContext(FormContext) const { updateCase, @@ -185,40 +185,37 @@ const AppealCase = () => { - {defaultJudges.map((judge, index) => { - return ( - - { + const judgeUpdate = (selectedOption as JudgeSelectOption) + .judge.id + const judgeProperty = `appealJudge${index + 1}Id` - handleChange(judgeUpdate, judgeProperty) - }} - required - /> - - ) - })} + handleChange(judgeUpdate, judgeProperty) + }} + required + /> + + ))} diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/Cases/Cases.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/Cases/Cases.tsx index 70327d4f9e05..6a15ae704209 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/Cases/Cases.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/Cases/Cases.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { useIntl } from 'react-intl' import { Box } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/Overview/Overview.spec.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/Overview/Overview.spec.tsx index 119896ed4e33..10f320a30d02 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/Overview/Overview.spec.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/Overview/Overview.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import faker from 'faker' import { MockedProvider } from '@apollo/client/testing' import { render, screen } from '@testing-library/react' diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/Overview/Overview.tsx index 30e4096951e3..ee4b2b49bd93 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/Overview/Overview.tsx @@ -1,9 +1,10 @@ -import React, { useContext } from 'react' +import { useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' import { Box } from '@island.is/island-ui/core' import * as constants from '@island.is/judicial-system/consts' +import { isInvestigationCase } from '@island.is/judicial-system/types' import { AlertBanner, CaseFilesAccordionItem, @@ -44,6 +45,7 @@ const CourtOfAppealOverview = () => { court, judge, registrar, + caseType, } = useInfoCardItems() const handleNavigationTo = (destination: string) => @@ -95,7 +97,10 @@ const CourtOfAppealOverview = () => { court, prosecutor(workingCase.type), judge, - registrar, + ...(isInvestigationCase(workingCase.type) + ? [caseType] + : []), + ...(workingCase.registrar ? [registrar] : []), ], columns: 2, }, diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/Result/Result.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/Result/Result.tsx index 352346984da5..0ccb45708b96 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/Result/Result.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/Result/Result.tsx @@ -1,8 +1,9 @@ -import React, { useContext } from 'react' +import { useContext, useState } from 'react' import { useIntl } from 'react-intl' import { AlertBanner, AlertMessage, Box } from '@island.is/island-ui/core' import * as constants from '@island.is/judicial-system/consts' +import { isInvestigationCase } from '@island.is/judicial-system/types' import { CaseFilesAccordionItem, Conclusion, @@ -30,7 +31,7 @@ type modalTypes = 'reopenCase' | 'none' const CourtOfAppealResult = () => { const { workingCase, setWorkingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) - const [modalVisible, setModalVisible] = React.useState('none') + const [modalVisible, setModalVisible] = useState('none') const { formatMessage } = useIntl() const { user } = useContext(UserContext) @@ -46,6 +47,7 @@ const CourtOfAppealResult = () => { court, judge, registrar, + caseType, appealCaseNumber, appealAssistant, appealJudges, @@ -111,13 +113,18 @@ const CourtOfAppealResult = () => { court, prosecutor(workingCase.type), judge, - ...(registrar ? [registrar] : []), - appealCaseNumber, - appealAssistant, - appealJudges, + ...(isInvestigationCase(workingCase.type) + ? [caseType] + : []), + ...(workingCase.registrar ? [registrar] : []), ], columns: 2, }, + { + id: 'court-of-appeal-section', + items: [appealCaseNumber, appealAssistant, appealJudges], + columns: 2, + }, ]} /> diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/Ruling/Ruling.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/Ruling/Ruling.tsx index 534335762f9f..09a716cf2685 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/Ruling/Ruling.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/Ruling/Ruling.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { ChangeEvent, useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -229,7 +229,9 @@ const CourtOfAppealRuling = () => { buttonLabel={formatMessage(strings.uploadButtonText)} onChange={(files) => { handleUpload( - addUploadFiles(files, CaseFileCategory.APPEAL_COURT_RECORD), + addUploadFiles(files, { + category: CaseFileCategory.APPEAL_COURT_RECORD, + }), updateUploadFile, ) }} @@ -250,7 +252,7 @@ const CourtOfAppealRuling = () => { , + event: ChangeEvent, ): void => { setAndSendCaseToServer( [ @@ -353,7 +355,9 @@ const CourtOfAppealRuling = () => { buttonLabel={formatMessage(strings.uploadButtonText)} onChange={(files) => { handleUpload( - addUploadFiles(files, CaseFileCategory.APPEAL_RULING), + addUploadFiles(files, { + category: CaseFileCategory.APPEAL_RULING, + }), updateUploadFile, ) }} diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/Summary/Summary.spec.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/Summary/Summary.spec.tsx index 8df2b424f0fb..75066f7860ae 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/Summary/Summary.spec.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/Summary/Summary.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import faker from 'faker' import { MockedProvider } from '@apollo/client/testing' import { act, render, screen, within } from '@testing-library/react' diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/Summary/Summary.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/Summary/Summary.tsx index 143e59a97377..4d77a326cd87 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/Summary/Summary.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/Summary/Summary.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useState } from 'react' +import { FC, useContext, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/WithdrawnAppealCase/WithdrawnAppealCase.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/WithdrawnAppealCase/WithdrawnAppealCase.tsx index 07e793dde3a2..34682238544c 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/WithdrawnAppealCase/WithdrawnAppealCase.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/WithdrawnAppealCase/WithdrawnAppealCase.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react' +import { useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -73,7 +73,9 @@ const WithdrawnAppealCase = () => { buttonLabel={formatMessage(strings.uploadBoxButtonLabel)} onChange={(files) => handleUpload( - addUploadFiles(files, CaseFileCategory.APPEAL_COURT_RECORD), + addUploadFiles(files, { + category: CaseFileCategory.APPEAL_COURT_RECORD, + }), updateUploadFile, ) } diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseFilesOverview/CaseFilesOverview.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseFilesOverview/CaseFilesOverview.tsx index 28f42e681c43..1464ce928c02 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseFilesOverview/CaseFilesOverview.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseFilesOverview/CaseFilesOverview.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react' +import { useContext } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseNumberInput/CaseNumberInput.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseNumberInput/CaseNumberInput.tsx index deeb1ea9fa3a..523ee9cd5bc9 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseNumberInput/CaseNumberInput.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseNumberInput/CaseNumberInput.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react' +import { FC, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { Input } from '@island.is/island-ui/core' @@ -11,7 +11,7 @@ import { useCase } from '@island.is/judicial-system-web/src/utils/hooks' import { strings } from './CaseNumberInput.strings' -const CaseNumberInput = () => { +const CaseNumberInput: FC = () => { const { formatMessage } = useIntl() const { workingCase, setWorkingCase } = useContext(FormContext) const [appealCaseNumberErrorMessage, setAppealCaseNumberErrorMessage] = diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseNumbers/CaseNumbers.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseNumbers/CaseNumbers.tsx index 49bbda0b3385..2e149745b8ea 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseNumbers/CaseNumbers.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseNumbers/CaseNumbers.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { Box, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseOverviewHeader/CaseOverviewHeader.tsx b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseOverviewHeader/CaseOverviewHeader.tsx index 4938dcb7f734..0a740e8b5d18 100644 --- a/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseOverviewHeader/CaseOverviewHeader.tsx +++ b/apps/judicial-system/web/src/routes/CourtOfAppeal/components/CaseOverviewHeader/CaseOverviewHeader.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Defender/CaseOverview.tsx b/apps/judicial-system/web/src/routes/Defender/CaseOverview.tsx index 0f321015eaeb..bba664ebdb7f 100644 --- a/apps/judicial-system/web/src/routes/Defender/CaseOverview.tsx +++ b/apps/judicial-system/web/src/routes/Defender/CaseOverview.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react' +import { useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx index 91b69fe242ae..ec6cdd737dd3 100644 --- a/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx +++ b/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useMemo, useState } from 'react' +import { FC, useEffect, useMemo, useState } from 'react' import { useIntl } from 'react-intl' import partition from 'lodash/partition' diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx index 73784ed888b0..169584028e9b 100644 --- a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx +++ b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import cn from 'classnames' import { AnimatePresence } from 'framer-motion' @@ -28,7 +28,7 @@ import { import { CaseListEntry } from '@island.is/judicial-system-web/src/graphql/schema' import { useCaseList, - useSortCases, + useSort, } from '@island.is/judicial-system-web/src/utils/hooks' import * as styles from './DefenderCasesTable.css' @@ -45,8 +45,26 @@ export const DefenderCasesTable: FC = ({ loading, }) => { const { formatMessage } = useIntl() - const { sortedData, requestSort, getClassNamesFor, isActiveColumn } = - useSortCases('created', 'descending', cases) + + const getColumnValue = ( + entry: CaseListEntry, + column: keyof CaseListEntry, + ) => { + if ( + column === 'defendants' && + entry.defendants && + entry.defendants.length > 0 + ) { + return entry.defendants[0].name ?? '' + } + return entry.created + } + const { sortedData, requestSort, getClassNamesFor, isActiveColumn } = useSort( + 'created', + 'descending', + cases, + getColumnValue, + ) const { isOpeningCaseId, LoadingIndicator, showLoading, handleOpenCase } = useCaseList() diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/components/FilterCheckboxes.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/components/FilterCheckboxes.tsx index f2e1a0516e57..042c66cb5dcd 100644 --- a/apps/judicial-system/web/src/routes/Defender/Cases/components/FilterCheckboxes.tsx +++ b/apps/judicial-system/web/src/routes/Defender/Cases/components/FilterCheckboxes.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Box, Checkbox } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/hooks/useSortCases.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/hooks/useSortCases.tsx deleted file mode 100644 index 11b687f55e71..000000000000 --- a/apps/judicial-system/web/src/routes/Defender/Cases/hooks/useSortCases.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useMemo, useState } from 'react' - -import { CaseListEntry } from '@island.is/judicial-system-web/src/graphql/schema' - -const useSortCases = ( - defaultColumn: string, - defaultDirection: 'ascending' | 'descending', - data: CaseListEntry[], -) => { - const [sortConfig, setSortConfig] = useState({ - column: defaultColumn, - direction: defaultDirection, - }) - - const requestSort = (column: string) => { - let direction: 'ascending' | 'descending' = 'ascending' - - if (sortConfig.column === column && sortConfig.direction === 'ascending') { - direction = 'descending' - } - - setSortConfig({ column, direction }) - } - - const getClassNamesFor = (column: string) => { - if (!sortConfig) { - return - } - return sortConfig.column === column ? sortConfig.direction : undefined - } - - const sortedData = useMemo(() => { - if (sortConfig && data) { - return [...data].sort((a, b) => { - const getColumnValue = (entry: CaseListEntry) => { - if ( - sortConfig.column === 'defendant' && - entry.defendants && - entry.defendants.length > 0 - ) { - return entry.defendants[0].name ?? '' - } - return entry.created ?? '' - } - - const compareResult = getColumnValue(a).localeCompare(getColumnValue(b)) - - return sortConfig.direction === 'ascending' - ? compareResult - : -compareResult - }) - } - return data - }, [data, sortConfig]) - - return { sortedData, requestSort, getClassNamesFor } -} - -export default useSortCases diff --git a/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.tsx b/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.tsx index 227fa70896a4..a0f765f07198 100644 --- a/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.tsx +++ b/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react' +import { useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFile/CaseFile.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFile/CaseFile.tsx index c4e91afec79f..63cb249bd6d4 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFile/CaseFile.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFile/CaseFile.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { LayoutGroup } from 'framer-motion' import router from 'next/router' @@ -24,6 +24,8 @@ const CaseFile = () => { const { workingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) const { formatMessage } = useIntl() + const [editCount, setEditCount] = useState(0) + const handleNavigationTo = useCallback( (destination: string) => router.push(`${destination}/${workingCase.id}`), [workingCase.id], @@ -70,6 +72,7 @@ const CaseFile = () => { } subtypes={workingCase.indictmentSubtypes} crimeScenes={workingCase.crimeScenes} + setEditCount={setEditCount} /> ))} @@ -98,6 +101,7 @@ const CaseFile = () => { handleNavigationTo(constants.INDICTMENTS_PROCESSING_ROUTE) } nextIsLoading={isLoadingWorkingCase} + nextIsDisabled={editCount > 0} /> diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx index 55b2f82c0791..91f8ef99a563 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext } from 'react' +import { useCallback, useContext } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' @@ -93,7 +93,9 @@ const CaseFiles = () => { buttonLabel={formatMessage(strings.caseFiles.buttonLabel)} onChange={(files) => handleUpload( - addUploadFiles(files, CaseFileCategory.INDICTMENT), + addUploadFiles(files, { + category: CaseFileCategory.INDICTMENT, + }), updateUploadFile, ) } @@ -116,7 +118,9 @@ const CaseFiles = () => { buttonLabel={formatMessage(strings.caseFiles.buttonLabel)} onChange={(files) => handleUpload( - addUploadFiles(files, CaseFileCategory.CRIMINAL_RECORD), + addUploadFiles(files, { + category: CaseFileCategory.CRIMINAL_RECORD, + }), updateUploadFile, ) } @@ -137,7 +141,9 @@ const CaseFiles = () => { buttonLabel={formatMessage(strings.caseFiles.buttonLabel)} onChange={(files) => handleUpload( - addUploadFiles(files, CaseFileCategory.COST_BREAKDOWN), + addUploadFiles(files, { + category: CaseFileCategory.COST_BREAKDOWN, + }), updateUploadFile, ) } @@ -158,7 +164,7 @@ const CaseFiles = () => { buttonLabel={formatMessage(strings.caseFiles.buttonLabel)} onChange={(files) => handleUpload( - addUploadFiles(files, CaseFileCategory.CASE_FILE), + addUploadFiles(files, { category: CaseFileCategory.CASE_FILE }), updateUploadFile, ) } diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx index 0975682e136c..fc2b4b10cbd2 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react' +import { useCallback, useContext, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence, motion } from 'framer-motion' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/LokeNumberList/LokeNumberList.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/LokeNumberList/LokeNumberList.tsx index 3323c267da08..eb3f14430062 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/LokeNumberList/LokeNumberList.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/LokeNumberList/LokeNumberList.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useEffect, useState } from 'react' +import { FC, useContext, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { Box } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/PoliceCaseInfo/PoliceCaseInfo.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/PoliceCaseInfo/PoliceCaseInfo.tsx index 54df47411c39..8ebfb8969f63 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/PoliceCaseInfo/PoliceCaseInfo.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/PoliceCaseInfo/PoliceCaseInfo.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useEffect, useMemo, useState } from 'react' +import { FC, useContext, useEffect, useMemo, useState } from 'react' import InputMask from 'react-input-mask' import { useIntl } from 'react-intl' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx index f2759ac6e222..a2cc60c4ff1c 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { IntlShape, useIntl } from 'react-intl' import { applyCase } from 'beygla/strict' import { AnimatePresence, motion } from 'framer-motion' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx index b1dfce8ca910..995eb38f5d4c 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo, useState } from 'react' +import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' import InputMask from 'react-input-mask' import { IntlShape, useIntl } from 'react-intl' @@ -43,7 +43,7 @@ import { indictmentCountSubstanceEnum as substanceStrings } from './IndictmentCo interface Props { indictmentCount: TIndictmentCount workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> onChange: ( indictmentCountId: string, updatedIndictmentCount: UpdateIndictmentCount, @@ -52,7 +52,7 @@ interface Props { updateIndictmentCountState: ( indictmentCountId: string, update: UpdateIndictmentCount, - setWorkingCase: React.Dispatch>, + setWorkingCase: Dispatch>, ) => void } diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substance/Substance.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substance/Substance.tsx index 830788c9fada..54f2f0d6749b 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substance/Substance.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substance/Substance.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from 'react' +import { FC, useState } from 'react' import { useIntl } from 'react-intl' import { Input } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substances/Substances.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substances/Substances.tsx index 3cec80f97d44..8a148721c67d 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substances/Substances.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substances/Substances.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo } from 'react' +import { FC, useMemo } from 'react' import { useIntl } from 'react-intl' import { Box, Select } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/DenyIndictmentCaseModal/DenyIndictmentCaseModal.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/DenyIndictmentCaseModal/DenyIndictmentCaseModal.tsx index 8873dcf03388..026f365bf31a 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/DenyIndictmentCaseModal/DenyIndictmentCaseModal.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/DenyIndictmentCaseModal/DenyIndictmentCaseModal.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FC, SetStateAction, useState } from 'react' +import { Dispatch, FC, SetStateAction, useState } from 'react' import { useIntl } from 'react-intl' import { Box, Input } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.strings.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.strings.ts index 24e17b69be86..4bc55575e0a9 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.strings.ts +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.strings.ts @@ -129,4 +129,10 @@ export const overview = { description: 'Texti í hætta við takka í afturkalla modal glugga á Yfirlit ákæru skefi í ákærum.', }), + addDocumentsButtonText: defineMessage({ + id: 'judicial.system.indictments:overview.add_documents_button_text', + defaultMessage: 'Bæta við gögnum', + description: + 'Texti á takka til að bæta við skjölum á Yfirlit ákæru skefi í ákærum.', + }), } diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx index aa8172918088..20ae6fa85667 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useState } from 'react' +import { FC, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' import { useRouter } from 'next/router' @@ -6,6 +6,7 @@ import { useRouter } from 'next/router' import { AlertMessage, Box, + Button, RadioButton, Text, toast, @@ -40,7 +41,7 @@ import DenyIndictmentCaseModal from './DenyIndictmentCaseModal/DenyIndictmentCas import { overview as strings } from './Overview.strings' import * as styles from './Overview.css' -const Overview: FC = () => { +const Overview: FC = () => { const { workingCase, setWorkingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) const { user } = useContext(UserContext) @@ -61,18 +62,24 @@ const Overview: FC = () => { const latestDate = workingCase.courtDate ?? workingCase.arraignmentDate const isIndictmentNew = workingCase.state === CaseState.DRAFT + const isIndictmentWaitingForConfirmation = + workingCase.state === CaseState.WAITING_FOR_CONFIRMATION const isIndictmentSubmitted = workingCase.state === CaseState.SUBMITTED const isIndictmentWaitingForCancellation = workingCase.state === CaseState.WAITING_FOR_CANCELLATION const isIndictmentReceived = workingCase.state === CaseState.RECEIVED const userCanSendIndictmentToCourt = - Boolean(user?.canConfirmIndictment) && - workingCase.state === CaseState.WAITING_FOR_CONFIRMATION + Boolean(user?.canConfirmIndictment) && isIndictmentWaitingForConfirmation const userCanCancelIndictment = - (workingCase.state === CaseState.SUBMITTED || - workingCase.state === CaseState.RECEIVED) && + (isIndictmentSubmitted || isIndictmentReceived) && !workingCase.indictmentDecision + const userCanAddDocuments = + isIndictmentSubmitted || + (isIndictmentReceived && + workingCase.indictmentDecision !== + IndictmentDecision.POSTPONING_UNTIL_VERDICT && + workingCase.indictmentDecision !== IndictmentDecision.COMPLETING) const handleTransition = async (transitionType: CaseTransition) => { const caseTransitioned = await transitionCase( @@ -203,9 +210,36 @@ const Overview: FC = () => { )} - + + {userCanAddDocuments && ( + + + + )} {userCanSendIndictmentToCourt && ( = ({ buttonLabel={formatMessage(strings.inputFileUpload.buttonLabel)} onChange={(files) => handleUpload( - addUploadFiles( - files, - CaseFileCategory.CASE_FILE_RECORD, - undefined, + addUploadFiles(files, { + category: CaseFileCategory.CASE_FILE_RECORD, policeCaseNumber, - ), + }), updateUploadFile, ) } diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx index d28259f0399a..82396a8cf72e 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useContext } from 'react' +import { FC, useCallback, useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Defendant/Defendant.tsx b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Defendant/Defendant.tsx index c1613b3e2ad0..985ca04df5a9 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Defendant/Defendant.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Defendant/Defendant.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useEffect } from 'react' +import { useCallback, useContext, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence, motion } from 'framer-motion' import { useRouter } from 'next/router' @@ -64,7 +64,7 @@ const Defendant = () => { // This state is needed because type is initially set to OHTER on the // workingCase and we need to validate that the user selects an option // from the case type list to allow the user to continue. - const [caseType, setCaseType] = React.useState() + const [caseType, setCaseType] = useState() useEffect(() => { if (workingCase.id) { diff --git a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/HearingArrangements/HearingArrangements.tsx b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/HearingArrangements/HearingArrangements.tsx index c15793998ad3..3ed05c4db58d 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/HearingArrangements/HearingArrangements.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/HearingArrangements/HearingArrangements.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Overview/Overview.tsx index 434625e66d3a..a1ca0086e267 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Overview/Overview.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react' +import { useContext, useState } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' import { useRouter } from 'next/router' @@ -72,7 +72,6 @@ export const Overview = () => { registrar, prosecutor, caseType, - confirmedCourtDate, } = useInfoCardItems() const [modal, setModal] = useState< @@ -191,9 +190,6 @@ export const Overview = () => { ...(workingCase.registrar ? [registrar] : []), prosecutor(workingCase.type), caseType, - ...(workingCase.arraignmentDate?.date - ? [confirmedCourtDate] - : []), ], columns: 2, }, diff --git a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/PoliceDemands/PoliceDemands.tsx b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/PoliceDemands/PoliceDemands.tsx index 35809ece029e..1da6026ff2d2 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/PoliceDemands/PoliceDemands.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/PoliceDemands/PoliceDemands.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { MessageDescriptor, useIntl } from 'react-intl' import router from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/PoliceReport/PoliceReport.tsx b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/PoliceReport/PoliceReport.tsx index 4b6325b1d25a..1873a2a82f29 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/PoliceReport/PoliceReport.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/PoliceReport/PoliceReport.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react' +import { useCallback, useContext, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/Defendant/Defendant.tsx b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/Defendant/Defendant.tsx index ec3a84e5788b..685cf8a440e4 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/Defendant/Defendant.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/Defendant/Defendant.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/HearingArrangements/ArrestDate.tsx b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/HearingArrangements/ArrestDate.tsx index 1c0e4e64d6d2..e7e5df65c306 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/HearingArrangements/ArrestDate.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/HearingArrangements/ArrestDate.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useMemo } from 'react' +import { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react' import { Box, Text } from '@island.is/island-ui/core' import { DateTime } from '@island.is/judicial-system-web/src/components' @@ -11,7 +11,7 @@ import { interface Props { workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> title: string } diff --git a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/HearingArrangements/HearingArrangements.tsx b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/HearingArrangements/HearingArrangements.tsx index 5d3a02462f02..d574cb3d8868 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/HearingArrangements/HearingArrangements.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/HearingArrangements/HearingArrangements.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/Overview/Overview.tsx index 33ea376b5af9..685a888a32fa 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/Overview/Overview.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react' +import { useContext, useState } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' import { useRouter } from 'next/router' @@ -81,7 +81,6 @@ export const Overview = () => { prosecutorsOffice, requestedCourtDate, parentCaseValidToDate, - confirmedCourtDate, } = useInfoCardItems() const handleNextButtonClick = async (caseResentExplanation?: string) => { @@ -198,9 +197,6 @@ export const Overview = () => { ...(workingCase.registrar ? [registrar] : []), prosecutor(workingCase.type), parentCaseValidToDate, - ...(workingCase.arraignmentDate?.date - ? [confirmedCourtDate] - : []), ], columns: 2, }, diff --git a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/PoliceDemands/PoliceDemands.tsx b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/PoliceDemands/PoliceDemands.tsx index 8a19cce44bb3..2b2367ad5b95 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/PoliceDemands/PoliceDemands.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/PoliceDemands/PoliceDemands.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { IntlShape, useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -138,7 +138,7 @@ export const PoliceDemands = () => { useOnceOn(isCaseUpToDate, initialize) - const onDemandsChange = React.useCallback( + const onDemandsChange = useCallback( ( entry: UpdateCase, caseType?: CaseType | null, diff --git a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/PoliceReport/PoliceReport.tsx b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/PoliceReport/PoliceReport.tsx index 25fa1fca8f44..574684a07844 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/PoliceReport/PoliceReport.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/RestrictionCase/PoliceReport/PoliceReport.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react' +import { useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/CaseFiles/CaseFiles.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/CaseFiles/CaseFiles.tsx index 92abe331098c..396051818735 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/CaseFiles/CaseFiles.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/CaseFiles/CaseFiles.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo, useState } from 'react' +import { useContext, useEffect, useMemo, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/CasesAwaitingConfirmationTable/CasesAwaitingConfirmationTable.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/CasesAwaitingConfirmationTable/CasesAwaitingConfirmationTable.tsx index fee2eeb769ed..d75c63a9f223 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/CasesAwaitingConfirmationTable/CasesAwaitingConfirmationTable.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/CasesAwaitingConfirmationTable/CasesAwaitingConfirmationTable.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/DefendantInfo/DefendantInfo.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/DefendantInfo/DefendantInfo.tsx index 76ef60a35a44..f807d897ead7 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/DefendantInfo/DefendantInfo.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/DefendantInfo/DefendantInfo.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' +import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' import InputMask from 'react-input-mask' import { useIntl } from 'react-intl' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseFiles/PoliceCaseFiles.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseFiles/PoliceCaseFiles.tsx index 7e5829a57a53..ec14f884739d 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseFiles/PoliceCaseFiles.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseFiles/PoliceCaseFiles.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { AlertMessage, Box } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseNumbers/PoliceCaseNumbers.spec.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseNumbers/PoliceCaseNumbers.spec.tsx index 62dcafc3c3a0..76c118b8dfde 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseNumbers/PoliceCaseNumbers.spec.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseNumbers/PoliceCaseNumbers.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { IntlProvider } from 'react-intl' import faker from 'faker' import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseNumbers/PoliceCaseNumbers.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseNumbers/PoliceCaseNumbers.tsx index aa394bdf932a..15a8ae0b9057 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseNumbers/PoliceCaseNumbers.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/PoliceCaseNumbers/PoliceCaseNumbers.tsx @@ -1,4 +1,12 @@ -import React, { FC, useCallback, useContext, useEffect, useState } from 'react' +import { + Dispatch, + FC, + SetStateAction, + useCallback, + useContext, + useEffect, + useState, +} from 'react' import { useIntl } from 'react-intl' import { Box, Icon, Tag, Text } from '@island.is/island-ui/core' @@ -16,11 +24,9 @@ import { policeCaseNumber as m } from './PoliceCaseNumbers.strings' interface Props { workingCase: Case - setWorkingCase: React.Dispatch> + setWorkingCase: Dispatch> clientPoliceNumbers?: string[] | null - setClientPoliceNumbers: React.Dispatch< - React.SetStateAction - > + setClientPoliceNumbers: Dispatch> } // Needed so that users can remove all loke numbers in client without syncing to server diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSection.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSection.tsx index a06a9b631da1..5d2252004be8 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSection.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSection.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react' +import { useContext } from 'react' import { Box } from '@island.is/island-ui/core' import { isIndictmentCase } from '@island.is/judicial-system/types' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSectionHeading.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSectionHeading.tsx index fb9c82aadf90..da3a517a61a7 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSectionHeading.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSectionHeading.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Box, Tooltip } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSectionHeightenedSecurity.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSectionHeightenedSecurity.tsx index 399d5c64da8a..fd51e1796d9b 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSectionHeightenedSecurity.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/ProsecutorSection/ProsecutorSectionHeightenedSecurity.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react' +import { useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/RequestCourtDate/RequestCourtDate.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/RequestCourtDate/RequestCourtDate.tsx index fc025fc031c6..418c234d6fc5 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/RequestCourtDate/RequestCourtDate.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/RequestCourtDate/RequestCourtDate.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { Box, Text, Tooltip } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/components/SelectCourt/SelectCourt.tsx b/apps/judicial-system/web/src/routes/Prosecutor/components/SelectCourt/SelectCourt.tsx index 19aa3c654033..b3bef49fa38f 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/components/SelectCourt/SelectCourt.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/components/SelectCourt/SelectCourt.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { useIntl } from 'react-intl' import { Box, Select, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Cases/PublicProsecutorCases.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Cases/PublicProsecutorCases.tsx index dfb56a6cb3d2..acf0f2bd264b 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Cases/PublicProsecutorCases.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Cases/PublicProsecutorCases.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo } from 'react' +import { FC, useMemo } from 'react' import { useIntl } from 'react-intl' import { AlertMessage } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.spec.ts b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.spec.ts index d6346590b97a..327f3cfdd024 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.spec.ts +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.spec.ts @@ -4,7 +4,7 @@ import { isDefendantInfoActionButtonDisabled } from './Overview' describe('Overview', () => { describe('isDefendantInfoActionButtonDisabled', () => { - test('should return true if defendant vervidictViewDate is not null', () => { + test('should return true if defendant service requirement is REQUIRED and defendant vervidictViewDate is not null', () => { const verdictViewDate = '2024-07-08' const serviceRequirement = ServiceRequirement.REQUIRED @@ -17,8 +17,21 @@ describe('Overview', () => { expect(res).toEqual(true) }) - test('should return true if defendant service requirement is NOT_APPLICABLE', () => { + test('should return true if defendant service requirement is REQUIRED and defendant vervidictViewDate is null', () => { const verdictViewDate = null + const serviceRequirement = ServiceRequirement.REQUIRED + + const res = isDefendantInfoActionButtonDisabled({ + id: 'id', + verdictViewDate, + serviceRequirement, + }) + + expect(res).toEqual(false) + }) + + test('should return true if defendant service requirement is NOT_APPLICABLE and defendant vervidictViewDate is not null', () => { + const verdictViewDate = '2024-07-09' const serviceRequirement = ServiceRequirement.NOT_APPLICABLE const res = isDefendantInfoActionButtonDisabled({ @@ -30,6 +43,19 @@ describe('Overview', () => { expect(res).toEqual(true) }) + test('should return true if defendant service requirement is NOT_APPLICABLE and defendant vervidictViewDate is null', () => { + const verdictViewDate = null + const serviceRequirement = ServiceRequirement.NOT_APPLICABLE + + const res = isDefendantInfoActionButtonDisabled({ + id: 'id', + verdictViewDate, + serviceRequirement, + }) + + expect(res).toEqual(false) + }) + test('should return true if defendant service requirement is NOT_REQUIRED', () => { const verdictViewDate = null const serviceRequirement = ServiceRequirement.NOT_REQUIRED diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx index 5b0fb9aa170c..9908cf73787a 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useMemo, useState } from 'react' +import { useCallback, useContext, useMemo, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -39,15 +39,10 @@ import { strings } from './Overview.strings' type VisibleModal = 'REVIEWER_ASSIGNED' | 'DEFENDANT_VIEWS_VERDICT' export const isDefendantInfoActionButtonDisabled = (defendant: Defendant) => { - switch (defendant.serviceRequirement) { - case ServiceRequirement.NOT_APPLICABLE: - case ServiceRequirement.NOT_REQUIRED: - return true - case ServiceRequirement.REQUIRED: - return defendant.verdictViewDate !== null - default: - return false - } + return ( + defendant.serviceRequirement === ServiceRequirement.NOT_REQUIRED || + Boolean(defendant.verdictViewDate) + ) } export const Overview = () => { @@ -89,7 +84,7 @@ export const Overview = () => { const updatedDefendant = { caseId: workingCase.id, defendantId: selectedDefendant.id, - verdictViewDate: formatDateForServer(new Date()), + verdictViewDate: formatDateForServer(new Date()), // TODO: Let the server override this date as we cannot trust the client date } setAndSendDefendantToServer(updatedDefendant, setWorkingCase) @@ -143,20 +138,15 @@ export const Overview = () => { { - setSelectedDefendant(defendant) - setModalVisible('DEFENDANT_VIEWS_VERDICT') - }, - icon: 'mailOpen', - isDisabled: isDefendantInfoActionButtonDisabled, - } - : undefined - } + defendantInfoActionButton={{ + text: fm(strings.displayVerdict), + onClick: (defendant) => { + setSelectedDefendant(defendant) + setModalVisible('DEFENDANT_VIEWS_VERDICT') + }, + icon: 'mailOpen', + isDisabled: isDefendantInfoActionButtonDisabled, + }} displayAppealExpirationInfo={true} /> diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesAwaitingReview.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesAwaitingReview.tsx index a846da6d15a3..5f1cc2d3dac2 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesAwaitingReview.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesAwaitingReview.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesForReview.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesForReview.tsx index aa5e0ca69cf0..cc698bca4dc5 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesForReview.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesForReview.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.tsx index f8f9ed5831d8..9110cf900731 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' @@ -41,7 +41,7 @@ const CasesReviewed: FC = ({ loading, cases }) => { } const getVerdictViewTag = (row: CaseListEntry) => { - const today = new Date() + const today = new Date() // TODO: Let the server determine if the deadline has passed const deadline = new Date(row.indictmentVerdictAppealDeadline ?? '') const variant = !row.indictmentVerdictViewedByAll ? 'red' diff --git a/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.strings.ts b/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.strings.ts new file mode 100644 index 000000000000..12df76225e3a --- /dev/null +++ b/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.strings.ts @@ -0,0 +1,49 @@ +import { defineMessage } from 'react-intl' + +export const strings = { + heading: defineMessage({ + id: 'judicial.system.core:add_files.heading', + defaultMessage: 'Gögn', + description: 'Notaður sem titill á Bæta við gögnum síðu í ákærum.', + }), + uploadFilesHeading: { + id: 'judicial.system.core:add_files.upload_files_heading', + defaultMessage: 'Innsending gagna til dómsins', + description: + 'Notaður sem titill í Innsending gagna til dómsins hluta á Gögn síðu í ákærum.', + }, + uploadFilesDescription: { + id: 'judicial.system.core:add_files.upload_files_description', + defaultMessage: 'Gögnin verða að hafa lýsandi skráarheiti.', + description: + 'Notaður sem texti í Innsending gagna til dómsins hluta á Gögn síðu í ákærum.', + }, + nextButtonText: { + id: 'judicial.system.core:add_files.next_button_text', + defaultMessage: 'Senda gögn', + description: 'Notaður sem texti í Senda gögn takka á Gögn síðu í ákærum', + }, + tryUploadAgain: { + id: 'judicial.system.core:add_files.try_upload_again', + defaultMessage: 'Reyna aftur', + description: 'Notaður sem texti í Reyna aftur takka á Gögn síðu í ákærum', + }, + filesSentModalTitle: { + id: 'judicial.system.core:add_files.files_sent_modal_title', + defaultMessage: 'Gögn send til héraðsdóms', + description: + 'Notaður sem titill í modal glugga þegar gögn eru send á Gögn síðu í ákærum', + }, + filesSentModalText: { + id: 'judicial.system.core:add_files.files_sent_modal_text', + defaultMessage: 'Gögnin eru sýnileg dómstólnum og aðilum máls.', + description: + 'Notaður sem texti í modal glugga þegar gögn eru send á Gögn síðu í ákærum', + }, + filesSentModalPrimaryButtonText: { + id: 'judicial.system.core:add_files.files_sent_modal_primary_button_text', + defaultMessage: 'Halda áfram', + description: + 'Notaður sem texti í takka í modal glugga þegar gögn eru send á Gögn síðu í ákærum', + }, +} diff --git a/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.tsx b/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.tsx new file mode 100644 index 000000000000..a40748e36aa8 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.tsx @@ -0,0 +1,152 @@ +import { FC, useCallback, useContext, useState } from 'react' +import { useIntl } from 'react-intl' +import { useRouter } from 'next/router' + +import { Box, Text } from '@island.is/island-ui/core' +import * as constants from '@island.is/judicial-system/consts' +import { isDefenceUser } from '@island.is/judicial-system/types' +import { titles } from '@island.is/judicial-system-web/messages' +import { + FormContentContainer, + FormContext, + FormFooter, + Modal, + PageHeader, + PageLayout, + ProsecutorCaseInfo, + SectionHeading, + UserContext, +} from '@island.is/judicial-system-web/src/components' +import UploadFiles from '@island.is/judicial-system-web/src/components/UploadFiles/UploadFiles' +import { CaseFileCategory } from '@island.is/judicial-system-web/src/graphql/schema' +import { + useS3Upload, + useUploadFiles, +} from '@island.is/judicial-system-web/src/utils/hooks' + +import { strings } from './AddFiles.strings' + +const AddFiles: FC = () => { + const { workingCase, isLoadingWorkingCase, caseNotFound } = + useContext(FormContext) + const { formatMessage } = useIntl() + const [editCount, setEditCount] = useState(0) + const [visibleModal, setVisibleModal] = useState<'sendFiles'>() + const router = useRouter() + const { user } = useContext(UserContext) + const previousRoute = isDefenceUser(user) + ? `${constants.DEFENDER_INDICTMENT_ROUTE}/${workingCase.id}` + : `${constants.INDICTMENTS_OVERVIEW_ROUTE}/${workingCase.id}` + + const { + uploadFiles, + allFilesDoneOrError, + someFilesError, + addUploadFiles, + removeUploadFile, + updateUploadFile, + } = useUploadFiles() + const { handleUpload } = useS3Upload(workingCase.id) + + const caseFileCategory = isDefenceUser(user) + ? CaseFileCategory.DEFENDANT_CASE_FILE + : CaseFileCategory.PROSECUTOR_CASE_FILE + + const handleChange = (files: File[]) => { + addUploadFiles( + files, + { + category: caseFileCategory, + status: 'done', + displayDate: new Date().toISOString(), + }, + true, + ) + } + + const handleRename = useCallback( + async (fileId: string, newName: string, newDisplayDate: string) => { + const fileToUpdate = uploadFiles.find((file) => file.id === fileId) + + if (!fileToUpdate) { + return + } + + updateUploadFile({ + ...fileToUpdate, + userGeneratedFilename: newName, + displayDate: newDisplayDate, + }) + }, + [updateUploadFile, uploadFiles], + ) + + const handleNextButtonClick = useCallback(async () => { + const allSucceeded = await handleUpload( + uploadFiles.filter((file) => file.percent === 0), + updateUploadFile, + ) + + if (allSucceeded) { + setVisibleModal('sendFiles') + } + }, [handleUpload, updateUploadFile, uploadFiles]) + + return ( + + + + + + {formatMessage(strings.heading)} + + + + + + + + 0 + } + onNextButtonClick={handleNextButtonClick} + /> + + {visibleModal === 'sendFiles' && ( + router.push(previousRoute)} + /> + )} + + ) +} + +export default AddFiles diff --git a/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealCaseFiles.tsx b/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealCaseFiles.tsx index e6417026475d..b872c4f4e79d 100644 --- a/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealCaseFiles.tsx +++ b/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealCaseFiles.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -84,7 +84,7 @@ const AppealFiles = () => { const handleNextButtonClick = useCallback(async () => { const allSucceeded = await handleUpload( - uploadFiles.filter((file) => !file.key), + uploadFiles.filter((file) => file.percent === 0), updateUploadFile, ) @@ -111,8 +111,8 @@ const AppealFiles = () => { } } - const handleChange = (files: File[], type: CaseFileCategory) => { - addUploadFiles(files, type, 'done') + const handleChange = (files: File[]) => { + addUploadFiles(files, { category: appealCaseFilesType, status: 'done' }) } return ( @@ -181,10 +181,8 @@ const AppealFiles = () => { fileEndings: '.pdf', })} buttonLabel={formatMessage(core.uploadBoxButtonLabel)} - onChange={(files) => { - handleChange(files, appealCaseFilesType) - }} - onRemove={(file) => handleRemoveFile(file)} + onChange={handleChange} + onRemove={handleRemoveFile} hideIcons={!allFilesDoneOrError} disabled={!allFilesDoneOrError} /> @@ -204,9 +202,8 @@ const AppealFiles = () => { ? strings.uploadFailedNextButtonText : strings.nextButtonText, )} - nextButtonIcon={undefined} nextIsLoading={!allFilesDoneOrError} - nextIsDisabled={uploadFiles.length === 0} + nextIsDisabled={uploadFiles.length === 0 || !allFilesDoneOrError} nextButtonColorScheme={someFilesError ? 'destructive' : 'default'} /> diff --git a/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealToCourtOfAppeals.tsx b/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealToCourtOfAppeals.tsx index aa7d56383229..72cec8d0db9b 100644 --- a/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealToCourtOfAppeals.tsx +++ b/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealToCourtOfAppeals.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -103,8 +103,8 @@ const AppealToCourtOfAppeals = () => { } } - const handleChange = (files: File[], type: CaseFileCategory) => { - addUploadFiles(files, type, 'done') + const handleChange = (files: File[], category: CaseFileCategory) => { + addUploadFiles(files, { category, status: 'done' }) } return ( diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx index 4e9046e60dc9..ac0bcd019845 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { useIntl } from 'react-intl' import { capitalize } from '@island.is/judicial-system/formatters' diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/AllCases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/AllCases.tsx index fc94c44896cd..54c7d73d79bd 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/AllCases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/AllCases.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import { FC, useContext } from 'react' import { isPublicProsecutorUser } from '@island.is/judicial-system/types' import { UserContext } from '@island.is/judicial-system-web/src/components' diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.spec.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.spec.tsx index 7da99096c234..5a95618ec575 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.spec.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { MockedProvider } from '@apollo/client/testing' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx index d84fc1a7a347..ddc3c79a69f8 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useEffect, useMemo, useState } from 'react' +import { FC, useContext, useEffect, useMemo, useState } from 'react' import { useIntl } from 'react-intl' import { AlertMessage, Box, Select } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx index d6abac9e9175..391d004d76d4 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren } from 'react' +import { FC, PropsWithChildren, ReactNode } from 'react' import { useIntl } from 'react-intl' import format from 'date-fns/format' import parseISO from 'date-fns/parseISO' @@ -18,8 +18,8 @@ import { displayCaseType } from './utils' import * as styles from './MobileCase.css' interface CategoryCardProps { - heading: string | React.ReactNode - tags?: React.ReactNode + heading: string | ReactNode + tags?: ReactNode onClick: () => void isLoading?: boolean } diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx index f5fffe16f172..ca2bd29cd98c 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useMemo } from 'react' +import { FC, useContext, useMemo } from 'react' import { useIntl } from 'react-intl' import partition from 'lodash/partition' diff --git a/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.strings.ts b/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.strings.ts index 1d124d853611..62da4e446ca7 100644 --- a/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.strings.ts +++ b/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.strings.ts @@ -17,4 +17,10 @@ export const strings = defineMessages({ defaultMessage: 'Ljúka yfirlestri', description: 'Notaður sem texti á takka til að loka yfirliti ákæru.', }, + addDocumentsButtonText: { + id: 'judicial.system.indictments:overview.add_documents_button_text', + defaultMessage: 'Bæta við gögnum', + description: + 'Texti á takka til að bæta við skjölum á Yfirlit ákæru skefi í ákærum.', + }, }) diff --git a/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.tsx b/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.tsx index cdfb30f7e17b..7151e95d1b3a 100644 --- a/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.tsx +++ b/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.tsx @@ -1,10 +1,13 @@ -import React, { FC, useCallback, useContext, useState } from 'react' +import { FC, useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' -import { Box } from '@island.is/island-ui/core' +import { Box, Button } from '@island.is/island-ui/core' import * as constants from '@island.is/judicial-system/consts' -import { isCompletedCase } from '@island.is/judicial-system/types' +import { + isCompletedCase, + isDefenceUser, +} from '@island.is/judicial-system/types' import { titles } from '@island.is/judicial-system-web/messages' import { CourtCaseInfo, @@ -50,6 +53,10 @@ const IndictmentOverview: FC = () => { isCompletedCase(workingCase.state) && workingCase.indictmentReviewer?.id === user?.id && Boolean(!workingCase.indictmentReviewDecision) + const canAddFiles = + isDefenceUser(user) && + workingCase.indictmentDecision !== + IndictmentDecision.POSTPONING_UNTIL_VERDICT const handleNavigationTo = useCallback( (destination: string) => router.push(`${destination}/${workingCase.id}`), @@ -119,11 +126,26 @@ const IndictmentOverview: FC = () => { {workingCase.caseFiles && ( )} + {canAddFiles && ( + + + + )} {shouldDisplayReviewDecision && ( { ), }) + // TODO: Pass the real insitution into shareCaseWithAnotherInstitution, no nned for faking values here. setWorkingCase((prevWorkingCase) => ({ ...prevWorkingCase, sharedWithProsecutorsOffice: { diff --git a/apps/judicial-system/web/src/routes/Shared/Statement/Statement.tsx b/apps/judicial-system/web/src/routes/Shared/Statement/Statement.tsx index daa6a3d51127..5924ad4e57ed 100644 --- a/apps/judicial-system/web/src/routes/Shared/Statement/Statement.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Statement/Statement.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -86,7 +86,7 @@ const Statement = () => { const updated = await updateCase( workingCase.id, isDefenceUser(user) - ? { defendantStatementDate: new Date().toISOString() } + ? { defendantStatementDate: new Date().toISOString() } // TODO: Let the server override this date. It is already overriding prosecutorStatementDate. : { prosecutorStatementDate: new Date().toISOString() }, ) @@ -109,8 +109,9 @@ const Statement = () => { removeUploadFile(file) } } - const handleChange = (files: File[], type: CaseFileCategory) => { - addUploadFiles(files, type, 'done') + + const handleChange = (files: File[], category: CaseFileCategory) => { + addUploadFiles(files, { category, status: 'done' }) } return ( diff --git a/apps/judicial-system/web/src/utils/formHelper.ts b/apps/judicial-system/web/src/utils/formHelper.ts index a8f96b84066d..18ad816407aa 100644 --- a/apps/judicial-system/web/src/utils/formHelper.ts +++ b/apps/judicial-system/web/src/utils/formHelper.ts @@ -50,10 +50,11 @@ export const validateAndSetErrorMessage = ( ) => { const validation = validations.validate([[value, validationsToRun]]) - if (!validation.isValid && errorMessageSetter) { + if (errorMessageSetter) { errorMessageSetter(validation.errorMessage) - return } + + return validation.isValid } export const validateAndSet = ( @@ -80,9 +81,13 @@ export const validateAndSendToServer = ( updateCase: (id: string, updateCase: UpdateCase) => void, setErrorMessage?: (value: SetStateAction) => void, ) => { - validateAndSetErrorMessage(validations, value, setErrorMessage) + const isValid = validateAndSetErrorMessage( + validations, + value, + setErrorMessage, + ) - if (theCase.id !== '') { + if (theCase.id !== '' && isValid) { updateCase(theCase.id, { [field]: value }) } } diff --git a/apps/judicial-system/web/src/utils/hooks/index.ts b/apps/judicial-system/web/src/utils/hooks/index.ts index 14e33b8328bf..5b45d181ec23 100644 --- a/apps/judicial-system/web/src/utils/hooks/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/index.ts @@ -17,8 +17,8 @@ export { getAppealDecision, default as useAppealAlertBanner, } from './useAppealAlertBanner' -export { default as useSortCases } from './useSort/useSortCases' -export { default as useSortAppealCases } from './useSort/useSortAppealCases' +export { default as useSort } from './useSort/useSort' + export { useGeoLocation } from './useGeoLocation/useGeoLocation' export { default as useDefendants } from './useDefendants' export { diff --git a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx index a4f6b465fc71..d184a5703fb9 100644 --- a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx +++ b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useMemo, useState } from 'react' +import { useCallback, useContext, useMemo, useState } from 'react' import { useIntl } from 'react-intl' import { motion } from 'framer-motion' import { useRouter } from 'next/router' diff --git a/apps/judicial-system/web/src/utils/hooks/useFileList/index.ts b/apps/judicial-system/web/src/utils/hooks/useFileList/index.ts index 2712f5a9bf25..475d9bd11bca 100644 --- a/apps/judicial-system/web/src/utils/hooks/useFileList/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useFileList/index.ts @@ -1,5 +1,8 @@ import { useContext, useEffect, useMemo, useState } from 'react' +import { useIntl } from 'react-intl' +import { toast } from '@island.is/island-ui/core' +import { errors } from '@island.is/judicial-system-web/messages' import { FormContext, UserContext, @@ -16,6 +19,7 @@ interface Parameters { const useFileList = ({ caseId }: Parameters) => { const { limitedAccess } = useContext(UserContext) const { setWorkingCase } = useContext(FormContext) + const { formatMessage } = useIntl() const [fileNotFound, setFileNotFound] = useState() const [ @@ -29,6 +33,9 @@ const useFileList = ({ caseId }: Parameters) => { window.open(data.getSignedUrl.url, '_blank') } }, + onError: () => { + toast.error(formatMessage(errors.openDocument)) + }, }) const [ @@ -42,6 +49,9 @@ const useFileList = ({ caseId }: Parameters) => { window.open(data.limitedAccessGetSignedUrl.url, '_blank') } }, + onError: () => { + toast.error(formatMessage(errors.openDocument)) + }, }) useEffect(() => { diff --git a/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts b/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts index 9580d95bfe8a..a65e1437d5d3 100644 --- a/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts +++ b/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts @@ -2,7 +2,7 @@ import { useCallback, useContext, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { uuid } from 'uuidv4' -import { toast, UploadFile, UploadFileStatus } from '@island.is/island-ui/core' +import { toast, UploadFile } from '@island.is/island-ui/core' import { UserContext } from '@island.is/judicial-system-web/src/components' import { CaseFile, @@ -46,6 +46,7 @@ export interface TUploadFile extends UploadFile { orderWithinChapter?: number | null displayDate?: string | null policeFileId?: string | null + userGeneratedFilename?: string | null } export interface UploadFileState { @@ -67,6 +68,7 @@ const mapCaseFileToUploadFile = (file: CaseFile): TUploadFile => ({ orderWithinChapter: file.orderWithinChapter, displayDate: file.displayDate, policeFileId: file.policeFileId, + userGeneratedFilename: file.userGeneratedFilename, }) export const useUploadFiles = (files?: CaseFile[] | null) => { @@ -89,9 +91,8 @@ export const useUploadFiles = (files?: CaseFile[] | null) => { const addUploadFiles = ( files: File[], - category?: CaseFileCategory, - status?: UploadFileStatus, - policeCaseNumber?: string, + overRides?: Partial, + setUserGeneratedFilename = false, ) => { // We generate an id for each file so that we find the file again when // updating the file's progress and onRetry. @@ -102,10 +103,9 @@ export const useUploadFiles = (files?: CaseFile[] | null) => { type: file.type, size: file.size, percent: 0, - status, - category, - policeCaseNumber, originalFileObj: file, + userGeneratedFilename: setUserGeneratedFilename ? file.name : undefined, + ...overRides, })) setUploadFiles((previous) => [...uploadFiles, ...previous]) @@ -113,12 +113,13 @@ export const useUploadFiles = (files?: CaseFile[] | null) => { return uploadFiles } - const updateUploadFile = (file: TUploadFile, newId?: string) => + const updateUploadFile = (file: TUploadFile, newId?: string) => { setUploadFiles((previous) => previous.map((f) => f.id === file.id ? { ...f, ...file, id: newId ?? file.id } : f, ), ) + } const removeUploadFile = (file: TUploadFile) => setUploadFiles((previous) => @@ -201,6 +202,41 @@ const useS3Upload = (caseId: string) => { const [limitedAccessDeleteFile] = useLimitedAccessDeleteFileMutation() const [uploadPoliceCaseFile] = useUploadPoliceCaseFileMutation() + const getPresignedPost = useCallback( + async (file: TUploadFile) => { + const mutation = limitedAccess + ? limitedAccessCreatePresignedPost + : createPresignedPost + + const { data } = await mutation({ + variables: { + input: { + caseId, + fileName: file.name.normalize(), + type: file.type ?? '', + }, + }, + }) + + const presignedPost = limitedAccess + ? (data as LimitedAccessCreatePresignedPostMutation) + ?.limitedAccessCreatePresignedPost + : (data as CreatePresignedPostMutation)?.createPresignedPost + + if (!presignedPost?.fields?.key) { + throw Error('Failed to get presigned post') + } + + return presignedPost + }, + [ + limitedAccess, + limitedAccessCreatePresignedPost, + createPresignedPost, + caseId, + ], + ) + const addFileToCaseState = useCallback( async (file: TUploadFile) => { const mutation = limitedAccess ? limitedAccessCreateFile : createFile @@ -218,6 +254,7 @@ const useS3Upload = (caseId: string) => { orderWithinChapter: file.orderWithinChapter, displayDate: file.displayDate, policeFileId: file.policeFileId, + userGeneratedFilename: file.userGeneratedFilename, }, }, }) @@ -240,33 +277,6 @@ const useS3Upload = (caseId: string) => { files: TUploadFile[], updateFile: (file: TUploadFile, newId?: string) => void, ) => { - const mutation = limitedAccess - ? limitedAccessCreatePresignedPost - : createPresignedPost - - const getPresignedPost = async (file: TUploadFile) => { - const { data } = await mutation({ - variables: { - input: { - caseId, - fileName: file.name.normalize(), - type: file.type ?? '', - }, - }, - }) - - const presignedPost = limitedAccess - ? (data as LimitedAccessCreatePresignedPostMutation) - ?.limitedAccessCreatePresignedPost - : (data as CreatePresignedPostMutation)?.createPresignedPost - - if (!presignedPost?.fields?.key) { - throw Error('Failed to get presigned post') - } - - return presignedPost - } - const promises = files.map(async (file) => { try { updateFile({ ...file, status: 'uploading' }) @@ -306,18 +316,11 @@ const useS3Upload = (caseId: string) => { results.every((result) => result), ) }, - [ - limitedAccess, - limitedAccessCreatePresignedPost, - createPresignedPost, - caseId, - addFileToCaseState, - formatMessage, - ], + [getPresignedPost, addFileToCaseState, formatMessage], ) const handleUploadFromPolice = useCallback( - async ( + ( files: TUploadFile[], callback: (file: TUploadFile, newId?: string) => void, ) => { @@ -372,7 +375,7 @@ const useS3Upload = (caseId: string) => { ) => { callback({ ...file, percent: 1, status: 'uploading' }) - handleUpload([file], callback) + return handleUpload([file], callback) }, [handleUpload], ) @@ -385,7 +388,7 @@ const useS3Upload = (caseId: string) => { id: fileId, }, } - const resopnse: { success: boolean; __typename: 'DeleteFileResponse' } = { + const response: { success: boolean; __typename: 'DeleteFileResponse' } = { success: true, __typename: 'DeleteFileResponse', } @@ -393,11 +396,11 @@ const useS3Upload = (caseId: string) => { return limitedAccess ? limitedAccessDeleteFile({ variables, - optimisticResponse: { limitedAccessDeleteFile: resopnse }, + optimisticResponse: { limitedAccessDeleteFile: response }, }) : deleteFile({ variables, - optimisticResponse: { deleteFile: resopnse }, + optimisticResponse: { deleteFile: response }, }) }, [caseId, limitedAccess, limitedAccessDeleteFile, deleteFile], diff --git a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts index babd157c2531..5608d706ab7e 100644 --- a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts @@ -400,7 +400,8 @@ const useSections = ( const { id, type, state } = workingCase const substepsShouldBeHidden = state === CaseState.RECEIVED || - state === CaseState.WAITING_FOR_CANCELLATION + state === CaseState.WAITING_FOR_CANCELLATION || + router.pathname === `${constants.INDICTMENTS_ADD_FILES_ROUTE}/[id]` const isTrafficViolation = isTrafficViolationCase(workingCase) return { diff --git a/apps/judicial-system/web/src/utils/hooks/useSections/useSections.spec.tsx b/apps/judicial-system/web/src/utils/hooks/useSections/useSections.spec.tsx index 1d09bf1918dc..e0f0ce3a4638 100644 --- a/apps/judicial-system/web/src/utils/hooks/useSections/useSections.spec.tsx +++ b/apps/judicial-system/web/src/utils/hooks/useSections/useSections.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { IntlProvider } from 'react-intl' import faker from 'faker' import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client' diff --git a/apps/judicial-system/web/src/utils/hooks/useSort/useSort.ts b/apps/judicial-system/web/src/utils/hooks/useSort/useSort.ts new file mode 100644 index 000000000000..a1fed64fdfcb --- /dev/null +++ b/apps/judicial-system/web/src/utils/hooks/useSort/useSort.ts @@ -0,0 +1,59 @@ +import { useCallback, useMemo, useState } from 'react' + +import { compareLocaleIS } from '../../sortHelper' + +type SortDirection = 'ascending' | 'descending' + +interface SortConfig { + column: keyof T + direction: SortDirection +} + +const useSort = ( + defaultColumn: keyof T, + defaultDirection: SortDirection, + data: T[], + getColumnValue: (entry: T, column: keyof T) => string | null | undefined, +) => { + const [sortConfig, setSortConfig] = useState>({ + column: defaultColumn, + direction: defaultDirection, + }) + + const requestSort = useCallback((column: keyof T) => { + setSortConfig((prevConfig) => ({ + column, + direction: + prevConfig.column === column && prevConfig.direction === 'ascending' + ? 'descending' + : 'ascending', + })) + }, []) + + const sortedData = useMemo(() => { + return [...data].sort((a, b) => { + const compareResult = compareLocaleIS( + getColumnValue(a, sortConfig.column), + getColumnValue(b, sortConfig.column), + ) + return sortConfig.direction === 'ascending' + ? compareResult + : -compareResult + }) + }, [data, sortConfig, getColumnValue]) + + const getClassNamesFor = useCallback( + (column: keyof T) => + sortConfig.column === column ? sortConfig.direction : undefined, + [sortConfig], + ) + + const isActiveColumn = useCallback( + (column: keyof T) => sortConfig.column === column, + [sortConfig], + ) + + return { sortedData, requestSort, getClassNamesFor, isActiveColumn } +} + +export default useSort diff --git a/apps/judicial-system/web/src/utils/hooks/useSort/useSortAppealCases.ts b/apps/judicial-system/web/src/utils/hooks/useSort/useSortAppealCases.ts deleted file mode 100644 index 65204872f27b..000000000000 --- a/apps/judicial-system/web/src/utils/hooks/useSort/useSortAppealCases.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { useMemo, useState } from 'react' - -import { CaseListEntry } from '@island.is/judicial-system-web/src/graphql/schema' -import { compareLocaleIS } from '@island.is/judicial-system-web/src/utils/sortHelper' - -const useSortAppealCases = ( - defaultColumn: string, - defaultDirection: 'ascending' | 'descending', - data: CaseListEntry[], -) => { - const [sortConfig, setSortConfig] = useState({ - column: defaultColumn, - direction: defaultDirection, - }) - - const requestSort = (column: string) => { - let direction: 'ascending' | 'descending' = 'ascending' - - if (sortConfig.column === column && sortConfig.direction === 'ascending') { - direction = 'descending' - } - - setSortConfig({ column, direction }) - } - - const isActiveColumn = (column: string) => { - if (!sortConfig) { - return false - } - return sortConfig.column === column - } - - const getClassNamesFor = (column: string) => { - if (!sortConfig) { - return - } - return sortConfig.column === column ? sortConfig.direction : undefined - } - - const sortedData = useMemo(() => { - if (sortConfig && data) { - return [...data].sort((a, b) => { - const getColumnValue = (entry: CaseListEntry): string => { - if ( - sortConfig.column === 'defendant' && - entry.defendants && - entry.defendants.length > 0 - ) { - return entry.defendants[0].name ?? '' - } - return entry.appealedDate ?? '' - } - const compareResult = compareLocaleIS( - getColumnValue(a), - getColumnValue(b), - ) - return sortConfig.direction === 'ascending' - ? compareResult - : -compareResult - }) - } - return data - }, [data, sortConfig]) - - return { sortedData, requestSort, getClassNamesFor, isActiveColumn } -} - -export default useSortAppealCases diff --git a/apps/judicial-system/web/src/utils/hooks/useSort/useSortCases.ts b/apps/judicial-system/web/src/utils/hooks/useSort/useSortCases.ts deleted file mode 100644 index 9fa8023ba261..000000000000 --- a/apps/judicial-system/web/src/utils/hooks/useSort/useSortCases.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { useMemo, useState } from 'react' - -import { CaseListEntry } from '@island.is/judicial-system-web/src/graphql/schema' -import { sortableTableColumn } from '@island.is/judicial-system-web/src/types' -import { compareLocaleIS } from '@island.is/judicial-system-web/src/utils/sortHelper' - -const useSortCases = ( - defaultColumn: sortableTableColumn, - defaultDirection: 'ascending' | 'descending', - data: CaseListEntry[], -) => { - const [sortConfig, setSortConfig] = useState({ - column: defaultColumn, - direction: defaultDirection, - }) - - const requestSort = (column: sortableTableColumn) => { - let direction: 'ascending' | 'descending' = 'ascending' - - if (sortConfig.column === column && sortConfig.direction === 'ascending') { - direction = 'descending' - } - - setSortConfig({ column, direction }) - } - - const getClassNamesFor = (column: sortableTableColumn) => { - if (!sortConfig) { - return - } - return sortConfig.column === column ? sortConfig.direction : undefined - } - - const isActiveColumn = (column: sortableTableColumn) => { - if (!sortConfig) { - return false - } - return sortConfig.column === column - } - - const sortedData = useMemo(() => { - if (sortConfig && data) { - return [...data].sort((a, b) => { - const getColumnValue = (entry: CaseListEntry) => { - if ( - sortConfig.column === 'defendants' && - entry.defendants && - entry.defendants.length > 0 - ) { - return entry.defendants[0].name ?? '' - } - return entry.created - } - - const compareResult = compareLocaleIS( - getColumnValue(a), - getColumnValue(b), - ) - - return sortConfig.direction === 'ascending' - ? compareResult - : -compareResult - }) - } - return data - }, [data, sortConfig]) - - return { sortedData, requestSort, getClassNamesFor, isActiveColumn } -} - -export default useSortCases diff --git a/apps/judicial-system/web/src/utils/restrictions.spec.tsx b/apps/judicial-system/web/src/utils/restrictions.spec.tsx index bfb87d7e6da6..cc602b2eb395 100644 --- a/apps/judicial-system/web/src/utils/restrictions.spec.tsx +++ b/apps/judicial-system/web/src/utils/restrictions.spec.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import { FC } from 'react' import { createIntl, IntlFormatters, useIntl } from 'react-intl' import { MockedProvider } from '@apollo/client/testing' import { getDefaultNormalizer, render, screen } from '@testing-library/react' diff --git a/apps/judicial-system/web/src/utils/stepHelper.ts b/apps/judicial-system/web/src/utils/stepHelper.ts index e410eac190bb..5ea47b22ea76 100644 --- a/apps/judicial-system/web/src/utils/stepHelper.ts +++ b/apps/judicial-system/web/src/utils/stepHelper.ts @@ -77,7 +77,7 @@ export const createCaseResentExplanation = ( workingCase: Case, explanation?: string, ) => { - const now = new Date() + const now = new Date() // TODO: Find a way to set this message server side as we cannot trust the client date. return `${ workingCase.caseResentExplanation diff --git a/apps/judicial-system/web/src/utils/testHelpers.tsx b/apps/judicial-system/web/src/utils/testHelpers.tsx index a8bfa94c562c..99e942d93f5c 100644 --- a/apps/judicial-system/web/src/utils/testHelpers.tsx +++ b/apps/judicial-system/web/src/utils/testHelpers.tsx @@ -1,4 +1,4 @@ -import React, { FC, PropsWithChildren, ReactNode } from 'react' +import { FC, PropsWithChildren, ReactNode } from 'react' import { createIntl, IntlProvider } from 'react-intl' import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client' diff --git a/apps/judicial-system/web/src/utils/utils.spec.tsx b/apps/judicial-system/web/src/utils/utils.spec.tsx index 3953e36f3c7f..a65cc31ecc11 100644 --- a/apps/judicial-system/web/src/utils/utils.spec.tsx +++ b/apps/judicial-system/web/src/utils/utils.spec.tsx @@ -1,15 +1,16 @@ -import React from 'react' import faker from 'faker' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { + Case, Gender, Notification, NotificationType, } from '@island.is/judicial-system-web/src/graphql/schema' import * as formatters from './formatters' +import { validateAndSendToServer } from './formHelper' import { getAppealEndDate, getShortGender, @@ -330,6 +331,53 @@ describe('Step helper', () => { }) }) + describe('validateAndSendToServer', () => { + test('should call the updateCase function with the correct parameters', () => { + // Arrange + const spy = jest.fn() + const fieldToUpdate = 'courtCaseNumber' + const value = '1234/1234' + const id = faker.datatype.uuid() + const theCase = { id } as Case + const update = { + courtCaseNumber: value, + } + + // Act + validateAndSendToServer( + fieldToUpdate, + value, + ['appeal-case-number-format'], + theCase, + spy, + ) + + // Assert + expect(spy).toBeCalledWith(id, update) + }) + + test('should not call the updateCase function if the value is invalid', () => { + // Arrange + const spy = jest.fn() + const fieldToUpdate = 'courtCaseNumber' + const value = '12341234' + const id = faker.datatype.uuid() + const theCase = { id } as Case + + // Act + validateAndSendToServer( + fieldToUpdate, + value, + ['appeal-case-number-format'], + theCase, + spy, + ) + + // Assert + expect(spy).not.toHaveBeenCalled() + }) + }) + test('should return false if no notification is found of a spesific notification type', () => { // Arrange const email = faker.internet.email() diff --git a/apps/native/app/android/app/build.gradle b/apps/native/app/android/app/build.gradle index c0019976ad31..bcdf28ccfabb 100644 --- a/apps/native/app/android/app/build.gradle +++ b/apps/native/app/android/app/build.gradle @@ -103,7 +103,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode getMyVersionCode(143) - versionName "1.4.0" + versionName "1.4.1" manifestPlaceholders = [ appAuthRedirectScheme: "is.island.app" // project.config.get("BUNDLE_ID_ANDROID") ] diff --git a/apps/native/app/android/app/src/main/assets/fonts/IBMPlexSans-Regular.ttf b/apps/native/app/android/app/src/main/assets/fonts/IBMPlexSans.ttf similarity index 100% rename from apps/native/app/android/app/src/main/assets/fonts/IBMPlexSans-Regular.ttf rename to apps/native/app/android/app/src/main/assets/fonts/IBMPlexSans.ttf diff --git a/apps/native/app/assets/fonts/IBMPlexSans.ttf b/apps/native/app/assets/fonts/IBMPlexSans.ttf new file mode 100644 index 000000000000..702c637f51ed Binary files /dev/null and b/apps/native/app/assets/fonts/IBMPlexSans.ttf differ diff --git a/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj b/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj index 2c9b5ace6acf..b13fc775ca43 100644 --- a/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj +++ b/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj @@ -24,7 +24,7 @@ E367D440298DF24D007054A4 /* IBMPlexSans-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E367D432298DF24C007054A4 /* IBMPlexSans-LightItalic.ttf */; }; E367D441298DF24D007054A4 /* IBMPlexSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E367D433298DF24C007054A4 /* IBMPlexSans-Light.ttf */; }; E367D442298DF24D007054A4 /* IBMPlexSans-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E367D434298DF24C007054A4 /* IBMPlexSans-ExtraLight.ttf */; }; - E367D443298DF24D007054A4 /* IBMPlexSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E367D435298DF24C007054A4 /* IBMPlexSans-Regular.ttf */; }; + E367D443298DF24D007054A4 /* IBMPlexSans.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E367D435298DF24C007054A4 /* IBMPlexSans.ttf */; }; E367D444298DF24D007054A4 /* IBMPlexSans-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E367D436298DF24D007054A4 /* IBMPlexSans-Thin.ttf */; }; E367D445298DF24D007054A4 /* IBMPlexSans-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E367D437298DF24D007054A4 /* IBMPlexSans-MediumItalic.ttf */; }; E367D446298DF24D007054A4 /* IBMPlexSans-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E367D438298DF24D007054A4 /* IBMPlexSans-Medium.ttf */; }; @@ -48,7 +48,7 @@ 5BD3F84B09D28615BB8D415C /* Pods-IslandApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IslandApp.release.xcconfig"; path = "Target Support Files/Pods-IslandApp/Pods-IslandApp.release.xcconfig"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = IslandApp/LaunchScreen.storyboard; sourceTree = ""; }; 8DCA6BB3F9BC0A645D723048 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-IslandApp/ExpoModulesProvider.swift"; sourceTree = ""; }; - 941A2A9F3CFC44F91E0A673C /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = IslandApp/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 941A2A9F3CFC44F91E0A673C /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = IslandApp/PrivacyInfo.xcprivacy; sourceTree = ""; }; E367D422298A854E007054A4 /* IslandApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "IslandApp-Bridging-Header.h"; sourceTree = ""; }; E367D423298A854E007054A4 /* noop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = noop.swift; sourceTree = ""; }; E367D425298AB63B007054A4 /* RNIsland.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNIsland.m; sourceTree = ""; }; @@ -63,7 +63,7 @@ E367D432298DF24C007054A4 /* IBMPlexSans-LightItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexSans-LightItalic.ttf"; path = "../../assets/fonts/IBMPlexSans-LightItalic.ttf"; sourceTree = ""; }; E367D433298DF24C007054A4 /* IBMPlexSans-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexSans-Light.ttf"; path = "../../assets/fonts/IBMPlexSans-Light.ttf"; sourceTree = ""; }; E367D434298DF24C007054A4 /* IBMPlexSans-ExtraLight.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexSans-ExtraLight.ttf"; path = "../../assets/fonts/IBMPlexSans-ExtraLight.ttf"; sourceTree = ""; }; - E367D435298DF24C007054A4 /* IBMPlexSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexSans-Regular.ttf"; path = "../../assets/fonts/IBMPlexSans-Regular.ttf"; sourceTree = ""; }; + E367D435298DF24C007054A4 /* IBMPlexSans.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = IBMPlexSans.ttf; path = ../../assets/fonts/IBMPlexSans.ttf; sourceTree = ""; }; E367D436298DF24D007054A4 /* IBMPlexSans-Thin.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexSans-Thin.ttf"; path = "../../assets/fonts/IBMPlexSans-Thin.ttf"; sourceTree = ""; }; E367D437298DF24D007054A4 /* IBMPlexSans-MediumItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexSans-MediumItalic.ttf"; path = "../../assets/fonts/IBMPlexSans-MediumItalic.ttf"; sourceTree = ""; }; E367D438298DF24D007054A4 /* IBMPlexSans-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexSans-Medium.ttf"; path = "../../assets/fonts/IBMPlexSans-Medium.ttf"; sourceTree = ""; }; @@ -189,7 +189,7 @@ E367D432298DF24C007054A4 /* IBMPlexSans-LightItalic.ttf */, E367D438298DF24D007054A4 /* IBMPlexSans-Medium.ttf */, E367D437298DF24D007054A4 /* IBMPlexSans-MediumItalic.ttf */, - E367D435298DF24C007054A4 /* IBMPlexSans-Regular.ttf */, + E367D435298DF24C007054A4 /* IBMPlexSans.ttf */, E367D42F298DF24C007054A4 /* IBMPlexSans-SemiBold.ttf */, E367D42D298DF24C007054A4 /* IBMPlexSans-SemiBoldItalic.ttf */, E367D436298DF24D007054A4 /* IBMPlexSans-Thin.ttf */, @@ -269,7 +269,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, E367D43C298DF24D007054A4 /* IBMPlexSans-BoldItalic.ttf in Resources */, E367D445298DF24D007054A4 /* IBMPlexSans-MediumItalic.ttf in Resources */, - E367D443298DF24D007054A4 /* IBMPlexSans-Regular.ttf in Resources */, + E367D443298DF24D007054A4 /* IBMPlexSans.ttf in Resources */, E367D43E298DF24D007054A4 /* IBMPlexSans-ThinItalic.ttf in Resources */, E367D444298DF24D007054A4 /* IBMPlexSans-Thin.ttf in Resources */, E367D442298DF24D007054A4 /* IBMPlexSans-ExtraLight.ttf in Resources */, @@ -550,10 +550,7 @@ "-DFOLLY_MOBILE=1", "-DFOLLY_USE_LIBCPP=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -625,10 +622,7 @@ "-DFOLLY_MOBILE=1", "-DFOLLY_USE_LIBCPP=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -748,10 +742,7 @@ "-DFOLLY_MOBILE=1", "-DFOLLY_USE_LIBCPP=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/apps/native/app/ios/IslandApp/Info.plist b/apps/native/app/ios/IslandApp/Info.plist index f3036261e8f6..b074b56c5c00 100644 --- a/apps/native/app/ios/IslandApp/Info.plist +++ b/apps/native/app/ios/IslandApp/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.0 + 1.4.1 CFBundleSignature ???? CFBundleURLTypes diff --git a/apps/native/app/ios/Podfile.lock b/apps/native/app/ios/Podfile.lock index 0030bde88e0c..d751cd655a22 100644 --- a/apps/native/app/ios/Podfile.lock +++ b/apps/native/app/ios/Podfile.lock @@ -1175,6 +1175,8 @@ PODS: - React-Core - react-native-cookies (6.2.1): - React-Core + - react-native-date-picker (5.0.4): + - React-Core - react-native-mmkv-storage (0.9.1): - MMKV (= 1.2.13) - React-Core @@ -1620,6 +1622,7 @@ DEPENDENCIES: - react-native-app-auth (from `../node_modules/react-native-app-auth`) - react-native-blob-util (from `../node_modules/react-native-blob-util`) - "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)" + - react-native-date-picker (from `../node_modules/react-native-date-picker`) - react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-passkey (from `../node_modules/react-native-passkey`) @@ -1800,6 +1803,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-blob-util" react-native-cookies: :path: "../node_modules/@react-native-cookies/cookies" + react-native-date-picker: + :path: "../node_modules/react-native-date-picker" react-native-mmkv-storage: :path: "../node_modules/react-native-mmkv-storage" react-native-netinfo: @@ -1978,6 +1983,7 @@ SPEC CHECKSUMS: react-native-app-auth: 63fa4e58c5bd29aeb974d3a06a23c5858322d533 react-native-blob-util: 18b510205c080a453574a7d2344d64673d0ad9af react-native-cookies: f54fcded06bb0cda05c11d86788020b43528a26c + react-native-date-picker: 6891317e850deae5b53d51355226e07a495aba61 react-native-mmkv-storage: cfb6854594cfdc5f7383a9e464bb025417d1721c react-native-netinfo: bdb108d340cdb41875c9ced535977cac6d2ff321 react-native-passkey: 29ff814a83dfd4311478498e71a801dce68043ac diff --git a/apps/native/app/package.json b/apps/native/app/package.json index 71f77cbd3b46..b1604aa07f38 100644 --- a/apps/native/app/package.json +++ b/apps/native/app/package.json @@ -68,6 +68,7 @@ "react-native-app-auth": "7.2.0", "react-native-blob-util": "0.19.9", "react-native-code-push": "8.2.2", + "react-native-date-picker": "5.0.4", "react-native-device-info": "10.3.0", "react-native-dialogs": "1.1.2", "react-native-gesture-handler": "2.17.1", diff --git a/apps/native/app/src/graphql/queries/inbox.graphql b/apps/native/app/src/graphql/queries/inbox.graphql index 2ecdf2458686..ac0fe35c5927 100644 --- a/apps/native/app/src/graphql/queries/inbox.graphql +++ b/apps/native/app/src/graphql/queries/inbox.graphql @@ -22,6 +22,17 @@ query ListDocuments($input: DocumentsV2DocumentsInput!) { } } +query GetDocumentsCategoriesAndSenders { + getDocumentCategories { + id + name + } + getDocumentSenders { + id + name + } +} + query GetDocument($input: DocumentInput!) { documentV2(input: $input) { ...ListDocument diff --git a/apps/native/app/src/messages/en.ts b/apps/native/app/src/messages/en.ts index 5dfabc036a6e..389df9de9346 100644 --- a/apps/native/app/src/messages/en.ts +++ b/apps/native/app/src/messages/en.ts @@ -195,10 +195,7 @@ export const en: TranslatedMessages = { 'inbox.emptyListTitle': 'There are currently no documents', 'inbox.emptyListDescription': 'When you receive electronic documents from the government, they will appear here.', - 'inbox.filterButtonTitle': 'Filter', - 'inbox.filterOpenedTagTitle': 'Unread', - 'inbox.filterArchivedTagTitle': 'Archived', - 'inbox.filterStarredTagTitle': 'Starred', + 'inbox.markAllAsReadPromptTitle': 'Do you want to mark all as read?', 'inbox.markAllAsReadPromptDescription': 'This action cannot be undone', 'inbox.markAllAsReadPromptCancel': 'Cancel', @@ -211,6 +208,20 @@ export const en: TranslatedMessages = { 'inboxFilters.unreadOnly': 'Show only unread', 'inboxFilters.starred': 'Starred', 'inboxFilters.archived': 'Archived', + 'inbox.filterButtonTitle': 'Filter', + 'inbox.filterOpenedTagTitle': 'Unread', + 'inbox.filterArchivedTagTitle': 'Archived', + 'inbox.filterStarredTagTitle': 'Starred', + 'inbox.filterOrganizationTitle': 'Organization', + 'inbox.filterCategoryTitle': 'Category', + 'inbox.filterDatesTitle': 'Dates', + 'inbox.filterClearButton': 'Clear', + 'inbox.filterApplyButton': 'Apply filters', + 'inbox.filterDateFromLabel': 'Date from', + 'inbox.filterDateToLabel': 'Date to', + 'inbox.filterDatePlaceholder': 'Choose a date', + 'inbox.filterDateConfirm': 'Confirm', + 'inbox.filterDateCancel': 'Cancel', // document detail 'documentDetail.screenTitle': 'Document', diff --git a/apps/native/app/src/messages/is.ts b/apps/native/app/src/messages/is.ts index 5deb9f43a606..502f6f0ebc57 100644 --- a/apps/native/app/src/messages/is.ts +++ b/apps/native/app/src/messages/is.ts @@ -180,7 +180,7 @@ export const is = { 'home.onboardingModule.card4': 'Notendum er bent á að kynna sér stefnu Stafræns Íslands um meðferð persónuupplýsinga á', 'home.vehicleModule.summary': - 'Skrá kílómetrastöðu rafmagns- og tengiltvinnbíla', + 'Skrá kílómetrastöðu rafmagns- og tengiltvinnbíla', 'home.vehicleModule.button': 'Mín ökutæki', 'button.seeAll': 'Sjá allt', @@ -195,10 +195,6 @@ export const is = { 'inbox.emptyListTitle': 'Hér eru engin skjöl sem stendur', 'inbox.emptyListDescription': 'Þegar þú færð send rafræn skjöl frá hinu opinbera birtast þau hér.', - 'inbox.filterButtonTitle': 'Sía', - 'inbox.filterOpenedTagTitle': 'Ólesið', - 'inbox.filterArchivedTagTitle': 'Geymsla', - 'inbox.filterStarredTagTitle': 'Stjörnumerkt', 'inbox.markAllAsReadPromptTitle': 'Viltu merkja öll skjöl sem lesin?', 'inbox.markAllAsReadPromptDescription': 'Þessi aðgerð er ekki afturkræf', 'inbox.markAllAsReadPromptCancel': 'Hætta við', @@ -211,6 +207,20 @@ export const is = { 'inboxFilters.unreadOnly': 'Sýna einungis ólesið', 'inboxFilters.starred': 'Stjörnumerkt', 'inboxFilters.archived': 'Geymsla', + 'inbox.filterButtonTitle': 'Sía', + 'inbox.filterOpenedTagTitle': 'Ólesið', + 'inbox.filterArchivedTagTitle': 'Geymsla', + 'inbox.filterStarredTagTitle': 'Stjörnumerkt', + 'inbox.filterOrganizationTitle': 'Stofnun', + 'inbox.filterCategoryTitle': 'Flokkur', + 'inbox.filterDatesTitle': 'Dagsetningar', + 'inbox.filterClearButton': 'Hreinsa', + 'inbox.filterApplyButton': 'Sjá valið', + 'inbox.filterDateFromLabel': 'Dagsetning frá', + 'inbox.filterDateToLabel': 'Dagsetning til', + 'inbox.filterDatePlaceholder': 'Veldu dagsetningu', + 'inbox.filterDateConfirm': 'Staðfesta', + 'inbox.filterDateCancel': 'Hætta við', // document detail 'documentDetail.screenTitle': 'Skjal', diff --git a/apps/native/app/src/screens/applications/applications.tsx b/apps/native/app/src/screens/applications/applications.tsx index 38e4b763c8a7..c4cca3365bcc 100644 --- a/apps/native/app/src/screens/applications/applications.tsx +++ b/apps/native/app/src/screens/applications/applications.tsx @@ -25,7 +25,6 @@ import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator import { useBrowser } from '../../lib/use-browser' import { getApplicationOverviewUrl } from '../../utils/applications-utils' import { testIDs } from '../../utils/test-ids' -import { getRightButtons } from '../../utils/get-main-root' import { ApplicationsModule } from '../home/applications-module' import { isIos } from '../../utils/devices' @@ -43,7 +42,6 @@ const { useNavigationOptions, getNavigationOptions } = searchBar: { visible: false, }, - rightButtons: initialized ? getRightButtons({ theme } as any) : [], }, bottomTab: { iconColor: theme.color.blue400, @@ -100,7 +98,6 @@ export const ApplicationsScreen: NavigationFunctionComponent = ({ useConnectivityIndicator({ componentId, - rightButtons: getRightButtons(), refetching, queryResult: [applicationsRes, res], }) diff --git a/apps/native/app/src/screens/document-detail/document-detail.tsx b/apps/native/app/src/screens/document-detail/document-detail.tsx index 945d72efbd9f..709b06934fa6 100644 --- a/apps/native/app/src/screens/document-detail/document-detail.tsx +++ b/apps/native/app/src/screens/document-detail/document-detail.tsx @@ -15,7 +15,7 @@ import { import Pdf, { Source } from 'react-native-pdf' import Share from 'react-native-share' import WebView from 'react-native-webview' -import styled from 'styled-components/native' +import styled, { useTheme } from 'styled-components/native' import { DocumentV2, ListDocumentFragmentDoc, @@ -45,8 +45,45 @@ const PdfWrapper = styled.View` flex: 1; background-color: ${dynamicColor('background')}; ` +const regexForBr = /
*\\?>/g -function getRightButtons({ +// Styles for html documents +const useHtmlStyles = () => { + const theme = useTheme() + return ` + ` +} + +function getRightButtonsForDocumentDetail({ archived, bookmarked, }: { @@ -105,7 +142,7 @@ const { useNavigationOptions, getNavigationOptions } = }, topBar: { noBorder: true, - rightButtons: getRightButtons(), + rightButtons: getRightButtonsForDocumentDetail(), }, }, ) @@ -168,6 +205,7 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ const client = useApolloClient() const intl = useIntl() + const htmlStyles = useHtmlStyles() const { getOrganizationLogoUrl } = useOrganizationsStore() const [accessToken, setAccessToken] = useState() const [error, setError] = useState(false) @@ -212,7 +250,7 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ useConnectivityIndicator({ componentId, - rightButtons: getRightButtons({ + rightButtons: getRightButtonsForDocumentDetail({ archived: doc.data?.archived ?? false, bookmarked: doc.data?.bookmarked ?? false, }), @@ -274,7 +312,7 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ }, }, }) - }, [Document.id, Document.opened]) + }, [Document.id]) useEffect(() => { const { authorizeResult, refresh } = authStore.getState() @@ -335,7 +373,15 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ !error && (isHtml ? ( tags to fix a bug in react-native that renders
with too much vertical space + // https://github.com/facebook/react-native/issues/32062 + `${htmlStyles}${Document.content?.value.replaceAll( + regexForBr, + '', + )}` ?? '', + }} scalesPageToFit onLoadEnd={() => { setLoaded(true) diff --git a/apps/native/app/src/screens/home/home.tsx b/apps/native/app/src/screens/home/home.tsx index 0b8a43b7a8a5..40fa0f020189 100644 --- a/apps/native/app/src/screens/home/home.tsx +++ b/apps/native/app/src/screens/home/home.tsx @@ -49,7 +49,9 @@ const { useNavigationOptions, getNavigationOptions } = createNavigationOptionHooks( (theme, intl, initialized) => ({ topBar: { - rightButtons: initialized ? getRightButtons({ theme } as any) : [], + rightButtons: initialized + ? getRightButtons({ icons: ['notifications'], theme: theme as any }) + : [], }, bottomTab: { ...({ @@ -108,7 +110,7 @@ export const MainHomeScreen: NavigationFunctionComponent = ({ useConnectivityIndicator({ componentId, - rightButtons: getRightButtons(), + rightButtons: getRightButtons({ icons: ['notifications'] }), queryResult: applicationsRes, refetching, }) diff --git a/apps/native/app/src/screens/inbox/inbox-filter.tsx b/apps/native/app/src/screens/inbox/inbox-filter.tsx index cf921abd1432..121def2ffdd8 100644 --- a/apps/native/app/src/screens/inbox/inbox-filter.tsx +++ b/apps/native/app/src/screens/inbox/inbox-filter.tsx @@ -1,12 +1,33 @@ -import { TableViewCell, theme } from '@ui' +import { + Accordion, + AccordionItem, + Button, + Checkbox, + DatePickerInput, + theme, +} from '@ui' import { useEffect, useState } from 'react' import { useIntl } from 'react-intl' -import { Platform, ScrollView, Switch } from 'react-native' +import { ScrollView, View } from 'react-native' import { Navigation } from 'react-native-navigation' -import { PressableHighlight } from '../../components/pressable-highlight/pressable-highlight' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' -import { ComponentRegistry } from '../../utils/component-registry' +import { + ButtonRegistry, + ComponentRegistry, +} from '../../utils/component-registry' +import { + DocumentsV2Category, + DocumentsV2Sender, +} from '../../graphql/types/schema' +import styled from 'styled-components' +import { useNavigationButtonPress } from 'react-native-navigation-hooks' + +const ButtonContainer = styled(View)` + margin-left: ${({ theme }) => theme.spacing[2]}px; + margin-right: ${({ theme }) => theme.spacing[2]}px; + bottom: ${theme.spacing[2]}px; +` const { useNavigationOptions, getNavigationOptions } = createNavigationOptionHooks((theme, intl) => ({ @@ -16,6 +37,7 @@ const { useNavigationOptions, getNavigationOptions } = id: 'inboxFilters.screenTitle', }), }, + rightButtons: [], }, })) @@ -23,6 +45,12 @@ export function InboxFilterScreen(props: { opened: boolean bookmarked: boolean archived: boolean + availableSenders: DocumentsV2Sender[] + availableCategories: DocumentsV2Category[] + selectedSenders: string[] + selectedCategories: string[] + dateFrom?: Date + dateTo?: Date componentId: string }) { useConnectivityIndicator({ componentId: props.componentId }) @@ -31,88 +59,208 @@ export function InboxFilterScreen(props: { const [opened, setOpened] = useState(props.opened) const [bookmarked, setBookmarked] = useState(props.bookmarked) const [archived, setArchived] = useState(props.archived) + const [selectedSenders, setSelectedSenders] = useState( + props.selectedSenders ?? [], + ) + const [selectedCategories, setSelectedCategories] = useState( + props.selectedCategories ?? [], + ) + const [dateFrom, setDateFrom] = useState(props.dateFrom) + const [dateTo, setDateTo] = useState(props.dateTo) + + const clearAllFilters = () => { + setOpened(false) + setBookmarked(false) + setArchived(false) + setSelectedSenders([]) + setSelectedCategories([]) + setDateFrom(undefined) + setDateTo(undefined) + } + + const isSelected = !!( + opened || + bookmarked || + archived || + selectedSenders.length || + selectedCategories.length || + dateFrom || + dateTo + ) useNavigationOptions(props.componentId) + useNavigationButtonPress(({ buttonId }) => { + if (buttonId === ButtonRegistry.InboxFilterClearButton) { + clearAllFilters() + } + }, props.componentId) + useEffect(() => { Navigation.updateProps(ComponentRegistry.InboxScreen, { opened, bookmarked, archived, + senderNationalId: selectedSenders, + categoryIds: selectedCategories, + dateFrom, + dateTo, }) - }, [opened, bookmarked, archived]) + }, [ + opened, + bookmarked, + archived, + selectedSenders, + selectedCategories, + dateFrom, + dateTo, + ]) + + useEffect(() => { + Navigation.mergeOptions(props.componentId, { + topBar: { + rightButtons: isSelected + ? [ + { + id: ButtonRegistry.InboxFilterClearButton, + text: intl.formatMessage({ + id: 'inbox.filterClearButton', + }), + }, + ] + : [], + }, + }) + }, [isSelected, props.componentId, intl]) return ( - - { - setOpened(!opened) - }} - > - + + setOpened(!opened)} - thumbColor={Platform.select({ android: theme.color.dark100 })} - trackColor={{ - false: theme.color.dark200, - true: theme.color.blue400, - }} - /> - } - border + checked={opened} + onPress={() => { + setOpened(!opened) + }} /> - - { - setBookmarked(!bookmarked) - }} - > - setBookmarked(!bookmarked)} - thumbColor={Platform.select({ android: theme.color.dark100 })} - trackColor={{ - false: theme.color.dark200, - true: theme.color.blue400, - }} - /> - } - border + checked={bookmarked} + onPress={() => { + setBookmarked(!bookmarked) + }} /> - - { - setArchived(!archived) - }} - > - setArchived(!archived)} - thumbColor={Platform.select({ android: theme.color.dark100 })} - trackColor={{ - false: theme.color.dark200, - true: theme.color.blue400, - }} - /> - } + checked={archived} + onPress={() => { + setArchived(!archived) + }} /> - - + + {props.availableSenders?.length ? ( + 0} + > + {props.availableSenders.map(({ name, id }) => { + return name && id ? ( + { + if (selectedSenders.includes(id)) { + setSelectedSenders((prev) => + prev.filter((senderId) => senderId !== id), + ) + } else { + setSelectedSenders((prev) => [...prev, id]) + } + }} + /> + ) : null + })} + + ) : null} + {props.availableCategories?.length ? ( + 0} + > + {props.availableCategories.map(({ name, id }) => { + return name && id ? ( + { + if (selectedCategories.includes(id)) { + setSelectedCategories((prev) => + prev.filter((categoryId) => categoryId !== id), + ) + } else { + setSelectedCategories((prev) => [...prev, id]) + } + }} + /> + ) : null + })} + + ) : null} + + + + + + + + {isSelected && ( + + + +
+ )} + + ) +} diff --git a/apps/web/components/Organization/Slice/SliceMachine.tsx b/apps/web/components/Organization/Slice/SliceMachine.tsx index dff0592cd94d..23ec73dd0095 100644 --- a/apps/web/components/Organization/Slice/SliceMachine.tsx +++ b/apps/web/components/Organization/Slice/SliceMachine.tsx @@ -103,6 +103,10 @@ const ChartNumberBox = dynamic(() => import('@island.is/web/components').then((mod) => mod.ChartNumberBox), ) +const LatestGenericListItems = dynamic(() => + import('@island.is/web/components').then((mod) => mod.LatestGenericListItems), +) + interface SliceMachineProps { slice: Slice namespace?: Record @@ -212,6 +216,9 @@ const renderSlice = ( ) case 'ChartNumberBox': return + case 'LatestGenericListItems': { + return + } default: return } diff --git a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx index 68734ed33293..c97ec5516c7f 100644 --- a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx +++ b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx @@ -1,4 +1,11 @@ -import React, { ReactNode, useEffect, useMemo, useState } from 'react' +import React, { + PropsWithChildren, + ReactNode, + useEffect, + useMemo, + useState, +} from 'react' +import { IntlConfig, IntlProvider } from 'react-intl' import { useWindowSize } from 'react-use' import NextLink from 'next/link' import { useRouter } from 'next/router' @@ -838,25 +845,54 @@ const getActiveNavigationItemTitle = ( } } } + +interface TranslationNamespaceProviderProps { + messages: IntlConfig['messages'] +} + +const TranslationNamespaceProvider = ({ + messages, + children, +}: PropsWithChildren) => { + const { activeLocale } = useI18n() + + return ( + + {children} + + ) +} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore make web strict const renderConnectedComponent = (slice) => { if (!slice?.componentType) return null + let connectedComponent = null + switch (slice.componentType) { case 'LatestNewsCard': - return ( + connectedComponent = ( ) + break case 'Fiskistofa/ShipSearchSidebarInput': - return ( - - ) + connectedComponent = + break case 'OrganizationSearchBox': - return + connectedComponent = + break default: return null } + + return ( + + {connectedComponent} + + ) } export const OrganizationWrapper: React.FC< diff --git a/apps/web/components/connected/AdministrationOfOccupationalSafetyAndHealthCourses/AdministrationOfOccupationalSafetyAndHealthCourses.tsx b/apps/web/components/connected/AdministrationOfOccupationalSafetyAndHealthCourses/AdministrationOfOccupationalSafetyAndHealthCourses.tsx index 337f945e1d21..d29a58cda6fb 100644 --- a/apps/web/components/connected/AdministrationOfOccupationalSafetyAndHealthCourses/AdministrationOfOccupationalSafetyAndHealthCourses.tsx +++ b/apps/web/components/connected/AdministrationOfOccupationalSafetyAndHealthCourses/AdministrationOfOccupationalSafetyAndHealthCourses.tsx @@ -1,4 +1,5 @@ import { useState } from 'react' +import { useIntl } from 'react-intl' import { useQuery } from '@apollo/client/react' import { @@ -11,11 +12,11 @@ import { } from '@island.is/island-ui/core' import { BorderAbove } from '@island.is/web/components' import { ConnectedComponent, Query } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' import { useDateUtils } from '@island.is/web/i18n/useDateUtils' import { extractHeadingLevels } from '@island.is/web/utils/navigation' import { GET_ADMINISTRATION_OF_SAFETY_AND_HEALTH_COURSES_QUERY } from './queries' +import { translation as translationStrings } from './translation.strings' import { getCurrencyString, parseDateString } from './utils' const normalizesAndMatch = (value1: string, value2: string) => { @@ -31,8 +32,8 @@ type ListState = 'loading' | 'loaded' | 'error' const AdministrationOfOccupationalSafetyAndHealthCourses = ({ slice, }: AdministrationOfOccupationalSafetyAndHealthCoursesProps) => { - const n = useNamespace(slice.json ?? {}) const { format } = useDateUtils() + const { formatMessage } = useIntl() const title = slice.json?.title ?? null const hasBorderAbove = slice.configJson?.hasBorderAbove ?? false @@ -99,15 +100,17 @@ const AdministrationOfOccupationalSafetyAndHealthCourses = ({ )} {listState === 'error' && ( )} {listState === 'loaded' && courses.length === 0 && ( - {n('noResults', 'Engin Námskeið fundust.')} + + {formatMessage(translationStrings.noResults)} + )} {listState === 'loaded' && courses.length > 0 && ( @@ -168,19 +171,19 @@ const AdministrationOfOccupationalSafetyAndHealthCourses = ({ > - {n('validPeriodLabel', 'Dagsetning')}:{' '} + {formatMessage(translationStrings.validPeriodLabel)}:{' '} {dateFrom !== dateTo ? dateFrom + ' - ' + dateTo : dateFrom} - {n('time', 'Klukkan')}: {course.time} + {formatMessage(translationStrings.time)}: {course.time} - {n('price', 'Verð')}:{' '} + {formatMessage(translationStrings.price)}:{' '} {getCurrencyString(course.price || 0)} diff --git a/apps/web/components/connected/AdministrationOfOccupationalSafetyAndHealthCourses/translation.strings.ts b/apps/web/components/connected/AdministrationOfOccupationalSafetyAndHealthCourses/translation.strings.ts new file mode 100644 index 000000000000..8379f4468caf --- /dev/null +++ b/apps/web/components/connected/AdministrationOfOccupationalSafetyAndHealthCourses/translation.strings.ts @@ -0,0 +1,34 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + errorTitle: { + id: 'web.administrationOfOccupationalSafetyAndHealthCourses:errorTitle', + defaultMessage: 'Villa', + description: 'Titill á villu skilaboðum', + }, + errorMessage: { + id: 'web.administrationOfOccupationalSafetyAndHealthCourses:errorMessage', + defaultMessage: 'Ekki tókst að sækja námskeið.', + description: 'Texti fyrir villu skilaboð', + }, + noResults: { + id: 'web.administrationOfOccupationalSafetyAndHealthCourses:noResults', + defaultMessage: 'Engin Námskeið fundust.', + description: 'Texti þegar engin námskeið finnast', + }, + validPeriodLabel: { + id: 'web.administrationOfOccupationalSafetyAndHealthCourses:validPeriodLabel', + defaultMessage: 'Dagsetning', + description: 'Texti fyrir framan dagsetningar á námskeiða spjöldum', + }, + time: { + id: 'web.administrationOfOccupationalSafetyAndHealthCourses:time', + defaultMessage: 'Klukkan', + description: 'Texti fyrir framan tímasetningu á námskeiða spjöldum', + }, + price: { + id: 'web.administrationOfOccupationalSafetyAndHealthCourses:price', + defaultMessage: 'Verð', + description: 'Texti fyrir framan verð á námskeiða spjöldum', + }, +}) diff --git a/apps/web/components/connected/DrivingInstructorList/DrivingInstructorList.tsx b/apps/web/components/connected/DrivingInstructorList/DrivingInstructorList.tsx index daafb38fcf8e..53853ed68285 100644 --- a/apps/web/components/connected/DrivingInstructorList/DrivingInstructorList.tsx +++ b/apps/web/components/connected/DrivingInstructorList/DrivingInstructorList.tsx @@ -1,4 +1,7 @@ +import { useState } from 'react' +import { useIntl } from 'react-intl' import { useQuery } from '@apollo/client' + import { AlertMessage, Box, @@ -13,9 +16,9 @@ import { GetDrivingInstructorsQuery, GetDrivingInstructorsQueryVariables, } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' import { GET_DRIVING_INSTRUCTORS_QUERY } from '@island.is/web/screens/queries/DrivingInstructors' -import { useState } from 'react' + +import { translation as translationStrings } from './translation.strings' const DEFAULT_ITEMS_PER_PAGE = 10 @@ -79,7 +82,7 @@ interface DrivingInstructorListProps { const DrivingInstructorList = ({ slice }: DrivingInstructorListProps) => { const [selectedPage, setSelectedPage] = useState(1) - const n = useNamespace(slice?.json ?? {}) + const { formatMessage } = useIntl() const [searchValue, setSearchValue] = useState('') const { data, error, loading, called } = useQuery< @@ -104,7 +107,7 @@ const DrivingInstructorList = ({ slice }: DrivingInstructorListProps) => { { {called && !loading && error && ( )} {called && !loading && !error && !filteredInstructors?.length && ( - {n('noResultsFound', 'Engir ökukennarar fundust')} + {formatMessage(translationStrings.noResultsFound)} )} @@ -146,16 +149,18 @@ const DrivingInstructorList = ({ slice }: DrivingInstructorListProps) => { - {n('name', 'Nafn')} + + {formatMessage(translationStrings.name)} + - {n('nationalId', 'Kennitala')} + {formatMessage(translationStrings.nationalId)} - {n('driverLicenseId', 'Ökuréttindisnúmer')} + {formatMessage(translationStrings.driverLicenseId)} diff --git a/apps/web/components/connected/DrivingInstructorList/translation.strings.ts b/apps/web/components/connected/DrivingInstructorList/translation.strings.ts new file mode 100644 index 000000000000..48b5359ffd73 --- /dev/null +++ b/apps/web/components/connected/DrivingInstructorList/translation.strings.ts @@ -0,0 +1,39 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + searchPlaceholder: { + id: 'web.drivingInstructorList:searchPlaceholder', + defaultMessage: 'Leita', + description: 'Titill á placeholder í leitarglugga', + }, + errorOccurredTitle: { + id: 'web.drivingInstructorList:errorOccurredTitle', + defaultMessage: 'Villa kom upp', + description: 'Titill á villu skilaboðum', + }, + errorOccurredMessage: { + id: 'web.drivingInstructorList:errorOccurredMessage', + defaultMessage: 'Ekki tókst að sækja ökukennara', + description: 'Texti fyrir villu skilaboð', + }, + noResultsFound: { + id: 'web.drivingInstructorList:noResultsFound', + defaultMessage: 'Engir ökukennarar fundust', + description: 'Texti þegar engin námskeið finnast', + }, + name: { + id: 'web.drivingInstructorList:name', + defaultMessage: 'Nafn', + description: 'Texti fyrir nafn í töflu haus', + }, + nationalId: { + id: 'web.drivingInstructorList:nationalId', + defaultMessage: 'Kennitala', + description: 'Texti fyrir kennitölu í töflu haus', + }, + driverLicenseId: { + id: 'web.drivingInstructorList:driverLicenseId', + defaultMessage: 'Ökuréttindisnúmer', + description: 'Texti fyrir ökuréttindisnúmer í töflu haus', + }, +}) diff --git a/apps/web/components/connected/GrindavikResidentialPropertyPurchaseCalculator/GrindavikResidentialPropertyPurchaseCalculator.tsx b/apps/web/components/connected/GrindavikResidentialPropertyPurchaseCalculator/GrindavikResidentialPropertyPurchaseCalculator.tsx index f46110257a54..35295b5f4b9b 100644 --- a/apps/web/components/connected/GrindavikResidentialPropertyPurchaseCalculator/GrindavikResidentialPropertyPurchaseCalculator.tsx +++ b/apps/web/components/connected/GrindavikResidentialPropertyPurchaseCalculator/GrindavikResidentialPropertyPurchaseCalculator.tsx @@ -1,5 +1,6 @@ import { useRef, useState } from 'react' import { FormProvider, useFieldArray, useForm, useWatch } from 'react-hook-form' +import { useIntl } from 'react-intl' import isEqual from 'lodash/isEqual' import { @@ -12,10 +13,9 @@ import { } from '@island.is/island-ui/core' import { InputController } from '@island.is/shared/form-fields' import { ConnectedComponent } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' -import { useI18n } from '@island.is/web/i18n' import { formatCurrency } from '@island.is/web/utils/currency' +import { translation as translationStrings } from './translation.strings' import * as styles from './GrindavikResidentialPropertyPurchaseCalculator.css' interface ResultState { @@ -88,7 +88,7 @@ const focusLoanInput = (index: number, delay = 100) => { const GrindavikResidentialPropertyPurchaseCalculator = ({ slice, }: GrindavikResidentialPropertyPurchaseCalculatorProps) => { - const n = useNamespace(slice.json ?? {}) + const { formatMessage } = useIntl() const methods = useForm({ defaultValues: { loans: [{ value: undefined }] }, }) @@ -96,7 +96,7 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ name: 'loans', control: methods.control, }) - const { activeLocale } = useI18n() + const resultContainerRef = useRef(null) const fireInsuranceValue = methods.watch('fireInsuranceValue') @@ -128,31 +128,18 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ setResultState(calculateResultState(inputState, thorkatlaPurchasePrice)) } - const mainHeading = n( - 'mainHeading', - 'Útreikningur vegna uppkaupa fasteigna í Grindavík', - ) - const currencySuffix = n('currencySuffix', ' krónur') as string + const mainHeading = formatMessage(translationStrings.mainHeading) + const currencySuffix = formatMessage(translationStrings.currencySuffix) const canCalculate = !isEqual(resultState?.inputState, inputState) const maxLength = (slice.configJson?.maxLength ?? 11) + currencySuffix.length - const thorkatlaPaymentDisclaimer = n( - 'thorkatlaPaymentDisclaimer', - 'Seljandi getur valið afhendingardagsetningu minnst 1 mánuði frá kaupsamningi og mest 3 mánuðum frá kaupsamningi. Afsal fer fram einum mánuði frá afhendingu.', - ) - const purchaseAgreementPaymentDisclaimer = n( - 'purchaseAgreementPaymentDisclaimer', - '*Greiðslur frá félaginu fara til eigenda í samræmi við eignarhlutfall.', - ) - const closingResultDisclaimer = n( - 'closingResultDisclaimer', - '**Í afsalsgreiðslu fer fram lögskilauppgjör sem kemur til hækkunar eða lækkunar á afsalsgreiðslu.', + const thorkatlaPaymentDisclaimer = formatMessage( + translationStrings.thorkatlaPaymentDisclaimer, ) - const loanDisclaimer = n( - 'loanDisclaimer', - 'Samtal er enn í gangi við lífeyrissjóði um þátttöku þeirra í úrræðinu. Vonast er til þess að niðurstaða liggi fyrir fljótlega', + const purchaseAgreementPaymentDisclaimer = formatMessage( + translationStrings.purchaseAgreementPaymentDisclaimer, ) return ( @@ -168,13 +155,17 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ {mainHeading && {mainHeading}} - {n('fireInsuranceValueHeading', 'Brunabótamat eignar')} + {formatMessage(translationStrings.fireInsuranceValueHeading)} - {n( - 'thorkatlaPurchasePriceLabel', - 'Kaupverð Þórkötlu (95% af brunabótamati)', + {formatMessage( + translationStrings.thorkatlaPurchasePriceLabel, )} @@ -197,7 +187,9 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ - {n('loanHeading', 'Áhvílandi lán')} + + {formatMessage(translationStrings.loanHeading)} + {loansFieldArray.fields.map((field, index) => { const name = `loans[${index}].value` @@ -215,10 +207,14 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ key={field.id} id={name} name={name} - label={`${n('loanLabel', 'Lán')} ${index + 1}`} + label={`${formatMessage( + translationStrings.loanLabel, + )} ${index + 1}`} currency={true} maxLength={maxLength} - placeholder={n('currencyInputPlaceholder', 'kr.')} + placeholder={formatMessage( + translationStrings.currencyInputPlaceholder, + )} type="number" size="sm" inputMode="numeric" @@ -241,12 +237,7 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ }} > - {n( - 'removeLoan', - activeLocale === 'is' - ? 'Eyða láni' - : 'Remove loan', - )} + {formatMessage(translationStrings.removeLoan)} - {n('appendLoan', 'Bæta við láni')} + {formatMessage(translationStrings.appendLoan)} @@ -298,7 +289,9 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ > - {n('resultsHeading', 'Niðurstöður')} + + {formatMessage(translationStrings.resultsHeading)} + - {n('thorkatlaPaymentLabel', 'Greitt úr af Þórkötlu*')} + {formatMessage(translationStrings.thorkatlaPaymentLabel)} {formatCurrency( @@ -318,7 +311,9 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ - {n('breakdown', 'Sundurliðun')} + + {formatMessage(translationStrings.breakdown)} + - {n( - 'purchaseAgreementPaymentLabel', - 'Greitt við kaupsamning', + {formatMessage( + translationStrings.purchaseAgreementPaymentLabel, )} @@ -348,10 +342,7 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ flexDirection={['column', 'row']} > - {n( - 'closingPaymentLabel', - 'Greitt við afsal (5% af kaupvirði)**', - )} + {formatMessage(translationStrings.closingPaymentLabel)} {formatCurrency( @@ -372,10 +363,6 @@ const GrindavikResidentialPropertyPurchaseCalculator = ({ {purchaseAgreementPaymentDisclaimer && ( {purchaseAgreementPaymentDisclaimer} )} - {closingResultDisclaimer && ( - {closingResultDisclaimer} - )} - {loanDisclaimer && {loanDisclaimer}}
diff --git a/apps/web/components/connected/GrindavikResidentialPropertyPurchaseCalculator/translation.strings.ts b/apps/web/components/connected/GrindavikResidentialPropertyPurchaseCalculator/translation.strings.ts new file mode 100644 index 000000000000..531befd12211 --- /dev/null +++ b/apps/web/components/connected/GrindavikResidentialPropertyPurchaseCalculator/translation.strings.ts @@ -0,0 +1,96 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + mainHeading: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:mainHeading', + defaultMessage: 'Bráðabirgðaútreikningur', + description: 'Titill á útreikning vegna uppkaupa fasteigna í Grindavík', + }, + currencySuffix: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:currencySuffix', + defaultMessage: ' kr.', + description: 'Gjaldeyrisviðskeyti', + }, + thorkatlaPaymentDisclaimer: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:thorkatlaPaymentDisclaimer', + defaultMessage: + 'Seljandi getur valið afhendingardagsetningu á bilinu 1-3 mánuðum eftir kaupsamning. Afsal fer fram einum mánuði frá afhendingu.', + description: 'Texti fyrir greiðslufyrirvara skilaboð', + }, + purchaseAgreementPaymentDisclaimer: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:purchaseAgreementPaymentDisclaimer', + defaultMessage: + 'Samhliða afsalsgreiðslu fer fram lögskilauppgjör sem kemur til hækkunar eða lækkunar á afsalsgreiðslu.', + description: 'Texti fyrir greiðslufyrirvara kaupsamnings', + }, + fireInsuranceValueHeading: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:fireInsuranceValueHeading', + defaultMessage: 'Brunabótamat eignar', + description: 'Titill fyrir brunabótamat eignar', + }, + fireInsuranceValueLabel: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:fireInsuranceValueLabel', + defaultMessage: 'Brunabótamat', + description: 'Label fyrir brunabótamat input', + }, + currencyInputPlaceholder: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:currencyInputPlaceholder', + defaultMessage: 'kr.', + description: 'Gjaldmiðill sem birtist í input reitum', + }, + thorkatlaPurchasePriceLabel: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:thorkatlaPurchasePriceLabel', + defaultMessage: 'Kaupverð Þórkötlu (95% af brunabótamati)', + description: 'Label fyrir kaupverð Þórkötlu (95% af brunabótamati)', + }, + loanHeading: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:loanHeading', + defaultMessage: 'Áhvílandi lán', + description: 'Áhvílandi lán', + }, + loanLabel: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:loanLabel', + defaultMessage: 'Lán', + description: 'Label fyrir lán input', + }, + removeLoan: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:removeLoan', + defaultMessage: 'Eyða láni', + description: 'Eyða láni', + }, + appendLoan: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:appendLoan', + defaultMessage: 'Bæta við láni', + description: 'Bæta við láni', + }, + calculate: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:calculate', + defaultMessage: 'Reikna', + description: 'Reikna', + }, + resultsHeading: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:resultsHeading', + defaultMessage: 'Niðurstöður', + description: 'Niðurstöður', + }, + thorkatlaPaymentLabel: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:thorkatlaPaymentLabel', + defaultMessage: 'Greitt til seljenda', + description: 'Label fyrir greiðslu til seljanda', + }, + breakdown: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:breakdown', + defaultMessage: 'Sundurliðun', + description: 'Sundurliðun', + }, + purchaseAgreementPaymentLabel: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:purchaseAgreementPaymentLabel', + defaultMessage: 'Greitt við kaupsamning', + description: 'Label fyrir greitt við kaupsamning', + }, + closingPaymentLabel: { + id: 'web.GrindavikResidentialPropertyPurchaseCalculator:closingPaymentLabel', + defaultMessage: 'Greitt við afsal (5% af kaupvirði)', + description: 'Label fyrir greiðslu við afsal', + }, +}) diff --git a/apps/web/components/connected/HousingBenefitCalculator/HousingBenefitCalculator.tsx b/apps/web/components/connected/HousingBenefitCalculator/HousingBenefitCalculator/HousingBenefitCalculator.tsx similarity index 77% rename from apps/web/components/connected/HousingBenefitCalculator/HousingBenefitCalculator.tsx rename to apps/web/components/connected/HousingBenefitCalculator/HousingBenefitCalculator/HousingBenefitCalculator.tsx index cddd141b9ec0..2007328c511e 100644 --- a/apps/web/components/connected/HousingBenefitCalculator/HousingBenefitCalculator.tsx +++ b/apps/web/components/connected/HousingBenefitCalculator/HousingBenefitCalculator/HousingBenefitCalculator.tsx @@ -1,5 +1,6 @@ import { useMemo, useState } from 'react' import { Controller, useForm } from 'react-hook-form' +import { useIntl } from 'react-intl' import { useLazyQuery } from '@apollo/client' import { @@ -17,10 +18,11 @@ import { GetHousingBenefitCalculationQuery, GetHousingBenefitCalculationQueryVariables, } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' import { GET_HOUSING_BENEFIT_CALCULATION } from '@island.is/web/screens/queries/HousingBenefitCalculator' import { formatCurrency } from '@island.is/web/utils/currency' +import { translation as translationStrings } from './translation.strings' + const MAX_LENGTH = 15 interface InputState { @@ -35,7 +37,7 @@ interface HousingBenefitCalculatorProps { } const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { - const n = useNamespace(slice.json ?? {}) + const { formatMessage } = useIntl() const [inputState, setInputState] = useState({ income: '', housingCost: '', @@ -116,9 +118,15 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { if (slice?.configJson?.showSixOptions) { options.push({ label: '4', value: 4 }) options.push({ label: '5', value: 5 }) - options.push({ label: n('sixOrMore', '6 eða fleiri'), value: 6 }) + options.push({ + label: formatMessage(translationStrings.sixOrMore), + value: 6, + }) } else { - options.push({ label: n('fourOrMore', '4 eða fleiri'), value: 4 }) + options.push({ + label: formatMessage(translationStrings.fourOrMore), + value: 4, + }) } return options @@ -135,7 +143,7 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { - {n('numberOfHouseholdMembers', 'Fjöldi heimilismanna í húsnæði?')} + {formatMessage(translationStrings.numberOfHouseholdMembers)} { - {n( - 'monthlyIncomeOfHouseholdMembers18YearsAndOlder', - 'Samanlagðar mánaðarlegartekjur heimilismanna 18 ára og eldri (tekjur f. skatt)?', + {formatMessage( + translationStrings.monthlyIncomeOfHouseholdMembers18YearsAndOlder, )} @@ -172,8 +179,8 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { id="income" control={control} name="income" - label={n('incomeLabel', 'Tekjur')} - placeholder={n('incomePlaceholder', 'kr.')} + label={formatMessage(translationStrings.incomeLabel)} + placeholder={formatMessage(translationStrings.incomePlaceholder)} currency={true} type="number" onChange={(event) => { @@ -186,9 +193,8 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { - {n( - 'assetsOfHouseholdMembers18YearsAndOlder', - 'Eignir heimilismanna 18 ára og eldri?', + {formatMessage( + translationStrings.assetsOfHouseholdMembers18YearsAndOlder, )} @@ -196,8 +202,8 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { id="assets" control={control} name="assets" - label={n('assetsLabel', 'Eignir')} - placeholder={n('assetsPlaceholder', 'kr.')} + label={formatMessage(translationStrings.assetsLabel)} + placeholder={formatMessage(translationStrings.assetsPlaceholder)} currency={true} type="number" onChange={(event) => { @@ -210,14 +216,16 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { - {n('housingCostsPerMonth', 'Húsnæðiskostnaður á mánuði?')} + {formatMessage(translationStrings.housingCostsPerMonth)} { @@ -229,13 +237,10 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { /> - {n( - 'calculatorDisclaimer', - 'Útreikningur húsnæðisbóta samkvæmt reiknivélinni byggir á þeim forsendum sem þú gafst upp og telst ekki bindandi ákvörðun um húsnæðisbætur. Útreikningur miðast við greiðslur húsnæðisbóta fyrir heilt almanaksár.', - )} + {formatMessage(translationStrings.calculatorDisclaimer)} @@ -246,7 +251,7 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { paddingX={[3, 3, 3, 3, 12]} > - {n('results', 'Niðurstöður')} + {formatMessage(translationStrings.results)} @@ -257,12 +262,9 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { paddingBottom={2} paddingTop={5} > - {n( - 'maximumHousingBenefits', - 'Hámarksbætur miðað við fjölda heimilismanna eru', - )}{' '} + {formatMessage(translationStrings.maximumHousingBenefits)}{' '} {formatCurrency(maximumHousingBenefits)}{' '} - {n('perMonth', 'á mánuði.')} + {formatMessage(translationStrings.perMonth)} )} @@ -270,30 +272,29 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { {typeof reductionsDueToIncome === 'number' && reductionsDueToIncome > 0 && ( - {n('reductionDueToIncome', 'Skerðing vegna tekna eru')}{' '} + {formatMessage(translationStrings.reductionDueToIncome)}{' '} {formatCurrency(reductionsDueToIncome)}{' '} - {n('perMonth', 'á mánuði.')} + {formatMessage(translationStrings.perMonth)} )} {typeof reductionsDueToAssets === 'number' && reductionsDueToAssets > 0 && ( - {n('reductionDueToAssets', 'Skerðing vegna eigna eru')}{' '} + {formatMessage(translationStrings.reductionDueToAssets)}{' '} {formatCurrency(reductionsDueToAssets)}{' '} - {n('perMonth', 'á mánuði.')} + {formatMessage(translationStrings.perMonth)} )} {typeof reductionsDueToHousingCosts === 'number' && reductionsDueToHousingCosts > 0 && ( - {n( - 'reductionsDueToHousingCosts', - 'Skerðing vegna húsnæðiskostnaðar eru', + {formatMessage( + translationStrings.reductionsDueToHousingCosts, )}{' '} {formatCurrency(reductionsDueToHousingCosts)}{' '} - {n('perMonth', 'á mánuði.')} + {formatMessage(translationStrings.perMonth)} )} @@ -301,9 +302,9 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { {typeof estimatedHousingBenefits === 'number' && ( - {n('estimatedHousingBenefits', 'Áætlaðar húsnæðisbætur eru')}{' '} + {formatMessage(translationStrings.estimatedHousingBenefits)}{' '} {formatCurrency(estimatedHousingBenefits)}{' '} - {n('perMonth', 'á mánuði.')} + {formatMessage(translationStrings.perMonth)} )} @@ -312,8 +313,8 @@ const HousingBenefitCalculator = ({ slice }: HousingBenefitCalculatorProps) => { {!loading && called && error && ( )} diff --git a/apps/web/components/connected/HousingBenefitCalculator/HousingBenefitCalculator/translation.strings.ts b/apps/web/components/connected/HousingBenefitCalculator/HousingBenefitCalculator/translation.strings.ts new file mode 100644 index 000000000000..683b32a080a5 --- /dev/null +++ b/apps/web/components/connected/HousingBenefitCalculator/HousingBenefitCalculator/translation.strings.ts @@ -0,0 +1,122 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + sixOrMore: { + id: 'web.HousingBenefitCalculator:sixOrMore', + defaultMessage: '6 eða fleiri', + description: 'Label fyrir 6 eða fleiri', + }, + fourOrMore: { + id: 'web.HousingBenefitCalculator:fourOrMore', + defaultMessage: '4 eða fleiri', + description: 'Label fyrir 4 eða fleiri', + }, + numberOfHouseholdMembers: { + id: 'web.HousingBenefitCalculator:numberOfHouseholdMembers', + defaultMessage: 'Fjöldi heimilismanna í húsnæði?', + description: 'Fjöldi heimilismanna í húsnæði', + }, + monthlyIncomeOfHouseholdMembers18YearsAndOlder: { + id: 'web.HousingBenefitCalculator:monthlyIncomeOfHouseholdMembers18YearsAndOlder', + defaultMessage: + 'Samanlagðar mánaðarlegartekjur heimilismanna 18 ára og eldri (tekjur f. skatt)?', + description: + 'Samanlagðar mánaðarlegartekjur heimilismanna 18 ára og eldri (tekjur f. skatt)', + }, + incomeLabel: { + id: 'web.HousingBenefitCalculator:incomeLabel', + defaultMessage: 'Tekjur', + description: 'Label fyrir tekjur input reit', + }, + incomePlaceholder: { + id: 'web.HousingBenefitCalculator:incomePlaceholder', + defaultMessage: 'kr.', + description: 'Placeholder fyrir tekjur input reit', + }, + assetsOfHouseholdMembers18YearsAndOlder: { + id: 'web.HousingBenefitCalculator:assetsOfHouseholdMembers18YearsAndOlder', + defaultMessage: 'Eignir heimilismanna 18 ára og eldri?', + description: 'Eignir heimilismanna 18 ára og eldri', + }, + assetsLabel: { + id: 'web.HousingBenefitCalculator:assetsLabel', + defaultMessage: 'Eignir', + description: 'Label fyrir eignir input reit', + }, + assetsPlaceholder: { + id: 'web.HousingBenefitCalculator:assetsPlaceholder', + defaultMessage: 'kr.', + description: 'Placeholder fyrir eignir input reit', + }, + housingCostsPerMonth: { + id: 'web.HousingBenefitCalculator:housingCostsPerMonth', + defaultMessage: 'Húsnæðiskostnaður á mánuði?', + description: 'Húsnæðiskostnaður á mánuði', + }, + housingCostLabel: { + id: 'web.HousingBenefitCalculator:housingCostLabel', + defaultMessage: 'Húsnæðiskostnaður', + description: 'Label fyrir húsnæðiskostnaður', + }, + housingCostPlaceholder: { + id: 'web.HousingBenefitCalculator:housingCostPlaceholder', + defaultMessage: 'kr.', + description: 'Placeholder fyrir húsnæðiskostnaðar input reit', + }, + calculatorDisclaimer: { + id: 'web.HousingBenefitCalculator:calculatorDisclaimer', + defaultMessage: + 'Útreikningur húsnæðisbóta samkvæmt reiknivélinni byggir á þeim forsendum sem þú gafst upp og telst ekki bindandi ákvörðun um húsnæðisbætur. Útreikningur miðast við greiðslur húsnæðisbóta fyrir heilt almanaksár.', + description: 'Reiknivéla fyrirvari', + }, + calculate: { + id: 'web.HousingBenefitCalculator:calculate', + defaultMessage: 'Reikna', + description: 'Texti á takka til að reikna', + }, + results: { + id: 'web.HousingBenefitCalculator:results', + defaultMessage: 'Niðurstöður', + description: 'Titill á niðurstöðum', + }, + maximumHousingBenefits: { + id: 'web.HousingBenefitCalculator:maximumHousingBenefits', + defaultMessage: 'Hámarksbætur miðað við fjölda heimilismanna eru', + description: 'Hámarksbætur miðað við fjölda heimilismanna eru', + }, + perMonth: { + id: 'web.HousingBenefitCalculator:perMonth', + defaultMessage: 'á mánuði.', + description: 'á mánuði', + }, + reductionDueToIncome: { + id: 'web.HousingBenefitCalculator:reductionDueToIncome', + defaultMessage: 'Skerðing vegna tekna eru', + description: 'Skerðing vegna tekna eru', + }, + reductionDueToAssets: { + id: 'web.HousingBenefitCalculator:reductionDueToAssets', + defaultMessage: 'Skerðing vegna eigna eru', + description: 'Skerðing vegna eigna eru', + }, + reductionsDueToHousingCosts: { + id: 'web.HousingBenefitCalculator:reductionsDueToHousingCosts', + defaultMessage: 'Skerðing vegna húsnæðiskostnaðar eru', + description: 'Skerðing vegna húsnæðiskostnaðar eru', + }, + estimatedHousingBenefits: { + id: 'web.HousingBenefitCalculator:estimatedHousingBenefits', + defaultMessage: 'Áætlaðar húsnæðisbætur eru', + description: 'Áætlaðar húsnæðisbætur eru', + }, + errorOccurredTitle: { + id: 'web.HousingBenefitCalculator:errorOccurredTitle', + defaultMessage: 'Villa kom upp', + description: 'Titill þegar villa kemur upp', + }, + errorOccurredMessage: { + id: 'web.HousingBenefitCalculator:errorOccurredMessage', + defaultMessage: 'Ekki tókst að sækja niðurstöður', + description: 'Skilaboð þegar villa kemur upp', + }, +}) diff --git a/apps/web/components/connected/HousingBenefitCalculator/SpecificHousingBenefitSupportCalculator.tsx b/apps/web/components/connected/HousingBenefitCalculator/SpecificHousingBenefitSupportCalculator/SpecificHousingBenefitSupportCalculator.tsx similarity index 78% rename from apps/web/components/connected/HousingBenefitCalculator/SpecificHousingBenefitSupportCalculator.tsx rename to apps/web/components/connected/HousingBenefitCalculator/SpecificHousingBenefitSupportCalculator/SpecificHousingBenefitSupportCalculator.tsx index 734a07a10e5a..6059770a3512 100644 --- a/apps/web/components/connected/HousingBenefitCalculator/SpecificHousingBenefitSupportCalculator.tsx +++ b/apps/web/components/connected/HousingBenefitCalculator/SpecificHousingBenefitSupportCalculator/SpecificHousingBenefitSupportCalculator.tsx @@ -1,5 +1,6 @@ import { useMemo, useState } from 'react' import { Controller, useForm } from 'react-hook-form' +import { useIntl } from 'react-intl' import { useLazyQuery } from '@apollo/client' import { @@ -21,6 +22,8 @@ import { useNamespace } from '@island.is/web/hooks' import { GET_SPECIFIC_HOUSING_BENEFIT_SUPPORT_CALCULATION } from '@island.is/web/screens/queries/HousingBenefitCalculator' import { formatCurrency } from '@island.is/web/utils/currency' +import { translation as translationStrings } from './translation.strings' + const MAX_LENGTH = 15 interface InputState { @@ -28,14 +31,8 @@ interface InputState { householdMemberCount: number } -interface SpecificHousingBenefitSupportCalculatorProps { - slice: ConnectedComponent -} - -const SpecificHousingBenefitSupportCalculator = ({ - slice, -}: SpecificHousingBenefitSupportCalculatorProps) => { - const n = useNamespace(slice.json ?? {}) +const SpecificHousingBenefitSupportCalculator = () => { + const { formatMessage } = useIntl() const [inputState, setInputState] = useState({ housingCost: '', householdMemberCount: 1, @@ -103,7 +100,7 @@ const SpecificHousingBenefitSupportCalculator = ({ { label: '3', value: 3 }, { label: '4', value: 4 }, { label: '5', value: 5 }, - { label: n('sixOrMore', '6 eða fleiri'), value: 6 }, + { label: formatMessage(translationStrings.sixOrMore), value: 6 }, ] }, []) @@ -118,7 +115,7 @@ const SpecificHousingBenefitSupportCalculator = ({ - {n('numberOfHouseholdMembers', 'Fjöldi heimilismanna í húsnæði?')} + {formatMessage(translationStrings.numberOfHouseholdMembers)} - {n('housingCostsPerMonth', 'Húsnæðiskostnaður á mánuði?')} + {formatMessage(translationStrings.housingCostsPerMonth)} { @@ -165,13 +164,10 @@ const SpecificHousingBenefitSupportCalculator = ({ /> - {n( - 'calculatorDisclaimer', - 'Útreikningur húsnæðisbóta samkvæmt reiknivélinni byggir á þeim forsendum sem þú gafst upp og telst ekki bindandi ákvörðun um húsnæðisbætur. Útreikningur miðast við greiðslur húsnæðisbóta fyrir heilt almanaksár.', - )} + {formatMessage(translationStrings.calculatorDisclaimer)} @@ -182,7 +178,7 @@ const SpecificHousingBenefitSupportCalculator = ({ paddingX={[3, 3, 3, 3, 12]} > - {n('results', 'Niðurstöður')} + {formatMessage(translationStrings.results)} @@ -193,12 +189,9 @@ const SpecificHousingBenefitSupportCalculator = ({ paddingBottom={2} paddingTop={5} > - {n( - 'maximumHousingBenefits', - 'Hámarksbætur miðað við fjölda heimilismanna eru', - )}{' '} + {formatMessage(translationStrings.maximumHousingBenefits)}{' '} {formatCurrency(maximumHousingBenefits)}{' '} - {n('perMonth', 'á mánuði.')} + {formatMessage(translationStrings.perMonth)} )} @@ -206,12 +199,11 @@ const SpecificHousingBenefitSupportCalculator = ({ {typeof reductionsDueToHousingCosts === 'number' && reductionsDueToHousingCosts > 0 && ( - {n( - 'reductionsDueToHousingCosts', - 'Skerðing vegna húsnæðiskostnaðar eru', + {formatMessage( + translationStrings.reductionsDueToHousingCosts, )}{' '} {formatCurrency(reductionsDueToHousingCosts)}{' '} - {n('perMonth', 'á mánuði.')} + {formatMessage(translationStrings.perMonth)} )} @@ -219,9 +211,9 @@ const SpecificHousingBenefitSupportCalculator = ({ {typeof estimatedHousingBenefits === 'number' && ( - {n('estimatedHousingBenefits', 'Áætlaðar húsnæðisbætur eru')}{' '} + {formatMessage(translationStrings.estimatedHousingBenefits)}{' '} {formatCurrency(estimatedHousingBenefits)}{' '} - {n('perMonth', 'á mánuði.')} + {formatMessage(translationStrings.perMonth)} )} @@ -230,8 +222,8 @@ const SpecificHousingBenefitSupportCalculator = ({ {!loading && called && error && ( )} diff --git a/apps/web/components/connected/HousingBenefitCalculator/SpecificHousingBenefitSupportCalculator/translation.strings.ts b/apps/web/components/connected/HousingBenefitCalculator/SpecificHousingBenefitSupportCalculator/translation.strings.ts new file mode 100644 index 000000000000..51436a7251c5 --- /dev/null +++ b/apps/web/components/connected/HousingBenefitCalculator/SpecificHousingBenefitSupportCalculator/translation.strings.ts @@ -0,0 +1,75 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + sixOrMore: { + id: 'web.SpecificHousingBenefitSupportCalculator:sixOrMore', + defaultMessage: '6 eða fleiri', + description: 'Label fyrir 6 eða fleiri', + }, + numberOfHouseholdMembers: { + id: 'web.SpecificHousingBenefitSupportCalculator:numberOfHouseholdMembers', + defaultMessage: 'Fjöldi heimilismanna í húsnæði?', + description: 'Fjöldi heimilismanna í húsnæði', + }, + housingCostsPerMonth: { + id: 'web.SpecificHousingBenefitSupportCalculator:housingCostsPerMonth', + defaultMessage: 'Húsnæðiskostnaður á mánuði?', + description: 'Húsnæðiskostnaður á mánuði', + }, + housingCostLabel: { + id: 'web.SpecificHousingBenefitSupportCalculator:housingCostLabel', + defaultMessage: 'Húsnæðiskostnaður', + description: 'Label fyrir húsnæðiskostnaður input reit', + }, + housingCostPlaceholder: { + id: 'web.SpecificHousingBenefitSupportCalculator:housingCostPlaceholder', + defaultMessage: 'kr.', + description: 'Placeholder fyrir húsnæðiskostnaður input reit', + }, + calculatorDisclaimer: { + id: 'web.SpecificHousingBenefitSupportCalculator:calculatorDisclaimer', + defaultMessage: + 'Útreikningur húsnæðisbóta samkvæmt reiknivélinni byggir á þeim forsendum sem þú gafst upp og telst ekki bindandi ákvörðun um húsnæðisbætur. Útreikningur miðast við greiðslur húsnæðisbóta fyrir heilt almanaksár.', + description: 'Reiknivéla fyrirvari', + }, + calculate: { + id: 'web.SpecificHousingBenefitSupportCalculator:calculate', + defaultMessage: 'Reikna', + description: 'Texti á takka til að reikna', + }, + results: { + id: 'web.SpecificHousingBenefitSupportCalculator:results', + defaultMessage: 'Niðurstöður', + description: 'Titill á niðurstöðum', + }, + maximumHousingBenefits: { + id: 'web.SpecificHousingBenefitSupportCalculator:maximumHousingBenefits', + defaultMessage: 'Hámarksbætur miðað við fjölda heimilismanna eru', + description: 'Hámarksbætur miðað við fjölda heimilismanna eru', + }, + perMonth: { + id: 'web.SpecificHousingBenefitSupportCalculator:perMonth', + defaultMessage: 'á mánuði.', + description: 'á mánuði', + }, + reductionsDueToHousingCosts: { + id: 'web.SpecificHousingBenefitSupportCalculator:reductionsDueToHousingCosts', + defaultMessage: 'Skerðing vegna húsnæðiskostnaðar eru', + description: 'Skerðing vegna húsnæðiskostnaðar eru', + }, + estimatedHousingBenefits: { + id: 'web.SpecificHousingBenefitSupportCalculator:estimatedHousingBenefits', + defaultMessage: 'Áætlaðar húsnæðisbætur eru', + description: 'Áætlaðar húsnæðisbætur eru', + }, + errorOccurredTitle: { + id: 'web.SpecificHousingBenefitSupportCalculator:errorOccurredTitle', + defaultMessage: 'Villa kom upp', + description: 'Titill þegar villa kemur upp', + }, + errorOccurredMessage: { + id: 'web.SpecificHousingBenefitSupportCalculator:errorOccurredMessage', + defaultMessage: 'Ekki tókst að sækja niðurstöður', + description: 'Skilaboð þegar villa kemur upp', + }, +}) diff --git a/apps/web/components/connected/HousingBenefitCalculator/index.ts b/apps/web/components/connected/HousingBenefitCalculator/index.ts index ba754fa25196..8cd4530e31cc 100644 --- a/apps/web/components/connected/HousingBenefitCalculator/index.ts +++ b/apps/web/components/connected/HousingBenefitCalculator/index.ts @@ -1,14 +1,17 @@ import dynamic from 'next/dynamic' export const HousingBenefitCalculator = dynamic( - () => import('./HousingBenefitCalculator'), + () => import('./HousingBenefitCalculator/HousingBenefitCalculator'), { ssr: false, }, ) export const SpecificHousingBenefitSupportCalculator = dynamic( - () => import('./SpecificHousingBenefitSupportCalculator'), + () => + import( + './SpecificHousingBenefitSupportCalculator/SpecificHousingBenefitSupportCalculator' + ), { ssr: true, }, diff --git a/apps/web/components/connected/UmbodsmadurSkuldara/UmsCostOfLivingCalculator.tsx b/apps/web/components/connected/UmbodsmadurSkuldara/UmsCostOfLivingCalculator.tsx index 5665daae30a0..9d03a50583c0 100644 --- a/apps/web/components/connected/UmbodsmadurSkuldara/UmsCostOfLivingCalculator.tsx +++ b/apps/web/components/connected/UmbodsmadurSkuldara/UmsCostOfLivingCalculator.tsx @@ -1,5 +1,6 @@ import { useMemo, useState } from 'react' import { Control, Controller, useForm } from 'react-hook-form' +import { useIntl } from 'react-intl' import { PropsValue } from 'react-select' import { useQuery } from '@apollo/client/react' @@ -13,11 +14,10 @@ import { Text, } from '@island.is/island-ui/core' import { InputController } from '@island.is/shared/form-fields' -import { ConnectedComponent, Query } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' -import { useI18n } from '@island.is/web/i18n' +import { Query } from '@island.is/web/graphql/schema' import { GET_UMS_COST_OF_LIVING_CALCULATOR } from '@island.is/web/screens/queries/UmsCostOfLivingCalculator' +import { translation as translationStrings } from './translation.strings' import * as styles from './UmsCostOfLivingCalculator.css' interface CalculatedFieldWrapperProps { @@ -84,15 +84,10 @@ interface CostOfLivingInput { otherExpenses?: string } -interface CostOfLivingCalculatorProps { - slice: ConnectedComponent -} - type DataState = 'loading' | 'loaded' | 'error' -const UmsCostOfLivingCalculator = ({ slice }: CostOfLivingCalculatorProps) => { - const n = useNamespace(slice.json ?? {}) - const { activeLocale } = useI18n() +const UmsCostOfLivingCalculator = () => { + const { formatMessage } = useIntl() const methods = useForm({ defaultValues: { familySize: '1+0' }, }) @@ -115,196 +110,109 @@ const UmsCostOfLivingCalculator = ({ slice }: CostOfLivingCalculatorProps) => { const costOfLivingOptions = useMemo(() => { return [ { - label: n( - 'individualLabel', - activeLocale === 'is' ? 'Einstaklingur' : 'Individual', - ), + label: formatMessage(translationStrings.individualLabel), options: [ { - label: n( - 'individualChildlessLabel', - activeLocale === 'is' - ? 'Einstaklingur, barnlaus' - : 'Individual, childless', - ), + label: formatMessage(translationStrings.individualChildlessLabel), value: '1+0', }, { - label: n( - 'individualOneChildLabel', - activeLocale === 'is' - ? 'Einstaklingur með 1 barn' - : 'Individual with 1 child', - ), + label: formatMessage(translationStrings.individualOneChildLabel), value: '1+1', }, { - label: n( - 'individualTwoChildrenLabel', - activeLocale === 'is' - ? 'Einstaklingur með 2 börn' - : 'Individual with 2 children', - ), + label: formatMessage(translationStrings.individualTwoChildrenLabel), value: '1+2', }, { - label: n( - 'individualThreeChildrenLabel', - activeLocale === 'is' - ? 'Einstaklingur með 3 börn' - : 'Individual with 3 children', + label: formatMessage( + translationStrings.individualThreeChildrenLabel, ), value: '1+3', }, { - label: n( - 'individualFourChildrenLabel', - activeLocale === 'is' - ? 'Einstaklingur með 4 börn' - : 'Individual with 4 children', + label: formatMessage( + translationStrings.individualFourChildrenLabel, ), value: '1+4', }, { - label: n( - 'individualFiveChildrenLabel', - activeLocale === 'is' - ? 'Einstaklingur með 5 börn' - : 'Individual with 5 children', + label: formatMessage( + translationStrings.individualFiveChildrenLabel, ), value: '1+5', }, { - label: n( - 'individualSixChildrenLabel', - activeLocale === 'is' - ? 'Einstaklingur með 6 börn' - : 'Individual with 6 children', - ), + label: formatMessage(translationStrings.individualSixChildrenLabel), value: '1+6', }, { - label: n( - 'individualSevenChildrenLabel', - activeLocale === 'is' - ? 'Einstaklingur með 7 börn' - : 'Individual with 7 children', + label: formatMessage( + translationStrings.individualSevenChildrenLabel, ), value: '1+7', }, { - label: n( - 'individualEightChildrenLabel', - activeLocale === 'is' - ? 'Einstaklingur með 8 börn' - : 'Individual with 8 children', + label: formatMessage( + translationStrings.individualEightChildrenLabel, ), value: '1+8', }, { - label: n( - 'individualNineChildrenLabel', - activeLocale === 'is' - ? 'Einstaklingur með 9 börn' - : 'Individual with 9 children', + label: formatMessage( + translationStrings.individualNineChildrenLabel, ), value: '1+9', }, ], }, { - label: n('coupleLabel', activeLocale === 'is' ? 'Hjón' : 'Couple'), + label: formatMessage(translationStrings.coupleLabel), options: [ { - label: n( - 'coupleChildlessLable', - activeLocale === 'is' ? 'Hjón, barnlaus' : 'Couple, childless', - ), + label: formatMessage(translationStrings.coupleChildlessLable), value: '2+0', }, { - label: n( - 'coupleOneChildLabel', - activeLocale === 'is' ? 'Hjón með 1 barn' : 'Couple with 1 child', - ), + label: formatMessage(translationStrings.coupleOneChildLabel), value: '2+1', }, { - label: n( - 'coupleTwoChildrenLable', - activeLocale === 'is' - ? 'Hjón með 2 börn' - : 'Couple with 2 children', - ), + label: formatMessage(translationStrings.coupleTwoChildrenLable), value: '2+2', }, { - label: n( - 'coupleThreeChildrenLable', - activeLocale === 'is' - ? 'Hjón með 3 börn' - : 'Couple with 3 children', - ), + label: formatMessage(translationStrings.coupleThreeChildrenLable), value: '2+3', }, { - label: n( - 'coupleFourChildrenLable', - activeLocale === 'is' - ? 'Hjón með 4 börn' - : 'Couple with 4 children', - ), + label: formatMessage(translationStrings.coupleFourChildrenLable), value: '2+4', }, { - label: n( - 'coupleFiveChildrenLable', - activeLocale === 'is' - ? 'Hjón með 5 börn' - : 'Couple with 5 children', - ), + label: formatMessage(translationStrings.coupleFiveChildrenLable), value: '2+5', }, { - label: n( - 'coupleSixChildrenLable', - activeLocale === 'is' - ? 'Hjón með 6 barn' - : 'Couple with 6 children', - ), + label: formatMessage(translationStrings.coupleSixChildrenLable), value: '2+6', }, { - label: n( - 'coupleSevenChildrenLable', - activeLocale === 'is' - ? 'Hjón með 7 börn' - : 'Couple with 7 children', - ), + label: formatMessage(translationStrings.coupleSevenChildrenLable), value: '2+7', }, { - label: n( - 'coupleEightChildrenLable', - activeLocale === 'is' - ? 'Hjón með 8 börn' - : 'Couple with 8 children', - ), + label: formatMessage(translationStrings.coupleEightChildrenLable), value: '2+8', }, { - label: n( - 'coupleNineChildrenLable', - activeLocale === 'is' - ? 'Hjón með 9 börn' - : 'Couple with 9 children', - ), + label: formatMessage(translationStrings.coupleNineChildrenLable), value: '2+9', }, ], }, ] - }, []) + }, [formatMessage]) const getSelectedValues = (option: string | undefined) => { if (costOfLivingCalculator) { @@ -376,13 +284,8 @@ const UmsCostOfLivingCalculator = ({ slice }: CostOfLivingCalculatorProps) => { )} {dataState === 'error' && ( )} @@ -390,10 +293,7 @@ const UmsCostOfLivingCalculator = ({ slice }: CostOfLivingCalculatorProps) => { - {n( - 'costOfLivingCalculatorTitle', - activeLocale === 'is' ? 'Fjölskyldustærð' : 'Family size', - )} + {formatMessage(translationStrings.costOfLivingCalculatorTitle)} { defaultValue="1+0" render={({ field: { onChange, value } }) => ( setNameInput(ev.target.value)} hasError={inputError.length > 0} @@ -126,7 +107,7 @@ const ShipSearch = ({ namespace }: ShipSearchProps) => { } onClick={() => handleShipSearch(nameInput)} > - {n('search', 'Leita')} + {formatMessage(translationStrings.search)} @@ -141,17 +122,14 @@ const ShipSearch = ({ namespace }: ShipSearchProps) => { {ships.length === 0 && called && !loading && !error && ( - {n('noResultsFound', 'Engar niðurstöður fundust')} + {formatMessage(translationStrings.noResultsFound)} )} {error && ( - {n( - 'errorOccuredWhileFetchingShips', - 'Villa kom upp við að leita eftir skipi', - )} + {formatMessage(translationStrings.errorOccuredWhileFetchingShips)} )} @@ -159,16 +137,26 @@ const ShipSearch = ({ namespace }: ShipSearchProps) => { {ships.length > 0 && ( <> - {n('resultsFound', 'Fjöldi skipa:')} {ships.length} + {formatMessage(translationStrings.resultsFound)} {ships.length} - {n('shipNumber', 'Skipnr.')} - {n('shipName', 'Nafn')} - {n('typeOfVessel', 'Útgerðarflokkur')} - {n('operator', 'Útgerð')} - {n('homePort', 'Heimahöfn')} + + {formatMessage(translationStrings.shipNumber)} + + + {formatMessage(translationStrings.shipName)} + + + {formatMessage(translationStrings.typeOfVessel)} + + + {formatMessage(translationStrings.operator)} + + + {formatMessage(translationStrings.homePort)} + diff --git a/apps/web/components/connected/fiskistofa/ShipSearch/translation.strings.ts b/apps/web/components/connected/fiskistofa/ShipSearch/translation.strings.ts new file mode 100644 index 000000000000..fc5c041fe9d1 --- /dev/null +++ b/apps/web/components/connected/fiskistofa/ShipSearch/translation.strings.ts @@ -0,0 +1,69 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + shipDetailsHref: { + id: 'web.fiskistofaShipSearch:shipDetailsHref', + defaultMessage: '/v/gagnasidur-fiskistofu?selectedTab=skip', + description: 'Slóða á valið skip', + }, + shipDetailsNumberQueryParam: { + id: 'web.fiskistofaShipSearch:shipDetailsNumberQueryParam', + defaultMessage: 'nr', + description: 'nr', + }, + searchStringIsTooShort: { + id: 'web.fiskistofaShipSearch:searchStringIsTooShort', + defaultMessage: 'Leitarstrengur þarf að vera a.m.k. 2 stafir', + description: 'Leitarstrengur þarf að vera a.m.k. 2 stafir', + }, + shipSearchInputLabel: { + id: 'web.fiskistofaShipSearch:shipSearchInputLabel', + defaultMessage: 'Skipaskrárnúmer eða nafn skips', + description: 'Skipaskrárnúmer eða nafn skips', + }, + search: { + id: 'web.fiskistofaShipSearch:search', + defaultMessage: 'Leita', + description: 'Leita', + }, + noResultsFound: { + id: 'web.fiskistofaShipSearch:noResultsFound', + defaultMessage: 'Engar niðurstöður fundust', + description: 'Engar niðurstöður fundust', + }, + errorOccuredWhileFetchingShips: { + id: 'web.fiskistofaShipSearch:errorOccuredWhileFetchingShips', + defaultMessage: 'Villa kom upp við að leita eftir skipi', + description: 'Texti fyrir villu skilaboð þegar ekki tekst að sækja gögn', + }, + resultsFound: { + id: 'web.fiskistofaShipSearch:resultsFound', + defaultMessage: 'Fjöldi skipa:', + description: 'Fjöldi skipa:', + }, + shipNumber: { + id: 'web.fiskistofaShipSearch:shipNumber', + defaultMessage: 'Skipnr.', + description: 'Skipa númer', + }, + shipName: { + id: 'web.fiskistofaShipSearch:shipName', + defaultMessage: 'Nafn', + description: 'Nafn', + }, + typeOfVessel: { + id: 'web.fiskistofaShipSearch:typeOfVessel', + defaultMessage: 'Útgerðarflokkur', + description: 'Útgerðarflokkur', + }, + operator: { + id: 'web.fiskistofaShipSearch:operator', + defaultMessage: 'Útgerð', + description: 'Útgerð', + }, + homePort: { + id: 'web.fiskistofaShipSearch:homePort', + defaultMessage: 'Heimahöfn', + description: 'Heimahöfn', + }, +}) diff --git a/apps/web/components/connected/fiskistofa/ShipSearchBoxedInput/ShipSearchBoxedInput.tsx b/apps/web/components/connected/fiskistofa/ShipSearchBoxedInput/ShipSearchBoxedInput.tsx index 2df70f0d2343..c2ed7667216f 100644 --- a/apps/web/components/connected/fiskistofa/ShipSearchBoxedInput/ShipSearchBoxedInput.tsx +++ b/apps/web/components/connected/fiskistofa/ShipSearchBoxedInput/ShipSearchBoxedInput.tsx @@ -1,5 +1,7 @@ import { useState } from 'react' +import { useIntl } from 'react-intl' import { useRouter } from 'next/router' + import { Box, BoxProps, @@ -11,11 +13,12 @@ import { Stack, Text, } from '@island.is/island-ui/core' -import { useNamespace } from '@island.is/web/hooks' +import { SpanType } from '@island.is/island-ui/core/types' +import { theme } from '@island.is/island-ui/theme' import { shouldLinkOpenInNewWindow } from '@island.is/shared/utils' import { useWindowSize } from '@island.is/web/hooks/useViewport' -import { theme } from '@island.is/island-ui/theme' -import { SpanType } from '@island.is/island-ui/core/types' + +import { translation as translationStrings } from './translation.strings' const INPUT_COLUMN_SPAN: SpanType = [ '12/12', @@ -40,20 +43,9 @@ const SEARCH_BUTTON_JUSTIFY_CONTENT: BoxProps['justifyContent'] = [ ] const SEARCH_BUTTON_MARGIN_TOP: ResponsiveSpace = [3, 3, 3, 3, 0] -interface ShipSearchBoxedInputProps { - namespace: { - shipDetailsHref?: string - shipSearchHref?: string - placeholder?: string - label?: string - title?: string - description?: string - } -} - -const ShipSearchBoxedInput = ({ namespace }: ShipSearchBoxedInputProps) => { +const ShipSearchBoxedInput = () => { const { width } = useWindowSize() - const n = useNamespace(namespace) + const { formatMessage } = useIntl() const [searchValue, setSearchValue] = useState('') const router = useRouter() @@ -61,10 +53,11 @@ const ShipSearchBoxedInput = ({ namespace }: ShipSearchBoxedInputProps) => { const searchValueIsNumber = !isNaN(Number(searchValue)) && searchValue.length > 0 if (searchValueIsNumber) { - const pathname = n('shipDetailsHref', '/v/gagnasidur-fiskistofu') + const pathname = formatMessage(translationStrings.shipDetailsHref) const query = { ...router.query, - [n('shipDetailsNumberQueryParam', 'nr')]: searchValue, + [formatMessage(translationStrings.shipDetailsNumberQueryParam)]: + searchValue, selectedTab: router.query?.selectedTab ?? 'skip', } @@ -84,20 +77,17 @@ const ShipSearchBoxedInput = ({ namespace }: ShipSearchBoxedInputProps) => { } else { const query = { ...router.query, name: searchValue } router.push({ - pathname: n('shipSearchHref', '/s/fiskistofa/skipaleit'), + pathname: formatMessage(translationStrings.shipSearchHref), query, }) } } - const label = n('label', 'Skipaskrárnúmer eða nafn skips') - const placeholder = n('placeholder', '') - const title = n('title', 'Skipaleit') - const description = n( - 'description', - 'Upplýsingar um skip, veiðiheimildir, landanir og fleira', - ) - const searchButtonText = n('searcButtonText', 'Leita') + const label = formatMessage(translationStrings.label) + const placeholder = formatMessage(translationStrings.placeholder) + const title = formatMessage(translationStrings.title) + const description = formatMessage(translationStrings.description) + const searchButtonText = formatMessage(translationStrings.searcButtonText) return ( diff --git a/apps/web/components/connected/fiskistofa/ShipSearchBoxedInput/translation.strings.ts b/apps/web/components/connected/fiskistofa/ShipSearchBoxedInput/translation.strings.ts new file mode 100644 index 000000000000..7387a303d62b --- /dev/null +++ b/apps/web/components/connected/fiskistofa/ShipSearchBoxedInput/translation.strings.ts @@ -0,0 +1,44 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + shipDetailsHref: { + id: 'web.fiskistofaShipSearchBoxedInput:shipDetailsHref', + defaultMessage: '/v/gagnasidur-fiskistofu', + description: '/v/gagnasidur-fiskistofu', + }, + shipDetailsNumberQueryParam: { + id: 'web.fiskistofaShipSearchBoxedInput:shipDetailsNumberQueryParam', + defaultMessage: 'nr', + description: 'nr', + }, + shipSearchHref: { + id: 'web.fiskistofaShipSearchBoxedInput:shipSearchHref', + defaultMessage: '/s/fiskistofa/skipaleit', + description: '/s/fiskistofa/skipaleit', + }, + label: { + id: 'web.fiskistofaShipSearchBoxedInput:label', + defaultMessage: 'Skipaskrárnúmer eða nafn skips', + description: 'Skipaskrárnúmer eða nafn skips', + }, + placeholder: { + id: 'web.fiskistofaShipSearchBoxedInput:placeholder', + defaultMessage: '', + description: 'Placeholder', + }, + title: { + id: 'web.fiskistofaShipSearchBoxedInput:title', + defaultMessage: 'Skipaleit', + description: 'Skipaleit', + }, + description: { + id: 'web.fiskistofaShipSearchBoxedInput:description', + defaultMessage: 'Upplýsingar um skip, veiðiheimildir, landanir og fleira', + description: 'Upplýsingar um skip, veiðiheimildir, landanir og fleira', + }, + searcButtonText: { + id: 'web.fiskistofaShipSearchBoxedInput:searcButtonText', + defaultMessage: 'Leita', + description: 'Leita', + }, +}) diff --git a/apps/web/components/connected/fiskistofa/SidebarShipSearchInput/SidebarShipSearchInput.tsx b/apps/web/components/connected/fiskistofa/SidebarShipSearchInput/SidebarShipSearchInput.tsx index 51cc8559a804..a9ac05bfe9c8 100644 --- a/apps/web/components/connected/fiskistofa/SidebarShipSearchInput/SidebarShipSearchInput.tsx +++ b/apps/web/components/connected/fiskistofa/SidebarShipSearchInput/SidebarShipSearchInput.tsx @@ -1,20 +1,14 @@ import { useState } from 'react' +import { useIntl } from 'react-intl' import { useRouter } from 'next/router' + import { AsyncSearchInput, Box, Text } from '@island.is/island-ui/core' -import { useNamespace } from '@island.is/web/hooks' import { shouldLinkOpenInNewWindow } from '@island.is/shared/utils' -interface SidebarShipSearchInputProps { - namespace: { - shipDetailsHref?: string - shipSearchHref?: string - placeholder?: string - label?: string - } -} +import { translation as translationStrings } from './translation.strings' -const SidebarShipSearchInput = ({ namespace }: SidebarShipSearchInputProps) => { - const n = useNamespace(namespace) +const SidebarShipSearchInput = () => { + const { formatMessage } = useIntl() const [searchValue, setSearchValue] = useState('') const router = useRouter() const [hasFocus, setHasFocus] = useState(false) @@ -23,10 +17,11 @@ const SidebarShipSearchInput = ({ namespace }: SidebarShipSearchInputProps) => { const searchValueIsNumber = !isNaN(Number(searchValue)) && searchValue.length > 0 if (searchValueIsNumber) { - const pathname = n('shipDetailsHref', '/v/gagnasidur-fiskistofu') + const pathname = formatMessage(translationStrings.shipDetailsHref) const query = { ...router.query, - [n('shipDetailsNumberQueryParam', 'nr')]: searchValue, + [formatMessage(translationStrings.shipDetailsNumberQueryParam)]: + searchValue, selectedTab: router.query?.selectedTab ?? 'skip', } @@ -46,14 +41,14 @@ const SidebarShipSearchInput = ({ namespace }: SidebarShipSearchInputProps) => { } else { const query = { ...router.query, name: searchValue } router.push({ - pathname: n('shipSearchHref', '/s/fiskistofa/skipaleit'), + pathname: formatMessage(translationStrings.shipSearchHref), query, }) } } - const label = n('label', 'Skoða skip') - const placeholder = n('placeholder', 'Skipaskrárnúmer eða nafn') + const label = formatMessage(translationStrings.label) + const placeholder = formatMessage(translationStrings.placeholder) return ( diff --git a/apps/web/components/connected/fiskistofa/SidebarShipSearchInput/translation.strings.ts b/apps/web/components/connected/fiskistofa/SidebarShipSearchInput/translation.strings.ts new file mode 100644 index 000000000000..b08d248ab489 --- /dev/null +++ b/apps/web/components/connected/fiskistofa/SidebarShipSearchInput/translation.strings.ts @@ -0,0 +1,29 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + shipDetailsHref: { + id: 'web.fiskistofaSidebarShipSearchInput:shipDetailsHref', + defaultMessage: '/v/gagnasidur-fiskistofu', + description: '/v/gagnasidur-fiskistofu', + }, + shipDetailsNumberQueryParam: { + id: 'web.fiskistofaSidebarShipSearchInput:shipDetailsNumberQueryParam', + defaultMessage: 'nr', + description: 'nr', + }, + shipSearchHref: { + id: 'web.fiskistofaSidebarShipSearchInput:shipSearchHref', + defaultMessage: '/s/fiskistofa/skipaleit', + description: '/s/fiskistofa/skipaleit', + }, + label: { + id: 'web.fiskistofaSidebarShipSearchInput:label', + defaultMessage: 'Skoða skip', + description: 'Skoða skip', + }, + placeholder: { + id: 'web.fiskistofaSidebarShipSearchInput:placeholder', + defaultMessage: 'Skipaskrárnúmer eða nafn', + description: 'Skipaskrárnúmer eða nafn', + }, +}) diff --git a/apps/web/components/connected/fiskistofa/calculators/CatchQuotaCalculator/CatchQuotaCalculator.tsx b/apps/web/components/connected/fiskistofa/calculators/CatchQuotaCalculator/CatchQuotaCalculator.tsx index 63b0bbbaf866..9908a6482890 100644 --- a/apps/web/components/connected/fiskistofa/calculators/CatchQuotaCalculator/CatchQuotaCalculator.tsx +++ b/apps/web/components/connected/fiskistofa/calculators/CatchQuotaCalculator/CatchQuotaCalculator.tsx @@ -1,7 +1,10 @@ -import { useRouter } from 'next/router' import { useEffect, useMemo, useRef, useState } from 'react' -import { useMachine } from '@xstate/react' +import { useIntl } from 'react-intl' import cn from 'classnames' +import { useRouter } from 'next/router' +import { useMachine } from '@xstate/react' + +import { FiskistofaExtendedCatchQuotaCategory as ExtendedCatchQuotaCategory } from '@island.is/api/schema' import { Box, Button, @@ -11,18 +14,17 @@ import { Tag, Text, } from '@island.is/island-ui/core' -import { useNamespace } from '@island.is/web/hooks' -import { FiskistofaExtendedCatchQuotaCategory as ExtendedCatchQuotaCategory } from '@island.is/api/schema' + import { formattedNumberStringToNumber, generateTimePeriodOptions, + isNumberBelowZero, + numberFormatter, sevenFractionDigitNumberFormatter, TimePeriodOption, - numberFormatter, - isNumberBelowZero, } from '../utils' -import { Context, machine, Event as EventType } from './machine' - +import { Context, Event as EventType, machine } from './machine' +import { translation as translationStrings } from './translation.strings' import * as styles from './CatchQuotaCalculator.css' const QUOTA_CHANGE_DEBOUNCE_TIME = 1000 @@ -81,13 +83,10 @@ interface QuotaStateChangeMetadata { timerId: number | null } -interface CatchQuotaCalculatorProps { - namespace: Record -} - -const CatchQuotaCalculator = ({ namespace }: CatchQuotaCalculatorProps) => { +const CatchQuotaCalculator = () => { const timePeriodOptions = useMemo(() => generateTimePeriodOptions(), []) - const n = useNamespace(namespace) + const { formatMessage } = useIntl() + const [selectedTimePeriod, setSelectedTimePeriod] = useState( timePeriodOptions[0], ) @@ -357,7 +356,7 @@ const CatchQuotaCalculator = ({ namespace }: CatchQuotaCalculatorProps) => { - {n('reset', 'Frumstilla')} + {formatMessage(translationStrings.reset)} - {n('calendarYear', 'Almanaksárið')} 01.01.{selectedYear.label} - 31.12. + {formatMessage(translationStrings.calendarYear)} 01.01. + {selectedYear.label} - 31.12. {selectedYear.label} @@ -306,7 +307,7 @@ const StraddlingStockCalculator = ({ size="small" colorScheme="default" > - {n('clearAll', 'Hreinsa allt')} + {formatMessage(translationStrings.clearAll)} )} @@ -314,9 +315,7 @@ const StraddlingStockCalculator = ({ {state.matches('error') && ( - - {n('deilistofnaError', 'Villa kom upp við að sækja gögn')} - + {formatMessage(translationStrings.deilistofnaError)} )} @@ -325,7 +324,7 @@ const StraddlingStockCalculator = ({ - + {state.context.data.catchQuotaCategories.map((category) => { return })} @@ -333,7 +332,7 @@ const StraddlingStockCalculator = ({ - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => { return ( @@ -517,7 +516,7 @@ const StraddlingStockCalculator = ({ })} - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => ( - + {state.context.data.catchQuotaCategories.map((category) => (
{n('kvotategund', 'Kvótategund')}{formatMessage(translationStrings.kvotategund)}{category.name}
{n('codEquivalentRatio', 'Þorskígildisstuðull')}{formatMessage(translationStrings.codEquivalentRatio)}
{n('uthlutun', 'Úthlutun')}{formatMessage(translationStrings.uthlutun)}
{n('serstokUthlutun', 'Sérst. úthl.')}{formatMessage(translationStrings.serstokUthlutun)}
{n('milliAra', 'Milli ára')}{formatMessage(translationStrings.milliAra)}
{n('milliSkipa', 'Milli skipa')}{formatMessage(translationStrings.milliSkipa)}
{n('aflamarksbreyting', 'Aflamarksbr.')}{formatMessage(translationStrings.aflamarksbreyting)} {category.id === 0 ? ( @@ -449,7 +448,7 @@ const StraddlingStockCalculator = ({ ))}
{n('aflamark', 'Aflamark')}{formatMessage(translationStrings.aflamark)}
{n('afli', 'Afli')}{formatMessage(translationStrings.afli)}
{n('aflabreyting', 'Aflabreyting')}{formatMessage(translationStrings.aflabreyting)}
{n('stada', 'Staða')}{formatMessage(translationStrings.stada)}
{n('tilfaersla', 'Tilfærsla')}{formatMessage(translationStrings.tilfaersla)}
{n('nyStada', 'Ný staða')}{formatMessage(translationStrings.nyStada)}
{n('aNaestaAr', 'Á næsta ár')}{formatMessage(translationStrings.aNaestaAr)}
{n('umframafli', 'Umframafli')}{formatMessage(translationStrings.umframafli)}
{n('onotad', 'Ónotað')}{formatMessage(translationStrings.onotad)} > = ({ slice }) => { - const n = useNamespace(slice.json ?? {}) + const { formatMessage } = useIntl() const { format } = useDateUtils() const PAGE_SIZE = slice?.configJson?.pageSize ?? DEFAULT_PAGE_SIZE - const DATE_FORMAT = n('dateFormat', 'd. MMMM yyyy') + const DATE_FORMAT = formatMessage(t.dateFormat) const [listState, setListState] = useState('loading') const [showCount, setShowCount] = useState(PAGE_SIZE) @@ -88,15 +91,15 @@ const AlcoholLicencesList: FC< return new Promise((resolve, reject) => { if (alcoholLicences) { const headerRow = [ - n('csvHeaderLicenceType', 'Tegund'), - n('csvHeaderLicenceSubType', 'Tegund leyfis'), - n('csvHeaderLicenseNumber', 'Leyfisnúmer'), - n('csvHeaderLicenseHolder', 'Leyfishafi'), - n('csvHeaderLicenseResponsible', 'Ábyrgðarmaður'), - n('csvHeaderValidFrom', 'Gildir frá'), - n('csvHeaderValidTo', 'Gildir til'), - n('csvHeaderOffice', 'Embætti'), - n('csvHeaderLocation', 'Starfsstöð embættis'), + formatMessage(t.csvHeaderLicenceType), + formatMessage(t.csvHeaderLicenceSubType), + formatMessage(t.csvHeaderLicenseNumber), + formatMessage(t.csvHeaderLicenseHolder), + formatMessage(t.csvHeaderLicenseResponsible), + formatMessage(t.csvHeaderValidFrom), + formatMessage(t.csvHeaderValidTo), + formatMessage(t.csvHeaderOffice), + formatMessage(t.csvHeaderLocation), ] const dataRows = [] for (const alcoholLicence of alcoholLicences) { @@ -121,7 +124,7 @@ const AlcoholLicencesList: FC< } // Filter - Office - const allOfficesOption = n('filterOfficeAll', 'Öll embætti') + const allOfficesOption = formatMessage(t.filterOfficeAll) const avaibleOfficesOptions = [ allOfficesOption, ...Array.from( @@ -135,7 +138,7 @@ const AlcoholLicencesList: FC< ) // Filter - Type - const allLicenceTypeOption = n('filterLicenceTypeAll', 'Allar tegundir') + const allLicenceTypeOption = formatMessage(t.filterLicenceTypeAll) const avaibleLicenceTypeOptions = [ allLicenceTypeOption, ...Array.from( @@ -191,8 +194,8 @@ const AlcoholLicencesList: FC< )} {listState === 'error' && ( )} @@ -210,7 +213,7 @@ const AlcoholLicencesList: FC< icon="chevronDown" size="sm" isSearchable - label={n('alcoholLicencesFilterLicenceType', 'Tegund')} + label={formatMessage(t.alcoholLicencesFilterLicenceType)} name="licenceTypeSelect" options={avaibleLicenceTypeOptions.map((x) => ({ label: x, @@ -238,7 +241,7 @@ const AlcoholLicencesList: FC< icon="chevronDown" size="sm" isSearchable - label={n('alcoholLicencesFilterOffice', 'Embætti')} + label={formatMessage(t.alcoholLicencesFilterOffice)} name="officeSelect" options={avaibleOfficesOptions.map((x) => ({ label: x, @@ -262,7 +265,7 @@ const AlcoholLicencesList: FC< @@ -296,7 +290,7 @@ const AlcoholLicencesList: FC< )} {listState === 'loaded' && filteredAlcoholLicences.length === 0 && ( - {n('noResults', 'Engin leyfi fundust.')} + {formatMessage(t.noResults)} )} {listState === 'loaded' && filteredAlcoholLicences.length > 0 && ( @@ -341,12 +335,12 @@ const AlcoholLicencesList: FC< {alcoholLicence.licenseHolder} - {n('licenseNumber', 'Leyfisnúmer')}:{' '} + {formatMessage(t.licenseNumber)}:{' '} {alcoholLicence.licenseNumber} - {n('validPeriodLabel', 'Gildistími')}:{' '} + {formatMessage(t.validPeriodLabel)}:{' '} {getValidPeriodRepresentation( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore make web strict @@ -354,18 +348,15 @@ const AlcoholLicencesList: FC< alcoholLicence.validTo, DATE_FORMAT, format, - n('validPeriodUntil', 'Til'), - n('validPeriodIndefinite', 'Ótímabundið'), + formatMessage(t.validPeriodUntil), + formatMessage(t.validPeriodIndefinite), )} - {n('licenseResponsible', 'Ábyrgðarmaður')}:{' '} + {formatMessage(t.licenseResponsible)}:{' '} {alcoholLicence.licenseResponsible || - n( - 'licenseResponsibleNotRegistered', - 'Enginn skráður', - )} + formatMessage(t.licenseResponsibleNotRegistered)} @@ -380,7 +371,7 @@ const AlcoholLicencesList: FC< > {showCount < filteredAlcoholLicences.length && ( )} diff --git a/apps/web/components/connected/syslumenn/CardLists/AlcoholLicencesList/translation.strings.ts b/apps/web/components/connected/syslumenn/CardLists/AlcoholLicencesList/translation.strings.ts new file mode 100644 index 000000000000..a376883ec819 --- /dev/null +++ b/apps/web/components/connected/syslumenn/CardLists/AlcoholLicencesList/translation.strings.ts @@ -0,0 +1,149 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + dateFormat: { + id: 'web.syslumenn.alcoholLicencesList:dateFormat', + defaultMessage: 'd. MMMM yyyy', + description: 'Hvernig dagsetningin birtist', + }, + csvHeaderLicenceType: { + id: 'web.syslumenn.alcoholLicencesList:csvHeaderLicenceType', + defaultMessage: 'Tegund', + description: 'Tegund (texti fyrir dálk í CSV skrá)', + }, + csvHeaderLicenceSubType: { + id: 'web.syslumenn.alcoholLicencesList:csvHeaderLicenceSubType', + defaultMessage: 'Tegund leyfis', + description: 'Tegund leyfis (texti fyrir dálk í CSV skrá)', + }, + csvHeaderLicenseNumber: { + id: 'web.syslumenn.alcoholLicencesList:csvHeaderLicenseNumber', + defaultMessage: 'Leyfisnúmer', + description: 'Leyfisnúmer (texti fyrir dálk í CSV skrá)', + }, + csvHeaderLicenseHolder: { + id: 'web.syslumenn.alcoholLicencesList:csvHeaderLicenseHolder', + defaultMessage: 'Leyfishafi', + description: 'Leyfishafi (texti fyrir dálk í CSV skrá)', + }, + csvHeaderLicenseResponsible: { + id: 'web.syslumenn.alcoholLicencesList:csvHeaderLicenseResponsible', + defaultMessage: 'Ábyrgðarmaður', + description: 'Ábyrgðarmaður (texti fyrir dálk í CSV skrá)', + }, + csvHeaderValidFrom: { + id: 'web.syslumenn.alcoholLicencesList:csvHeaderValidFrom', + defaultMessage: 'Gildir frá', + description: 'Gildir frá (texti fyrir dálk í CSV skrá)', + }, + csvHeaderValidTo: { + id: 'web.syslumenn.alcoholLicencesList:csvHeaderValidTo', + defaultMessage: 'Gildir til', + description: 'Gildir til (texti fyrir dálk í CSV skrá)', + }, + csvHeaderOffice: { + id: 'web.syslumenn.alcoholLicencesList:csvHeaderOffice', + defaultMessage: 'Embætti', + description: 'Embætti (texti fyrir dálk í CSV skrá)', + }, + csvHeaderLocation: { + id: 'web.syslumenn.alcoholLicencesList:csvHeaderLocation', + defaultMessage: 'Starfsstöð embættis', + description: 'Starfsstöð embættis (texti fyrir dálk í CSV skrá)', + }, + filterOfficeAll: { + id: 'web.syslumenn.alcoholLicencesList:filterOfficeAll', + defaultMessage: 'Öll embætti', + description: 'Öll embætti', + }, + filterLicenceTypeAll: { + id: 'web.syslumenn.alcoholLicencesList:filterLicenceTypeAll', + defaultMessage: 'Allar tegundir', + description: 'Allar tegundir', + }, + errorTitle: { + id: 'web.syslumenn.alcoholLicencesList:errorTitle', + defaultMessage: 'Villa', + description: 'Titill á villuskilaboðum ef ekki tókst að sækja áfengisleyfi', + }, + errorMessage: { + id: 'web.syslumenn.alcoholLicencesList:errorMessage', + defaultMessage: 'Ekki tókst að sækja áfengisleyfi.', + description: 'Villuskilaboð ef ekki tókst að sækja áfengisleyfi', + }, + alcoholLicencesFilterLicenceType: { + id: 'web.syslumenn.alcoholLicencesList:alcoholLicencesFilterLicenceType', + defaultMessage: 'Tegund', + description: 'Label á tegund filter', + }, + alcoholLicencesFilterOffice: { + id: 'web.syslumenn.alcoholLicencesList:alcoholLicencesFilterOffice', + defaultMessage: 'Embætti', + description: 'Label á embættis filter', + }, + searchPlaceholder: { + id: 'web.syslumenn.alcoholLicencesList:searchPlaceholder', + defaultMessage: 'Leita', + description: 'Placeholder texti í leitarboxi', + }, + csvButtonLabelDefault: { + id: 'web.syslumenn.alcoholLicencesList:csvButtonLabelDefault', + defaultMessage: 'Sækja öll leyfi (CSV).', + description: 'Texti fyrir CSV hnapp', + }, + csvButtonLabelLoading: { + id: 'web.syslumenn.alcoholLicencesList:csvButtonLabelLoading', + defaultMessage: 'Sæki öll leyfi...', + description: 'Texti þegar smellt er á CSV hnapp', + }, + csvButtonLabelError: { + id: 'web.syslumenn.alcoholLicencesList:csvButtonLabelError', + defaultMessage: 'Ekki tókst að sækja leyfi, reyndu aftur.', + description: 'Texti fyrir CSV hnapp ef ekki tókst að sækja skrá', + }, + csvFileTitlePrefix: { + id: 'web.syslumenn.alcoholLicencesList:csvFileTitlePrefix', + defaultMessage: 'Áfengisleyfi', + description: 'Titill á CSV skrá', + }, + noResults: { + id: 'web.syslumenn.alcoholLicencesList:noResults', + defaultMessage: 'Engin leyfi fundust', + description: 'Texti sem birtist ef engin leyfi fundust', + }, + licenseNumber: { + id: 'web.syslumenn.alcoholLicencesList:licenseNumber', + defaultMessage: 'Leyfisnúmer', + description: 'Leyfisnúmer', + }, + validPeriodLabel: { + id: 'web.syslumenn.alcoholLicencesList:validPeriodLabel', + defaultMessage: 'Gildistími', + description: 'Gildistími', + }, + validPeriodUntil: { + id: 'web.syslumenn.alcoholLicencesList:validPeriodUntil', + defaultMessage: 'Til', + description: 'Til', + }, + validPeriodIndefinite: { + id: 'web.syslumenn.alcoholLicencesList:validPeriodIndefinite', + defaultMessage: 'Ótímabundið', + description: 'Ótímabundið', + }, + licenseResponsible: { + id: 'web.syslumenn.alcoholLicencesList:licenseResponsible', + defaultMessage: 'Ábyrgðarmaður', + description: 'Ábyrgðarmaður', + }, + licenseResponsibleNotRegistered: { + id: 'web.syslumenn.alcoholLicencesList:licenseResponsibleNotRegistered', + defaultMessage: 'Enginn skráður', + description: 'Enginn skráður', + }, + loadMore: { + id: 'web.syslumenn.alcoholLicencesList:loadMore', + defaultMessage: 'Sjá fleiri', + description: 'Sjá fleiri', + }, +}) diff --git a/apps/web/components/connected/syslumenn/CardLists/TemporaryEventLicencesList/TemporaryEventLicencesList.tsx b/apps/web/components/connected/syslumenn/CardLists/TemporaryEventLicencesList/TemporaryEventLicencesList.tsx index 2f2fd298e566..0ddcc8a4a0fa 100644 --- a/apps/web/components/connected/syslumenn/CardLists/TemporaryEventLicencesList/TemporaryEventLicencesList.tsx +++ b/apps/web/components/connected/syslumenn/CardLists/TemporaryEventLicencesList/TemporaryEventLicencesList.tsx @@ -1,34 +1,37 @@ import { FC, useState } from 'react' +import { useIntl } from 'react-intl' import { useQuery } from '@apollo/client/react' -import { GET_TEMPORARY_EVENT_LICENCES } from './queries' + +import { + AlertMessage, + Box, + Button, + GridColumn, + GridContainer, + GridRow, + Input, + LoadingDots, + Select, + Tag, + Text, +} from '@island.is/island-ui/core' +import { SyslumennListCsvExport } from '@island.is/web/components' import { ConnectedComponent, Maybe, Query, TemporaryEventLicence, } from '@island.is/web/graphql/schema' +import { useDateUtils } from '@island.is/web/i18n/useDateUtils' + import { - prepareCsvString, - textSearch, getNormalizedSearchTerms, getValidPeriodRepresentation, + prepareCsvString, + textSearch, } from '../../utils' -import { - Box, - Button, - Tag, - LoadingDots, - Text, - Input, - AlertMessage, - Select, - GridContainer, - GridRow, - GridColumn, -} from '@island.is/island-ui/core' -import { SyslumennListCsvExport } from '@island.is/web/components' -import { useNamespace } from '@island.is/web/hooks' -import { useDateUtils } from '@island.is/web/i18n/useDateUtils' +import { GET_TEMPORARY_EVENT_LICENCES } from './queries' +import { translation as t } from './translation.strings' const DEFAULT_PAGE_SIZE = 10 @@ -41,10 +44,10 @@ type ListState = 'loading' | 'loaded' | 'error' const TemporaryEventLicencesList: FC< React.PropsWithChildren > = ({ slice }) => { - const n = useNamespace(slice.json ?? {}) + const { formatMessage } = useIntl() const { format } = useDateUtils() const PAGE_SIZE = slice?.configJson?.pageSize ?? DEFAULT_PAGE_SIZE - const DATE_FORMAT = n('dateFormat', "d. MMMM yyyy 'kl.' HH:mm") + const DATE_FORMAT = formatMessage(t.dateFormat) const [listState, setListState] = useState('loading') const [showCount, setShowCount] = useState(PAGE_SIZE) @@ -89,14 +92,14 @@ const TemporaryEventLicencesList: FC< return new Promise((resolve, reject) => { if (temporaryEventLicences) { const headerRow = [ - n('csvHeaderLicenceType', 'Tegund'), - n('csvHeaderLicenceSubType', 'Tegund leyfis'), - n('csvHeaderLicenseNumber', 'Leyfisnúmer'), - n('csvHeaderLicenseHolder', 'Leyfishafi'), - n('csvHeaderLicenseResponsible', 'Ábyrgðarmaður'), - n('csvHeaderValidFrom', 'Gildir frá'), - n('csvHeaderValidTo', 'Gildir til'), - n('csvHeaderIssuedBy', 'Útgefið af'), + formatMessage(t.csvHeaderLicenceType), + formatMessage(t.csvHeaderLicenceSubType), + formatMessage(t.csvHeaderLicenseNumber), + formatMessage(t.csvHeaderLicenseHolder), + formatMessage(t.csvHeaderLicenseResponsible), + formatMessage(t.csvHeaderValidFrom), + formatMessage(t.csvHeaderValidTo), + formatMessage(t.csvHeaderIssuedBy), ] const dataRows = [] for (const temporaryEventLicence of temporaryEventLicences) { @@ -118,7 +121,7 @@ const TemporaryEventLicencesList: FC< } // Filter - Office - const allOfficesOption = n('filterOfficeAll', 'Öll embætti') + const allOfficesOption = formatMessage(t.filterOfficeAll) const avaibleOfficesOptions = [ allOfficesOption, ...Array.from( @@ -132,7 +135,7 @@ const TemporaryEventLicencesList: FC< ) // Filter - SubType - const allLicenceSubTypeOption = n('filterLicenceSubTypeAll', 'Allar tegundir') + const allLicenceSubTypeOption = formatMessage(t.filterLicenceSubTypeAll) const avaibleLicenceSubTypeOptions = [ allLicenceSubTypeOption, ...Array.from( @@ -192,8 +195,8 @@ const TemporaryEventLicencesList: FC< )} {listState === 'error' && ( )} @@ -211,7 +214,7 @@ const TemporaryEventLicencesList: FC< icon="chevronDown" size="sm" isSearchable - label={n('alcoholLicencesFilterLicenceSubType', 'Tegund')} + label={formatMessage(t.alcoholLicencesFilterLicenceSubType)} name="licenceSubTypeSelect" options={avaibleLicenceSubTypeOptions.map((x) => ({ label: x, @@ -239,7 +242,7 @@ const TemporaryEventLicencesList: FC< icon="chevronDown" size="sm" isSearchable - label={n('alcoholLicencesFilterOffice', 'Embætti')} + label={formatMessage(t.alcoholLicencesFilterOffice)} name="officeSelect" options={avaibleOfficesOptions.map((x) => ({ label: x, @@ -263,7 +266,7 @@ const TemporaryEventLicencesList: FC< @@ -300,7 +291,7 @@ const TemporaryEventLicencesList: FC< )} {listState === 'loaded' && filteredTemporaryEventLicences.length === 0 && ( - {n('noResults', 'Engin leyfi fundust.')} + {formatMessage(t.noResults)} )} {listState === 'loaded' && filteredTemporaryEventLicences.length > 0 && ( @@ -343,12 +334,12 @@ const TemporaryEventLicencesList: FC< - {n('licenseNumber', 'Leyfisnúmer')}:{' '} + {formatMessage(t.licenseNumber)}:{' '} {temporaryEventLicence.licenseNumber} - {n('validPeriodLabel', 'Gildistími')}:{' '} + {formatMessage(t.validPeriodLabel)}:{' '} {getValidPeriodRepresentation( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore make web strict @@ -356,47 +347,28 @@ const TemporaryEventLicencesList: FC< temporaryEventLicence.validTo, DATE_FORMAT, format, - n('validPeriodUntil', 'Til'), - n('validPeriodIndefinite', 'Ótímabundið'), + formatMessage(t.validPeriodUntil), + formatMessage(t.validPeriodIndefinite), )} - {n('licenseResponsible', 'Ábyrgðarmaður')}:{' '} + {formatMessage(t.licenseResponsible)}:{' '} {temporaryEventLicence.licenseResponsible || - n( - 'licenseResponsibleNotRegistered', - 'Enginn skráður', - )} + formatMessage(t.licenseResponsibleNotRegistered)} - {typeof temporaryEventLicence.estimatedNumberOfGuests === - 'number' && - temporaryEventLicence.estimatedNumberOfGuests > 0 && ( - - {n( - 'licenseEstimatedNumberOfGuests', - 'Áætlaður fjöldi gesta', - )} - : {temporaryEventLicence.estimatedNumberOfGuests} - - )} + {temporaryEventLicence.estimatedNumberOfGuests && ( + + {formatMessage(t.licenseEstimatedNumberOfGuests)}:{' '} + {temporaryEventLicence.estimatedNumberOfGuests} + + )} - {typeof temporaryEventLicence.maximumNumberOfGuests === - 'number' && - temporaryEventLicence.maximumNumberOfGuests > 0 && ( - - {n( - 'licenseMaximumNumberOfGuests', - 'Hámarksfjöldi gesta', - )} - : {temporaryEventLicence.maximumNumberOfGuests} - - )} - {temporaryEventLicence.location && ( + {temporaryEventLicence.maximumNumberOfGuests && ( - {n('licenceLocationPrefix', 'Staðsetning')}:{' '} - {temporaryEventLicence.location} + {formatMessage(t.licenseMaximumNumberOfGuests)}:{' '} + {temporaryEventLicence.maximumNumberOfGuests} )} @@ -412,7 +384,7 @@ const TemporaryEventLicencesList: FC< > {showCount < filteredTemporaryEventLicences.length && ( )} diff --git a/apps/web/components/connected/syslumenn/CardLists/TemporaryEventLicencesList/translation.strings.ts b/apps/web/components/connected/syslumenn/CardLists/TemporaryEventLicencesList/translation.strings.ts new file mode 100644 index 000000000000..9f7cc20c27b4 --- /dev/null +++ b/apps/web/components/connected/syslumenn/CardLists/TemporaryEventLicencesList/translation.strings.ts @@ -0,0 +1,155 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + dateFormat: { + id: 'web.syslumenn.temporaryEventLicencesList:dateFormat', + defaultMessage: "d. MMMM yyyy 'kl.' HH:mm", + description: 'Hvernig dagsetningin birtist', + }, + csvHeaderLicenceType: { + id: 'web.syslumenn.temporaryEventLicencesList:csvHeaderLicenceType', + defaultMessage: 'Tegund', + description: 'Tegund (texti fyrir dálk í CSV skrá)', + }, + csvHeaderLicenceSubType: { + id: 'web.syslumenn.temporaryEventLicencesList:csvHeaderLicenceSubType', + defaultMessage: "'Tegund leyfis", + description: 'Tegund leyfis (texti fyrir dálk í CSV skrá)', + }, + csvHeaderLicenseNumber: { + id: 'web.syslumenn.temporaryEventLicencesList:csvHeaderLicenseNumber', + defaultMessage: 'Leyfisnúmer', + description: 'Leyfisnúmer (texti fyrir dálk í CSV skrá)', + }, + csvHeaderLicenseHolder: { + id: 'web.syslumenn.temporaryEventLicencesList:csvHeaderLicenseHolder', + defaultMessage: 'Leyfishafi', + description: 'Leyfishafi (texti fyrir dálk í CSV skrá)', + }, + csvHeaderLicenseResponsible: { + id: 'web.syslumenn.temporaryEventLicencesList:csvHeaderLicenseResponsible', + defaultMessage: 'Ábyrgðarmaður', + description: 'Ábyrgðarmaður (texti fyrir dálk í CSV skrá)', + }, + csvHeaderValidFrom: { + id: 'web.syslumenn.temporaryEventLicencesList:csvHeaderValidFrom', + defaultMessage: 'Gildir frá', + description: 'Gildir frá (texti fyrir dálk í CSV skrá)', + }, + csvHeaderValidTo: { + id: 'web.syslumenn.temporaryEventLicencesList:csvHeaderValidTo', + defaultMessage: 'Gildir til', + description: 'Gildir til (texti fyrir dálk í CSV skrá)', + }, + csvHeaderIssuedBy: { + id: 'web.syslumenn.temporaryEventLicencesList:csvHeaderIssuedBy', + defaultMessage: 'Útgefið af', + description: 'Útgefið af (texti fyrir dálk í CSV skrá)', + }, + filterOfficeAll: { + id: 'web.syslumenn.temporaryEventLicencesList:filterOfficeAll', + defaultMessage: 'Öll embætti', + description: 'Öll embætti', + }, + filterLicenceSubTypeAll: { + id: 'web.syslumenn.temporaryEventLicencesList:filterLicenceSubTypeAll', + defaultMessage: 'Allar tegundir', + description: 'Allar tegundir', + }, + errorTitle: { + id: 'web.syslumenn.temporaryEventLicencesList:errorTitle', + defaultMessage: 'Villa', + description: + 'Titill á villuskilaboðum ef ekki tókst að sækja tækifærisleyfi', + }, + errorMessage: { + id: 'web.syslumenn.temporaryEventLicencesList:errorMessage', + defaultMessage: 'Ekki tókst að sækja tækifærisleyfi.', + description: 'Villutexti sem birtist ef ekki tókst að sækja tækifærisleyfi', + }, + alcoholLicencesFilterLicenceSubType: { + id: 'web.syslumenn.temporaryEventLicencesList:alcoholLicencesFilterLicenceSubType', + defaultMessage: 'Tegund', + description: 'Label fyrir tegund filter', + }, + alcoholLicencesFilterOffice: { + id: 'web.syslumenn.temporaryEventLicencesList:alcoholLicencesFilterOffice', + defaultMessage: 'Embætti', + description: 'Label fyrir embættis filter', + }, + searchPlaceholder: { + id: 'web.syslumenn.temporaryEventLicencesList:searchPlaceholder', + defaultMessage: 'Leita', + description: 'Placeholder texti fyrir leitarbox', + }, + noResults: { + id: 'web.syslumenn.temporaryEventLicencesList:noResults', + defaultMessage: 'Engin leyfi fundust', + description: 'Texti sem birtist ef engin leyfi fundust', + }, + licenseNumber: { + id: 'web.syslumenn.temporaryEventLicencesList:licenseNumber', + defaultMessage: 'Leyfisnúmer', + description: 'Leyfisnúmer', + }, + validPeriodLabel: { + id: 'web.syslumenn.temporaryEventLicencesList:validPeriodLabel', + defaultMessage: 'Gildistími', + description: 'Gildistími', + }, + validPeriodUntil: { + id: 'web.syslumenn.temporaryEventLicencesList:validPeriodUntil', + defaultMessage: 'Til', + description: 'Til', + }, + validPeriodIndefinite: { + id: 'web.syslumenn.temporaryEventLicencesList:validPeriodIndefinite', + defaultMessage: 'Ótímabundið', + description: 'Ótímabundið', + }, + licenseResponsible: { + id: 'web.syslumenn.temporaryEventLicencesList:licenseResponsible', + defaultMessage: 'Ábyrgðarmaður', + description: 'Ábyrgðarmaður', + }, + licenseResponsibleNotRegistered: { + id: 'web.syslumenn.temporaryEventLicencesList:licenseResponsibleNotRegistered', + defaultMessage: 'Enginn skráður', + description: 'Enginn skráður', + }, + licenseEstimatedNumberOfGuests: { + id: 'web.syslumenn.temporaryEventLicencesList:licenseEstimatedNumberOfGuests', + defaultMessage: 'Áætlaður fjöldi gesta', + description: 'Áætlaður fjöldi gesta', + }, + licenseMaximumNumberOfGuests: { + id: 'web.syslumenn.temporaryEventLicencesList:licenseMaximumNumberOfGuests', + defaultMessage: 'Hámarksfjöldi gesta', + description: 'Hámarksfjöldi gesta', + }, + loadMore: { + id: 'web.syslumenn.temporaryEventLicencesList:loadMore', + defaultMessage: 'Sjá fleiri', + description: 'Sjá fleiri', + }, + csvButtonLabelDefault: { + id: 'web.syslumenn.temporaryEventLicencesList:csvButtonLabelDefault', + defaultMessage: 'Sækja öll leyfi (CSV).', + description: 'Texti fyrir CSV hnapp', + }, + csvButtonLabelLoading: { + id: 'web.syslumenn.temporaryEventLicencesList:csvButtonLabelLoading', + defaultMessage: 'Sæki öll leyfi...', + description: 'Texti þegar smellt er á CSV hnapp', + }, + csvButtonLabelError: { + id: 'web.syslumenn.temporaryEventLicencesList:csvButtonLabelError', + defaultMessage: 'Ekki tókst að sækja leyfi, reyndu aftur.', + description: 'Texti fyrir CSV hnapp ef ekki tókst að sækja skrá', + }, + csvFileTitlePrefix: { + id: 'web.syslumenn.temporaryEventLicencesList:csvFileTitlePrefix', + defaultMessage: 'Tækifærisleyfi', + description: 'Titill á CSV skrá', + }, +}) diff --git a/apps/web/components/connected/syslumenn/TableLists/BrokersList/BrokersList.tsx b/apps/web/components/connected/syslumenn/TableLists/BrokersList/BrokersList.tsx index 4782b2c0691a..e8a12bb7bc0f 100644 --- a/apps/web/components/connected/syslumenn/TableLists/BrokersList/BrokersList.tsx +++ b/apps/web/components/connected/syslumenn/TableLists/BrokersList/BrokersList.tsx @@ -1,18 +1,21 @@ import { CSSProperties, FC, useState } from 'react' +import { useIntl } from 'react-intl' import { useQuery } from '@apollo/client/react' -import { GET_BROKERS_QUERY } from './queries' + import { ConnectedComponent, Query } from '@island.is/api/schema' -import { sortAlpha } from '@island.is/shared/utils' import { + AlertMessage, Box, + Input, LoadingDots, Pagination, Table as T, Text, - Input, - AlertMessage, } from '@island.is/island-ui/core' -import { useNamespace } from '@island.is/web/hooks' +import { sortAlpha } from '@island.is/shared/utils' + +import { GET_BROKERS_QUERY } from './queries' +import { translation as t } from './translation.strings' const DEFAULT_PAGE_SIZE = 5 const DEFAULT_TABLE_MIN_HEIGHT = '800px' @@ -66,7 +69,7 @@ const getSortedAndFilteredBrokers = ( const BrokersList: FC> = ({ slice, }) => { - const n = useNamespace(slice.json ?? {}) + const { formatMessage } = useIntl() const [listState, setListState] = useState('loading') const [brokers, setBrokers] = useState([]) @@ -138,11 +141,8 @@ const BrokersList: FC> = ({ )} {listState === 'error' && ( )} @@ -150,7 +150,7 @@ const BrokersList: FC> = ({ > = ({ )} {listState === 'loaded' && filteredBrokers.length === 0 && ( - - {n('noBrokersFound', 'Engir verðbréfamiðlarar fundust.')} - + {formatMessage(t.noBrokersFound)} )} {listState === 'loaded' && filteredBrokers.length > 0 && ( @@ -174,8 +172,8 @@ const BrokersList: FC> = ({ - {n('name', 'Nafn')} - {n('nationalId', 'Kennitala')} + {formatMessage(t.name)} + {formatMessage(t.nationalId)} diff --git a/apps/web/components/connected/syslumenn/TableLists/BrokersList/translation.strings.ts b/apps/web/components/connected/syslumenn/TableLists/BrokersList/translation.strings.ts new file mode 100644 index 000000000000..f163e0e62a95 --- /dev/null +++ b/apps/web/components/connected/syslumenn/TableLists/BrokersList/translation.strings.ts @@ -0,0 +1,36 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + errorTitle: { + id: 'web.syslumenn.brokersList:errorTitle', + defaultMessage: 'Villa', + description: + 'Villa (titill á villuskilaboðum ef ekki tókst að sækja lista yfir verðbréfamiðlara)', + }, + errorMessage: { + id: 'web.syslumenn.brokersList:errorMessage', + defaultMessage: 'Ekki tókst að sækja lista yfir verðbréfamiðlara.', + description: + 'Ekki tókst að sækja lista yfir verðbréfamiðlara (villutexti ef ekki tókst að sækja lista yfir verðbréfamiðlara)', + }, + searchPlaceholder: { + id: 'web.syslumenn.brokersList:searchPlaceholder', + defaultMessage: 'Leita', + description: 'Placeholder texti fyrir leitarbox', + }, + noBrokersFound: { + id: 'web.syslumenn.brokersList:noBrokersFound', + defaultMessage: 'Engir verðbréfamiðlarar fundust.', + description: 'Texti sem birtist ef engir verðbréfamiðlarar fundust.', + }, + name: { + id: 'web.syslumenn.brokersList:name', + defaultMessage: 'Nafn', + description: 'Nafn (texti á dálk í töflu)', + }, + nationalId: { + id: 'web.syslumenn.brokersList:nationalId', + defaultMessage: 'Kennitala', + description: 'Kennitala (texti á dálk í töflu)', + }, +}) diff --git a/apps/web/components/connected/syslumenn/TableLists/JourneymanList/JourneymanList.tsx b/apps/web/components/connected/syslumenn/TableLists/JourneymanList/JourneymanList.tsx index 53a2af36f2e1..23162b629e9b 100644 --- a/apps/web/components/connected/syslumenn/TableLists/JourneymanList/JourneymanList.tsx +++ b/apps/web/components/connected/syslumenn/TableLists/JourneymanList/JourneymanList.tsx @@ -1,4 +1,5 @@ import { CSSProperties, useState } from 'react' +import { useIntl } from 'react-intl' import { useQueryState } from 'next-usequerystate' import { useQuery } from '@apollo/client/react' @@ -19,7 +20,6 @@ import { import { sortAlpha } from '@island.is/shared/utils' import { SyslumennListCsvExport } from '@island.is/web/components' import { JourneymanLicence } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' import { useDateUtils } from '@island.is/web/i18n/useDateUtils' import { @@ -28,6 +28,7 @@ import { prepareCsvString, } from '../../utils' import { GET_JOURNEYMAN_LICENCES_QUERY } from './queries' +import { translation as t } from './translation.strings' const DEFAULT_PAGE_SIZE = 20 const DEFAULT_TABLE_MIN_HEIGHT = '800px' @@ -44,7 +45,7 @@ interface JourneymanListProps { type ListState = 'loading' | 'loaded' | 'error' const JourneymanList = ({ slice }: JourneymanListProps) => { - const n = useNamespace(slice.json ?? {}) + const { formatMessage } = useIntl() const [listState, setListState] = useState('loading') const [licences, setLicences] = useState< Query['getJourneymanLicences']['licences'] @@ -114,10 +115,10 @@ const JourneymanList = ({ slice }: JourneymanListProps) => { return new Promise((resolve, reject) => { if (licences) { const headerRow = [ - n('csvHeaderName', 'Nafn') as string, - n('csvHeaderProfession', 'Iðngrein') as string, - n('csvHeaderDateOfPublication', 'Útgáfuár') as string, - n('csvHeaderNationalId', 'Kennitala') as string, + formatMessage(t.csvHeaderName), + formatMessage(t.csvHeaderProfession), + formatMessage(t.csvHeaderDateOfPublication), + formatMessage(t.csvHeaderNationaId), ] const dataRows = [] for (const licence of licences) { @@ -137,10 +138,7 @@ const JourneymanList = ({ slice }: JourneymanListProps) => { } // Filter - Profession - const allLicenceProfessionOption = n( - 'filterLicenceProfessionAll', - 'Allar tegundir', - ) as string + const allLicenceProfessionOption = formatMessage(t.filterLicenceProfessionAll) // Filter const filteredLicences = getSortedAndFilteredList( @@ -183,11 +181,8 @@ const JourneymanList = ({ slice }: JourneymanListProps) => { )} {listState === 'error' && ( )} @@ -205,7 +200,7 @@ const JourneymanList = ({ slice }: JourneymanListProps) => { icon="chevronDown" size="sm" isSearchable - label={n('licencesFilterLicenceProfession', 'Iðngrein')} + label={formatMessage(t.licencesFilterLicenceProfession)} name="licenceProfessionSelect" options={availableLicenceProfessionOptions} value={filterLicenceProfession} @@ -221,7 +216,7 @@ const JourneymanList = ({ slice }: JourneymanListProps) => { { /> @@ -255,9 +241,7 @@ const JourneymanList = ({ slice }: JourneymanListProps) => { )} {listState === 'loaded' && filteredLicences.length === 0 && ( - - {n('noLicencesFound', 'Engar niðurstöður fundust.')} - + {formatMessage(t.noLicencesFound)} )} {listState === 'loaded' && filteredLicences.length > 0 && ( @@ -266,11 +250,12 @@ const JourneymanList = ({ slice }: JourneymanListProps) => { - {n('name', 'Nafn')} - {n('profession', 'Iðngrein')} - {n('dateOfPublication', 'Útgáfuár')} + {formatMessage(t.name)} + {formatMessage(t.profession)} + + {formatMessage(t.dateOfPublication)} - {n('nationalId', 'Kennitala')} + {formatMessage(t.nationalId)} diff --git a/apps/web/components/connected/syslumenn/TableLists/JourneymanList/translation.strings.ts b/apps/web/components/connected/syslumenn/TableLists/JourneymanList/translation.strings.ts new file mode 100644 index 000000000000..d5ba5f6fd757 --- /dev/null +++ b/apps/web/components/connected/syslumenn/TableLists/JourneymanList/translation.strings.ts @@ -0,0 +1,94 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + csvHeaderName: { + id: 'web.syslumenn.journeymanList:csvHeaderName', + defaultMessage: 'Nafn', + description: 'Nafn (texti fyrir dálk í CSV skrá)', + }, + csvHeaderProfession: { + id: 'web.syslumenn.journeymanList:csvHeaderProfession', + defaultMessage: 'Iðngrein', + description: 'Iðngrein (texti fyrir dálk í CSV skrá)', + }, + csvHeaderDateOfPublication: { + id: 'web.syslumenn.journeymanList:csvHeaderDateOfPublication', + defaultMessage: 'Útgáfuár', + description: 'Útgáfuár (texti fyrir dálk í CSV skrá)', + }, + licencesFilterLicenceProfession: { + id: 'web.syslumenn.journeymanList:licencesFilterLicenceProfession', + defaultMessage: 'Iðngrein', + description: 'Label fyrir iðngrein filter', + }, + searchPlaceholder: { + id: 'web.syslumenn.journeymanList:searchPlaceholder', + defaultMessage: 'Leita', + description: 'Placeholder texti fyrir leitarbox', + }, + csvFileTitlePrefix: { + id: 'web.syslumenn.journeymanList:csvFileTitlePrefix', + defaultMessage: 'Sveinslisti', + description: 'Titill á CSV skrá', + }, + noLicencesFound: { + id: 'web.syslumenn.journeymanList:noLicencesFound', + defaultMessage: 'Engar niðurstöður fundust', + description: 'Texti sem birtist ef engar niðurstöður fundust', + }, + name: { + id: 'web.syslumenn.journeymanList:name', + defaultMessage: 'Nafn', + description: 'Nafn', + }, + profession: { + id: 'web.syslumenn.journeymanList:profession', + defaultMessage: 'Iðngrein', + description: 'Iðngrein', + }, + dateOfPublication: { + id: 'web.syslumenn.journeymanList:dateOfPublication', + defaultMessage: 'Útgáfuár', + description: 'Útgáfuár', + }, + filterLicenceProfessionAll: { + id: 'web.syslumenn.journeymanList:filterLicenceProfessionAll', + defaultMessage: 'Allar tegundir', + description: 'Allar tegundir', + }, + errorTitle: { + id: 'web.syslumenn.journeymanList:errorTitle', + defaultMessage: 'Villa', + description: 'Villa', + }, + errorMessage: { + id: 'web.syslumenn.journeymanList:errorMessage', + defaultMessage: 'Ekki tókst að sækja lista yfir sveinsbréfin.', + description: 'Ekki tókst að sækja lista yfir sveinsbréfin.', + }, + csvButtonLabelDefault: { + id: 'web.syslumenn.journeymanList:csvButtonLabelDefault', + defaultMessage: 'Sækja öll leyfi (CSV)', + description: 'Titill á CSV takka', + }, + csvButtonLabelLoading: { + id: 'web.syslumenn.journeymanList:csvButtonLabelLoading', + defaultMessage: 'Sæki öll leyfi...', + description: 'Titill á CSV takka eftir smell', + }, + csvButtonLabelError: { + id: 'web.syslumenn.journeymanList:csvButtonLabelError', + defaultMessage: 'Ekki tókst að sækja leyfi, reyndu aftur', + description: 'Texti þegar ekki tekst að sækja CSV skrá', + }, + csvHeaderNationaId: { + id: 'web.syslumenn.journeymanList:csvHeaderNationaId', + defaultMessage: 'Kennitala', + description: 'Kennitala (texti fyrir dálk í CSV skrá)', + }, + nationalId: { + id: 'web.syslumenn.journeymanList:nationalId', + defaultMessage: 'Kennitala', + description: 'Kennitala', + }, +}) diff --git a/apps/web/components/connected/syslumenn/TableLists/MasterList/MasterList.tsx b/apps/web/components/connected/syslumenn/TableLists/MasterList/MasterList.tsx index 5f576dd0ba04..6977134bfa54 100644 --- a/apps/web/components/connected/syslumenn/TableLists/MasterList/MasterList.tsx +++ b/apps/web/components/connected/syslumenn/TableLists/MasterList/MasterList.tsx @@ -1,4 +1,5 @@ import { CSSProperties, useState } from 'react' +import { useIntl } from 'react-intl' import { useQueryState } from 'next-usequerystate' import { useQuery } from '@apollo/client/react' @@ -19,7 +20,6 @@ import { import { sortAlpha } from '@island.is/shared/utils' import { SyslumennListCsvExport } from '@island.is/web/components' import { MasterLicence } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' import { useDateUtils } from '@island.is/web/i18n/useDateUtils' import { @@ -28,6 +28,7 @@ import { prepareCsvString, } from '../../utils' import { GET_MASTER_LICENCES_QUERY } from './queries' +import { translation as t } from './translation.strings' const DEFAULT_PAGE_SIZE = 20 const DEFAULT_TABLE_MIN_HEIGHT = '800px' @@ -44,7 +45,7 @@ interface MasterListProps { type ListState = 'loading' | 'loaded' | 'error' const MasterList = ({ slice }: MasterListProps) => { - const n = useNamespace(slice.json ?? {}) + const { formatMessage } = useIntl() const [listState, setListState] = useState('loading') const [licences, setLicences] = useState< Query['getMasterLicences']['licences'] @@ -116,10 +117,10 @@ const MasterList = ({ slice }: MasterListProps) => { return new Promise((resolve, reject) => { if (licences) { const headerRow = [ - n('csvHeaderMasterLicenceName', 'Nafn') as string, - n('csvHeaderMasterLicenseProfession', 'Iðngrein') as string, - n('csvHeaderMasterLicenceDateOfPublication', 'Útgáfuár') as string, - n('csvHeaderMasterLicenceNationalId', 'Kennitala') as string, + formatMessage(t.csvHeaderMasterLicenceName), + formatMessage(t.csvHeaderMasterLicenseProfession), + formatMessage(t.csvHeaderMasterLicenceDateOfPublication), + formatMessage(t.csvHeaderMasterLicenceNationalId), ] const dataRows = [] for (const licence of licences) { @@ -139,10 +140,7 @@ const MasterList = ({ slice }: MasterListProps) => { } // Filter - Profession - const allLicenceProfessionOption = n( - 'filterLicenceProfessionAll', - 'Allar tegundir', - ) as string + const allLicenceProfessionOption = formatMessage(t.filterLicenceProfessionAll) // Filter const filteredMasterLicences = getSortedAndFilteredList( @@ -185,11 +183,8 @@ const MasterList = ({ slice }: MasterListProps) => { )} {listState === 'error' && ( )} @@ -207,9 +202,8 @@ const MasterList = ({ slice }: MasterListProps) => { icon="chevronDown" size="sm" isSearchable - label={n( - 'alcoholLicencesFilterLicenceProfession', - 'Iðngrein', + label={formatMessage( + t.alcoholLicencesFilterLicenceProfession, )} name="licenceProfessionSelect" options={availableLicenceProfessionOptions} @@ -226,7 +220,7 @@ const MasterList = ({ slice }: MasterListProps) => { { /> @@ -260,9 +245,7 @@ const MasterList = ({ slice }: MasterListProps) => { )} {listState === 'loaded' && filteredMasterLicences.length === 0 && ( - - {n('noLicencesFound', 'Engar niðurstöður fundust.')} - + {formatMessage(t.noLicencesFound)} )} {listState === 'loaded' && filteredMasterLicences.length > 0 && ( @@ -271,11 +254,11 @@ const MasterList = ({ slice }: MasterListProps) => { - {n('name', 'Nafn')} - {n('profession', 'Iðngrein')} - {n('dateOfPublication', 'Útgáfuár')} + {formatMessage(t.name)} + {formatMessage(t.profession)} + {formatMessage(t.dateOfPublication)} - {n('nationalId', 'Kennitala')} + {formatMessage(t.nationalId)} diff --git a/apps/web/components/connected/syslumenn/TableLists/MasterList/translation.strings.ts b/apps/web/components/connected/syslumenn/TableLists/MasterList/translation.strings.ts new file mode 100644 index 000000000000..8c43c96c78c6 --- /dev/null +++ b/apps/web/components/connected/syslumenn/TableLists/MasterList/translation.strings.ts @@ -0,0 +1,95 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + csvHeaderMasterLicenceName: { + id: 'web.syslumenn.masterList:csvHeaderMasterLicenceName', + defaultMessage: 'Nafn', + description: 'Nafn (texti fyrir dálk í CSV skrá)', + }, + csvHeaderMasterLicenseProfession: { + id: 'web.syslumenn.masterList:csvHeaderMasterLicenseProfession', + defaultMessage: 'Iðngrein', + description: 'Iðngrein (texti fyrir dálk í CSV skrá)', + }, + csvHeaderMasterLicenceDateOfPublication: { + id: 'web.syslumenn.masterList:csvHeaderMasterLicenceDateOfPublication', + defaultMessage: 'Útgáfuár', + description: 'Útgáfuár (texti fyrir dálk í CSV skrá)', + }, + csvHeaderMasterLicenceNationalId: { + id: 'web.syslumenn.masterList:csvHeaderMasterLicenceNationalId', + defaultMessage: 'Kennitala', + description: 'Kennitala (texti fyrir dálk í CSV skrá)', + }, + filterLicenceProfessionAll: { + id: 'web.syslumenn.masterList:filterLicenceProfessionAll', + defaultMessage: 'Allar tegundir', + description: 'Allar tegundir', + }, + errorTitle: { + id: 'web.syslumenn.masterList:errorTitle', + defaultMessage: 'Villa', + description: 'Villa', + }, + errorMessage: { + id: 'web.syslumenn.masterList:errorMessage', + defaultMessage: 'Ekki tókst að sækja lista yfir meistarabréfin.', + description: 'Ekki tókst að sækja lista yfir meistarabréfin.', + }, + searchPlaceholder: { + id: 'web.syslumenn.masterList:searchPlaceholder', + defaultMessage: 'Leita', + description: 'Placeholder texti fyrir leitarbox', + }, + csvButtonLabelDefault: { + id: 'web.syslumenn.masterList:csvButtonLabelDefault', + defaultMessage: 'Sækja öll leyfi (CSV)', + description: '(Texti fyrir CSV hnapp) Sækja öll leyfi (CSV)', + }, + csvButtonLabelLoading: { + id: 'web.syslumenn.masterList:csvButtonLabelLoading', + defaultMessage: 'Sæki öll leyfi...', + description: '(Loading texti fyrir CSV hnapp) Sæki öll leyfi...', + }, + csvButtonLabelError: { + id: 'web.syslumenn.masterList:csvButtonLabelError', + defaultMessage: 'Ekki tókst að sækja leyfi, reyndu aftur', + description: + '(Villu texti fyrir CSV hnapp) Ekki tókst að sækja leyfi, reyndu aftur', + }, + csvFileTitlePrefix: { + id: 'web.syslumenn.masterList:csvFileTitlePrefix', + defaultMessage: 'Meistarabréf', + description: 'Titill á CSV skrá', + }, + noLicencesFound: { + id: 'web.syslumenn.masterList:noLicencesFound', + defaultMessage: 'Engar niðurstöður fundust.', + description: 'Texti sem birtist ef engar niðurstöður fundust.', + }, + name: { + id: 'web.syslumenn.masterList:name', + defaultMessage: 'Nafn', + description: 'Nafn (titill á dálk í töflu)', + }, + profession: { + id: 'web.syslumenn.masterList:profession', + defaultMessage: 'Iðngrein', + description: 'Iðngrein (titill á dálk í töflu)', + }, + dateOfPublication: { + id: 'web.syslumenn.masterList:dateOfPublication', + defaultMessage: 'Útgáfuár', + description: 'Útgáfuár (titill á dálk í töflu)', + }, + nationalId: { + id: 'web.syslumenn.masterList:nationalId', + defaultMessage: 'Kennitala', + description: 'Kennitala (titill á dálk í töflu)', + }, + alcoholLicencesFilterLicenceProfession: { + id: 'web.syslumenn.masterList:alcoholLicencesFilterLicenceProfession', + defaultMessage: 'Iðngrein', + description: 'Iðngrein (label á iðngrein filter)', + }, +}) diff --git a/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/ProfessionRights.tsx b/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/ProfessionRights.tsx index 37746d7cd934..867fefd2eded 100644 --- a/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/ProfessionRights.tsx +++ b/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/ProfessionRights.tsx @@ -1,4 +1,5 @@ import { CSSProperties, useState } from 'react' +import { useIntl } from 'react-intl' import { useQueryState } from 'next-usequerystate' import { useQuery } from '@apollo/client/react' @@ -17,12 +18,7 @@ import { } from '@island.is/island-ui/core' import { sortAlpha } from '@island.is/shared/utils' import { SyslumennListCsvExport } from '@island.is/web/components' -import type { - ConnectedComponent, - ProfessionRight, - Query, -} from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' +import type { ConnectedComponent, Query } from '@island.is/web/graphql/schema' import { getNormalizedSearchTerms, @@ -30,10 +26,11 @@ import { prepareCsvString, } from '../../utils' import { GET_PROFESSION_RIGHTS_QUERY } from './queries' +import { translation as t } from './translation.strings' const DEFAULT_PAGE_SIZE = 20 const DEFAULT_TABLE_MIN_HEIGHT = '800px' -const SEARCH_KEYS: (keyof ProfessionRight)[] = ['name'] +const SEARCH_KEYS = ['name', 'nationalId'] interface ProfessionRightsProps { slice: ConnectedComponent @@ -42,7 +39,7 @@ interface ProfessionRightsProps { type ListState = 'loading' | 'loaded' | 'error' const ProfessionRights = ({ slice }: ProfessionRightsProps) => { - const n = useNamespace(slice.json ?? {}) + const { formatMessage } = useIntl() const [listState, setListState] = useState('loading') const [list, setList] = useState([]) const [currentPageNumber, setCurrentPageNumber] = useState(1) @@ -108,14 +105,17 @@ const ProfessionRights = ({ slice }: ProfessionRightsProps) => { return new Promise((resolve, reject) => { if (list) { const headerRow = [ - n('csvHeaderName', 'Nafn') as string, - n('csvHeaderProfession', 'Starf') as string, + formatMessage(t.csvHeaderName), + formatMessage(t.csvHeaderProfession), + formatMessage(t.csvHeaderNationalId), ] const dataRows = [] for (const item of list) { dataRows.push([ item.name ?? '', // Nafn item.profession ?? '', // Starf + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (item as any).nationalId ?? '', // Kennitala ]) } return resolve(prepareCsvString(headerRow, dataRows)) @@ -125,10 +125,7 @@ const ProfessionRights = ({ slice }: ProfessionRightsProps) => { } // Filter - Profession - const allProfessionOption = n( - 'filterProfessionAll', - 'Allar tegundir', - ) as string + const allProfessionOption = formatMessage(t.filterProfessionAll) // Filter const filteredList = getSortedAndFilteredList( @@ -138,7 +135,8 @@ const ProfessionRights = ({ slice }: ProfessionRightsProps) => { : item.profession === filterProfession?.value, ), searchTerms, - SEARCH_KEYS, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + SEARCH_KEYS as any, ) const pageSize = slice?.configJson?.pageSize ?? DEFAULT_PAGE_SIZE @@ -171,11 +169,8 @@ const ProfessionRights = ({ slice }: ProfessionRightsProps) => { )} {listState === 'error' && ( )} @@ -193,7 +188,7 @@ const ProfessionRights = ({ slice }: ProfessionRightsProps) => { icon="chevronDown" size="sm" isSearchable - label={n('filterProfession', 'Starf')} + label={formatMessage(t.filterProfession)} name="professionSelect" options={availableProfessionOptions} value={filterProfession} @@ -209,7 +204,7 @@ const ProfessionRights = ({ slice }: ProfessionRightsProps) => { { /> @@ -246,9 +229,7 @@ const ProfessionRights = ({ slice }: ProfessionRightsProps) => { )} {listState === 'loaded' && filteredList.length === 0 && ( - - {n('noResultsFound', 'Engar niðurstöður fundust.')} - + {formatMessage(t.noResultsFound)} )} {listState === 'loaded' && filteredList.length > 0 && ( @@ -257,8 +238,11 @@ const ProfessionRights = ({ slice }: ProfessionRightsProps) => { - {n('name', 'Nafn')} - {n('profession', 'Starf')} + {formatMessage(t.name)} + {formatMessage(t.profession)} + + {formatMessage(t.nationalId)} + @@ -278,6 +262,14 @@ const ProfessionRights = ({ slice }: ProfessionRightsProps) => { {item.profession} + + + + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} + {(item as any).nationalId} + + + ) })} diff --git a/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/queries.ts b/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/queries.ts index 1c4cb7246c09..7658a46be709 100644 --- a/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/queries.ts +++ b/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/queries.ts @@ -6,6 +6,7 @@ export const GET_PROFESSION_RIGHTS_QUERY = gql` list { name profession + nationalId } } } diff --git a/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/translation.strings.ts b/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/translation.strings.ts new file mode 100644 index 000000000000..9637772fc9aa --- /dev/null +++ b/apps/web/components/connected/syslumenn/TableLists/ProfessionRights/translation.strings.ts @@ -0,0 +1,84 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + csvHeaderName: { + id: 'web.syslumenn.professionRights:csvHeaderName', + defaultMessage: 'Nafn', + description: 'Nafn (texti fyrir dálk í CSV skrá)', + }, + csvHeaderProfession: { + id: 'web.syslumenn.professionRights:csvHeaderProfession', + defaultMessage: 'Starf', + description: 'Starf (texti fyrir dálk í CSV skrá)', + }, + filterProfessionAll: { + id: 'web.syslumenn.professionRights:filterProfessionAll', + defaultMessage: 'Allar tegundir', + description: 'Allar tegundir', + }, + errorTitle: { + id: 'web.syslumenn.professionRights:errorTitle', + defaultMessage: 'Villa', + description: 'Villa (Titill á villuskilaboðum ef ekki tekst ap sækja gögn)', + }, + errorMessage: { + id: 'web.syslumenn.professionRights:errorMessage', + defaultMessage: 'Ekki tókst að sækja lista yfir starfsréttindi.', + description: 'Villutexti ef ekki tókst að sækja lista yfir starfsréttindi.', + }, + filterProfession: { + id: 'web.syslumenn.professionRights:filterProfession', + defaultMessage: 'Starf', + description: 'Starf', + }, + searchPlaceholder: { + id: 'web.syslumenn.professionRights:searchPlaceholder', + defaultMessage: 'Leita', + description: 'Leita', + }, + csvButtonLabelDefault: { + id: 'web.syslumenn.professionRights:csvButtonLabelDefault', + defaultMessage: 'Sækja öll starfsréttindi (CSV)', + description: 'Texti á CSV hnapp', + }, + csvButtonLabelLoading: { + id: 'web.syslumenn.professionRights:csvButtonLabelLoading', + defaultMessage: 'Sæki öll starfsréttindi...', + description: 'Texti þegar smellt er á CSV hnapp', + }, + csvButtonLabelError: { + id: 'web.syslumenn.professionRights:csvButtonLabelError', + defaultMessage: 'Ekki tókst að sækja starfsréttindi, reyndu aftur', + description: 'Texti ef ekki tókst að sækja CSV skrá', + }, + csvFileTitlePrefix: { + id: 'web.syslumenn.professionRights:csvFileTitlePrefix', + defaultMessage: 'Starfsréttindi', + description: 'Starfsréttindi', + }, + noResultsFound: { + id: 'web.syslumenn.professionRights:noResultsFound', + defaultMessage: 'Engar niðurstöður fundust.', + description: 'Engar niðurstöður fundust.', + }, + name: { + id: 'web.syslumenn.professionRights:name', + defaultMessage: 'Nafn', + description: 'Nafn', + }, + profession: { + id: 'web.syslumenn.professionRights:profession', + defaultMessage: 'Starf', + description: 'Starf', + }, + csvHeaderNationalId: { + id: 'web.syslumenn.professionRights:csvHeaderNationalId', + defaultMessage: 'Kennitala', + description: 'Kennitala (texti fyrir dálk í CSV skrá)', + }, + nationalId: { + id: 'web.syslumenn.professionRights:nationalId', + defaultMessage: 'Kennitala', + description: 'Kennitala', + }, +}) diff --git a/apps/web/components/connected/vehicles/AircraftSearch.tsx b/apps/web/components/connected/vehicles/AircraftSearch/AircraftSearch.tsx similarity index 83% rename from apps/web/components/connected/vehicles/AircraftSearch.tsx rename to apps/web/components/connected/vehicles/AircraftSearch/AircraftSearch.tsx index 29c60fd1d18f..4119a8a05ab8 100644 --- a/apps/web/components/connected/vehicles/AircraftSearch.tsx +++ b/apps/web/components/connected/vehicles/AircraftSearch/AircraftSearch.tsx @@ -1,13 +1,8 @@ -import { useState, useEffect, CSSProperties } from 'react' +import { CSSProperties, useEffect, useState } from 'react' +import { useIntl } from 'react-intl' import { useRouter } from 'next/router' -import { - ConnectedComponent, - AircraftRegistryAircraft, - GetAllAircraftsQuery, - GetAllAircraftsQueryVariables, - AircraftRegistryPerson, -} from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' +import { useLazyQuery } from '@apollo/client' + import { AlertMessage, AsyncSearchInput, @@ -17,9 +12,17 @@ import { Table as T, Text, } from '@island.is/island-ui/core' -import { useLazyQuery } from '@apollo/client' +import { + AircraftRegistryAircraft, + AircraftRegistryPerson, + ConnectedComponent, + GetAllAircraftsQuery, + GetAllAircraftsQueryVariables, +} from '@island.is/web/graphql/schema' import { GET_ALL_AIRCRAFTS_QUERY } from '@island.is/web/screens/queries/AircraftSearch' +import { translation as translationStrings } from './translation.strings' + const DEFAULT_PAGE_SIZE = 10 const getDisplayedOwner = (aircraft: AircraftRegistryAircraft) => { @@ -49,8 +52,8 @@ const AircraftSearch = ({ slice }: AircraftSearchProps) => { const router = useRouter() const [searchInputHasFocus, setSearchInputHasFocus] = useState(false) const [searchTerm, setSearchTerm] = useState('') - const namespace = slice?.json ?? {} - const n = useNamespace(namespace) + + const { formatMessage } = useIntl() const handleSearch = (page = 1, searchValue?: string) => { let searchString = searchTerm @@ -148,7 +151,7 @@ const AircraftSearch = ({ slice }: AircraftSearchProps) => { const totalAircrafts = latestAircraftListResponse?.totalCount ?? 0 const displayedAircraftList = latestAircraftListResponse?.aircrafts ?? [] - const resetSearchText = n('resetSearch', 'Núllstilla leit') + const resetSearchText = formatMessage(translationStrings.resetSearch) const shouldDisplayResetButton = !!router?.query?.aq const minHeightFromConfig = slice?.configJson?.minHeight @@ -178,7 +181,7 @@ const AircraftSearch = ({ slice }: AircraftSearchProps) => { onBlur: () => setSearchInputHasFocus(false), name: 'public-vehicle-search', inputSize: 'medium', - placeholder: n('inputPlaceholder', 'Númer eða eigandi'), + placeholder: formatMessage(translationStrings.inputPlaceholder), colored: true, onChange: (ev) => setSearchTerm(ev.target.value), value: searchTerm, @@ -209,8 +212,8 @@ const AircraftSearch = ({ slice }: AircraftSearchProps) => { {!loading && errorOccurred && ( )} @@ -218,17 +221,14 @@ const AircraftSearch = ({ slice }: AircraftSearchProps) => { !loading && displayedAircraftList.length === 1 && selectedPage === 1 && ( - + )} {called && !loading && !errorOccurred && displayedAircraftList.length === 0 && ( - {n('noResultFound', 'Ekkert loftfar fannst')} + {formatMessage(translationStrings.noResultFound)} )} {!errorOccurred && @@ -236,7 +236,6 @@ const AircraftSearch = ({ slice }: AircraftSearchProps) => { handleSearch(1, identifier)} /> @@ -280,17 +279,15 @@ const AircraftPerson = ({ person }: AircraftPersonProps) => { } interface AircraftDetailsProps { - namespace: Record aircraft: AircraftRegistryAircraft } -const AircraftDetails = ({ namespace, aircraft }: AircraftDetailsProps) => { - const n = useNamespace(namespace) - +const AircraftDetails = ({ aircraft }: AircraftDetailsProps) => { + const { formatMessage } = useIntl() const displayedOwner = getDisplayedOwner(aircraft) const displayedOwnerName = getDisplayedOwnerName( aircraft, - n('andMore', 'ofl.'), + formatMessage(translationStrings.andMore), ) return ( @@ -301,7 +298,7 @@ const AircraftDetails = ({ namespace, aircraft }: AircraftDetailsProps) => { - {n('identifier', 'Einkennisstafir')}: + {formatMessage(translationStrings.identifier)}: @@ -311,7 +308,7 @@ const AircraftDetails = ({ namespace, aircraft }: AircraftDetailsProps) => { - {n('registrationNumber', 'Skráningarnúmer')}: + {formatMessage(translationStrings.registrationNumber)}: @@ -320,7 +317,9 @@ const AircraftDetails = ({ namespace, aircraft }: AircraftDetailsProps) => { - {n('type', 'Tegund')}: + + {formatMessage(translationStrings.type)}: + {aircraft.type} @@ -329,7 +328,7 @@ const AircraftDetails = ({ namespace, aircraft }: AircraftDetailsProps) => { - {n('productionYear', 'Framleiðsluár')}: + {formatMessage(translationStrings.productionYear)}: @@ -339,7 +338,7 @@ const AircraftDetails = ({ namespace, aircraft }: AircraftDetailsProps) => { - {n('serialNumber', 'Raðnúmer')}: + {formatMessage(translationStrings.serialNumber)}: @@ -349,7 +348,7 @@ const AircraftDetails = ({ namespace, aircraft }: AircraftDetailsProps) => { - {n('maxWeight', 'Hámarksþungi')}: + {formatMessage(translationStrings.maxWeight)}: @@ -358,7 +357,9 @@ const AircraftDetails = ({ namespace, aircraft }: AircraftDetailsProps) => { - {n('owner', 'Eigandi')}: + + {formatMessage(translationStrings.owner)}: + { - {n('operator', 'Umráðandi')}: + + {formatMessage(translationStrings.operator)}: + { } interface AircraftTableProps { - namespace: Record aircrafts: AircraftRegistryAircraft[] onAircraftClick: (identifier: string) => void } -const AircraftTable = ({ - aircrafts, - namespace, - onAircraftClick, -}: AircraftTableProps) => { - const n = useNamespace(namespace) - +const AircraftTable = ({ aircrafts, onAircraftClick }: AircraftTableProps) => { + const { formatMessage } = useIntl() return ( - {n('identifier', 'Einkennisstafir')} + {formatMessage(translationStrings.identifier)} - {n('registrationNumber', 'Skráningarnúmer')} + {formatMessage(translationStrings.registrationNumber)} - {n('serialNumber', 'Raðnúmer')} + + {formatMessage(translationStrings.serialNumber)} + - {n('type', 'Tegund')} + + {formatMessage(translationStrings.type)} + - {n('owner', 'Eigandi')} + + {formatMessage(translationStrings.owner)} + - {n('operator', 'Umráðandi')} + + {formatMessage(translationStrings.operator)} + @@ -429,7 +434,7 @@ const AircraftTable = ({ {aircrafts.map((aircraft) => { const displayedOwnerName = getDisplayedOwnerName( aircraft, - n('andMore', 'ofl.'), + formatMessage(translationStrings.andMore), ) return ( diff --git a/apps/web/components/connected/vehicles/AircraftSearch/translation.strings.ts b/apps/web/components/connected/vehicles/AircraftSearch/translation.strings.ts new file mode 100644 index 000000000000..0701863e8240 --- /dev/null +++ b/apps/web/components/connected/vehicles/AircraftSearch/translation.strings.ts @@ -0,0 +1,74 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + resetSearch: { + id: 'web.aircraftSearch:resetSearch', + defaultMessage: 'Núllstilla leit', + description: 'Núllstilla leit', + }, + inputPlaceholder: { + id: 'web.aircraftSearch:inputPlaceholder', + defaultMessage: 'Númer eða eigandi', + description: 'Placeholder texti á leit', + }, + errorOccurredTitle: { + id: 'web.aircraftSearch:errorOccurredTitle', + defaultMessage: 'Villa kom upp', + description: 'Titill á villuskilaboðum', + }, + errorOccurredMessage: { + id: 'web.aircraftSearch:errorOccurredMessage', + defaultMessage: 'Ekki tókst að sækja loftför', + description: 'Skilaboð þegar villa kemur upp við að sækja gögn', + }, + noResultFound: { + id: 'web.aircraftSearch:noResultFound', + defaultMessage: 'Ekkert loftfar fannst', + description: 'Texti þegar engar niðurstöður finnast', + }, + andMore: { + id: 'web.aircraftSearch:andMore', + defaultMessage: 'ofl.', + description: 'ofl.', + }, + identifier: { + id: 'web.aircraftSearch:identifier', + defaultMessage: 'Einkennisstafir', + description: 'Einkennisstafir', + }, + registrationNumber: { + id: 'web.aircraftSearch:registrationNumber', + defaultMessage: 'Skráningarnúmer', + description: 'Skráningarnúmer', + }, + type: { + id: 'web.aircraftSearch:type', + defaultMessage: 'Tegund', + description: 'Tegund', + }, + productionYear: { + id: 'web.aircraftSearch:productionYear', + defaultMessage: 'Framleiðsluár', + description: 'Framleiðsluár', + }, + serialNumber: { + id: 'web.aircraftSearch:serialNumber', + defaultMessage: 'Raðnúmer', + description: 'Raðnúmer', + }, + maxWeight: { + id: 'web.aircraftSearch:maxWeight', + defaultMessage: 'Hámarksþungi', + description: 'Hámarksþungi', + }, + owner: { + id: 'web.aircraftSearch:owner', + defaultMessage: 'Eigandi', + description: 'Eigandi', + }, + operator: { + id: 'web.aircraftSearch:operator', + defaultMessage: 'Umráðandi', + description: 'Umráðandi', + }, +}) diff --git a/apps/web/components/connected/vehicles/KilometerFee.tsx b/apps/web/components/connected/vehicles/KilometerFee/KilometerFee.tsx similarity index 75% rename from apps/web/components/connected/vehicles/KilometerFee.tsx rename to apps/web/components/connected/vehicles/KilometerFee/KilometerFee.tsx index 41f7fd7c03fb..273d987b01ec 100644 --- a/apps/web/components/connected/vehicles/KilometerFee.tsx +++ b/apps/web/components/connected/vehicles/KilometerFee/KilometerFee.tsx @@ -1,4 +1,5 @@ import { useMemo, useState } from 'react' +import { useIntl } from 'react-intl' import { Box, @@ -11,10 +12,11 @@ import { Text, } from '@island.is/island-ui/core' import { ConnectedComponent } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' import { useI18n } from '@island.is/web/i18n' import { formatCurrency } from '@island.is/web/utils/currency' +import { translation as translationStrings } from './translation.strings' + const MAX_KILOMETER_INPUT_LENGTH = 10 const calculate = (inputState: InputState, slice: ConnectedComponent) => { @@ -56,7 +58,7 @@ const initialInputState: InputState = { } const KilometerFee = ({ slice }: KilometerFeeProps) => { - const n = useNamespace(slice?.json ?? {}) + const { formatMessage } = useIntl() const { activeLocale } = useI18n() const [result, setResult] = useState(0) const [inputState, setInputState] = useState(initialInputState) @@ -64,24 +66,15 @@ const KilometerFee = ({ slice }: KilometerFeeProps) => { const timelineOptions = useMemo(() => { return [ { - label: n( - 'perYear', - activeLocale === 'is' ? 'á ári' : 'per year', - ) as string, + label: formatMessage(translationStrings.perYear), value: 'perYear', }, { - label: n( - 'perMonth', - activeLocale === 'is' ? 'á mánuði' : 'per month', - ) as string, + label: formatMessage(translationStrings.perMonth), value: 'perMonth', }, { - label: n( - 'perDay', - activeLocale === 'is' ? 'á dag' : 'per day', - ) as string, + label: formatMessage(translationStrings.perDay), value: 'perDay', }, ] @@ -113,20 +106,12 @@ const KilometerFee = ({ slice }: KilometerFeeProps) => { - {n( - 'energySource', - activeLocale === 'is' - ? 'Orkugjafi ökutækis' - : 'Energy source of vehicle', - )} + {formatMessage(translationStrings.energySource)} { updateInputState('energySource', 'electric') @@ -134,10 +119,7 @@ const KilometerFee = ({ slice }: KilometerFeeProps) => { checked={inputState.energySource === 'electric'} /> { updateInputState('energySource', 'hydrogen') @@ -145,10 +127,7 @@ const KilometerFee = ({ slice }: KilometerFeeProps) => { checked={inputState.energySource === 'hydrogen'} /> { updateInputState('energySource', 'hybrid') @@ -160,12 +139,7 @@ const KilometerFee = ({ slice }: KilometerFeeProps) => { - {n( - 'kilometerInputLabel', - activeLocale === 'is' - ? 'Áætlaður akstur í kílómetrum' - : 'Estimated driving in kilometers', - )} + {formatMessage(translationStrings.kilometerInputLabel)} @@ -176,7 +150,9 @@ const KilometerFee = ({ slice }: KilometerFeeProps) => { inputMode="numeric" size="xs" value={inputState.kilometers} - placeholder={n('kilometerInputPlaceholder', 'km')} + placeholder={formatMessage( + translationStrings.kilometerInputPlaceholder, + )} onChange={(ev) => { if ( ev.target.value.length > maxKilometerInputLength || @@ -212,25 +188,17 @@ const KilometerFee = ({ slice }: KilometerFeeProps) => { {result > 0 && ( - {n( - 'resultPrefix', - activeLocale === 'is' - ? 'Áætlað kílómetragjald' - : 'Estimated kilometer fee', - )} + {formatMessage(translationStrings.resultPrefix)} {formatCurrency(result, '')}{' '} - {n( - 'resultPostfix', - activeLocale === 'is' ? 'krónur á mánuði' : 'isk per month', - )} + {formatMessage(translationStrings.resultPostfix)} )} diff --git a/apps/web/components/connected/vehicles/KilometerFee/translation.strings.ts b/apps/web/components/connected/vehicles/KilometerFee/translation.strings.ts new file mode 100644 index 000000000000..d41cf452a01e --- /dev/null +++ b/apps/web/components/connected/vehicles/KilometerFee/translation.strings.ts @@ -0,0 +1,64 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + perYear: { + id: 'web.kilometerFee:perYear', + defaultMessage: 'á ári', + description: 'á ári', + }, + perMonth: { + id: 'web.kilometerFee:perMonth', + defaultMessage: 'á mánuði', + description: 'á mánuði', + }, + perDay: { + id: 'web.kilometerFee:perDay', + defaultMessage: 'á dag', + description: 'á dag', + }, + energySource: { + id: 'web.kilometerFee:energySource', + defaultMessage: 'Orkugjafi ökutækis', + description: 'Label fyrir orkugjafa ökutækis valkost', + }, + electric: { + id: 'web.kilometerFee:electric', + defaultMessage: 'Rafmagn', + description: 'Rafmagn', + }, + hydrogen: { + id: 'web.kilometerFee:hydrogen', + defaultMessage: 'Vetni', + description: 'Vetni', + }, + hybrid: { + id: 'web.kilometerFee:hybrid', + defaultMessage: 'Tengiltvinn', + description: 'Tengiltvinn', + }, + kilometerInputLabel: { + id: 'web.kilometerFee:kilometerInputLabel', + defaultMessage: 'Áætlaður akstur í kílómetrum', + description: 'Label fyrir áætlaðan akstur í kílómetrum', + }, + kilometerInputPlaceholder: { + id: 'web.kilometerFee:kilometerInputPlaceholder', + defaultMessage: 'km', + description: 'Placeholder fyrir kílómetra input reit', + }, + calculate: { + id: 'web.kilometerFee:calculate', + defaultMessage: 'Reikna', + description: 'Texti sem birtist á reikna takka', + }, + resultPrefix: { + id: 'web.kilometerFee:resultPrefix', + defaultMessage: 'Áætlað kílómetragjald', + description: 'Áætlað kílómetragjald', + }, + resultPostfix: { + id: 'web.kilometerFee:resultPostfix', + defaultMessage: 'krónur á mánuði', + description: 'krónur á mánuði', + }, +}) diff --git a/apps/web/components/connected/vehicles/PlateAvailableSearch.css.ts b/apps/web/components/connected/vehicles/PlateAvailableSearch/PlateAvailableSearch.css.ts similarity index 100% rename from apps/web/components/connected/vehicles/PlateAvailableSearch.css.ts rename to apps/web/components/connected/vehicles/PlateAvailableSearch/PlateAvailableSearch.css.ts diff --git a/apps/web/components/connected/vehicles/PlateAvailableSearch.tsx b/apps/web/components/connected/vehicles/PlateAvailableSearch/PlateAvailableSearch.tsx similarity index 71% rename from apps/web/components/connected/vehicles/PlateAvailableSearch.tsx rename to apps/web/components/connected/vehicles/PlateAvailableSearch/PlateAvailableSearch.tsx index fb272f4f4a42..8267fb6fc8b9 100644 --- a/apps/web/components/connected/vehicles/PlateAvailableSearch.tsx +++ b/apps/web/components/connected/vehicles/PlateAvailableSearch/PlateAvailableSearch.tsx @@ -1,19 +1,17 @@ -import { useLazyQuery } from '@apollo/client' import { useState } from 'react' +import { useIntl } from 'react-intl' +import { useLazyQuery } from '@apollo/client' + import { AlertMessage, AsyncSearchInput, Box, Text, } from '@island.is/island-ui/core' -import { - ConnectedComponent, - Query, - QueryPlateAvailableArgs, -} from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' +import { Query, QueryPlateAvailableArgs } from '@island.is/web/graphql/schema' import { PLATE_AVAILABLE_SEARCH_QUERY } from '@island.is/web/screens/queries/PublicVehicleSearch' +import { translation as translationStrings } from './translation.strings' import * as styles from './PlateAvailableSearch.css' const PLATE_NUMBER_REPLACEMENT_KEY = '{{USER_INPUT}}' @@ -45,12 +43,8 @@ const TextWithReplacedBoldValue = ({ ) } -interface PlateAvailableSearchProps { - slice: ConnectedComponent -} - -const PlateAvailableSearch = ({ slice }: PlateAvailableSearchProps) => { - const n = useNamespace(slice?.json ?? {}) +const PlateAvailableSearch = () => { + const { formatMessage } = useIntl() const [hasFocus, setHasFocus] = useState(false) const [searchValue, setSearchValue] = useState('') const [shouldDisplayValidationError, setShouldDisplayValidationError] = @@ -72,10 +66,7 @@ const PlateAvailableSearch = ({ slice }: PlateAvailableSearchProps) => { }) } - const aboveText = n( - 'aboveText', - 'Hér má athuga hvort tiltekið einkanúmer sé laust', - ) as string + const aboveText = formatMessage(translationStrings.aboveText) return ( @@ -94,7 +85,7 @@ const PlateAvailableSearch = ({ slice }: PlateAvailableSearchProps) => { inputProps={{ name: 'plate-available-search', inputSize: 'large', - placeholder: n('inputPlaceholder', 'Leita að einkanúmeri'), + placeholder: formatMessage(translationStrings.inputPlaceholder), colored: true, onChange: (ev) => setSearchValue(ev.target.value.toUpperCase()), value: searchValue, @@ -112,20 +103,16 @@ const PlateAvailableSearch = ({ slice }: PlateAvailableSearchProps) => { {!loading && error && ( )} {shouldDisplayValidationError && ( - {n('attention', 'Athugið:')} - {n( - 'regnoValidationText', - 'Einkanúmer mega vera 2-6 íslenskir stafir eða tölur, og eitt bil að auki, en mega ekki líkjast venjulegum skráningarnúmerum.', - )} + + {formatMessage(translationStrings.attention)}{' '} + + {formatMessage(translationStrings.regnoValidationText)} )} {!error && @@ -134,10 +121,9 @@ const PlateAvailableSearch = ({ slice }: PlateAvailableSearchProps) => { data.plateAvailable?.regno && data.plateAvailable.available && ( @@ -148,10 +134,9 @@ const PlateAvailableSearch = ({ slice }: PlateAvailableSearchProps) => { data.plateAvailable?.regno && !data.plateAvailable.available && ( diff --git a/apps/web/components/connected/vehicles/PlateAvailableSearch/translation.strings.ts b/apps/web/components/connected/vehicles/PlateAvailableSearch/translation.strings.ts new file mode 100644 index 000000000000..8dd1c8d5f913 --- /dev/null +++ b/apps/web/components/connected/vehicles/PlateAvailableSearch/translation.strings.ts @@ -0,0 +1,47 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + aboveText: { + id: 'web.plateAvailableSearch:aboveText', + defaultMessage: 'Hér má athuga hvort tiltekið einkanúmer sé laust', + description: 'Texti sem birtist fyrir ofan', + }, + inputPlaceholder: { + id: 'web.plateAvailableSearch:inputPlaceholder', + defaultMessage: 'Leita að einkanúmeri', + description: 'Placeholder texti fyrir leit', + }, + errorOccurredTitle: { + id: 'web.plateAvailableSearch:errorOccurredTitle', + defaultMessage: 'Villa kom upp', + description: 'Titill á villuskilaboðum þegar villa kemur upp', + }, + errorOccurredMessage: { + id: 'web.plateAvailableSearch:errorOccurredMessage', + defaultMessage: 'Ekki tókst að upplýsingar um einkanúmer', + description: 'Skilboð sem birtast þegar villa kemur upp', + }, + attention: { + id: 'web.plateAvailableSearch:attention', + defaultMessage: 'Athugið:', + description: 'Athugið', + }, + regnoValidationText: { + id: 'web.plateAvailableSearch:regnoValidationText', + defaultMessage: + 'Einkanúmer mega vera 2-6 íslenskir stafir eða tölur, og eitt bil að auki, en mega ekki líkjast venjulegum skráningarnúmerum.', + description: + 'Skilaboð sem birtast þegar einkanúmer leit er ekki með rétt validation', + }, + plateAvailableText: { + id: 'web.plateAvailableSearch:plateAvailableText', + defaultMessage: 'Merkið {PLATE_NUMBER} er laust', + description: 'Texti sem birtist þegar merkið er laust', + }, + plateUnavailableText: { + id: 'web.plateAvailableSearch:plateUnavailableText', + defaultMessage: + 'Merkið {PLATE_NUMBER} er í notkun og ekki laust til úthlutunar', + description: 'Texti sem birtist þegar merkið er ekki laust til úthlutunar', + }, +}) diff --git a/apps/web/components/connected/vehicles/PublicShipSearch.tsx b/apps/web/components/connected/vehicles/PublicShipSearch/PublicShipSearch.tsx similarity index 82% rename from apps/web/components/connected/vehicles/PublicShipSearch.tsx rename to apps/web/components/connected/vehicles/PublicShipSearch/PublicShipSearch.tsx index 6ada10ffe6b4..4cc83ff207a1 100644 --- a/apps/web/components/connected/vehicles/PublicShipSearch.tsx +++ b/apps/web/components/connected/vehicles/PublicShipSearch/PublicShipSearch.tsx @@ -1,7 +1,9 @@ -import { useLazyQuery } from '@apollo/client' import { useEffect, useRef, useState } from 'react' -import { useRouter } from 'next/router' +import { useIntl } from 'react-intl' import isEqual from 'lodash/isEqual' +import { useRouter } from 'next/router' +import { useLazyQuery } from '@apollo/client' + import { AlertMessage, AsyncSearchInput, @@ -10,21 +12,17 @@ import { Table, Text, } from '@island.is/island-ui/core' -import { PUBLIC_SHIP_SEARCH_QUERY } from '@island.is/web/screens/queries/PublicShipSearch' import { - ConnectedComponent, GetPublicShipSearchQuery, GetPublicShipSearchQueryVariables, } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' +import { PUBLIC_SHIP_SEARCH_QUERY } from '@island.is/web/screens/queries/PublicShipSearch' -const numberFormatter = new Intl.NumberFormat('de-DE') +import { translation as translationStrings } from './translation.strings' -interface PublicShipSearchProps { - slice: ConnectedComponent -} +const numberFormatter = new Intl.NumberFormat('de-DE') -const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { +const PublicShipSearch = () => { const [hasFocus, setHasFocus] = useState(false) const [searchValue, setSearchValue] = useState('') const router = useRouter() @@ -34,7 +32,7 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { GetPublicShipSearchQuery['shipRegistryShipSearch']['ships'] >([]) - const n = useNamespace(slice?.json ?? {}) + const { formatMessage } = useIntl() const [search, { loading, error, called }] = useLazyQuery< GetPublicShipSearchQuery, @@ -99,7 +97,7 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { inputProps={{ name: 'public-ship-search', inputSize: 'large', - placeholder: n('inputPlaceholder', 'Númer eða nafn skips'), + placeholder: formatMessage(translationStrings.inputPlaceholder), colored: true, onChange: (ev) => setSearchValue(ev.target.value), value: searchValue, @@ -116,15 +114,15 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { {called && !loading && !error && noShipWasFound && ( - {n('noShipFound', 'Ekkert skip fannst')} + {formatMessage(translationStrings.noShipFound)} )} {called && !loading && error && ( )} @@ -135,9 +133,8 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n( - 'shipInformationTableHeaderText', - 'Niðurstaða leitar:', + {formatMessage( + translationStrings.shipInformationTableHeaderText, )} @@ -149,7 +146,7 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('shipName', 'Nafn:')} + {formatMessage(translationStrings.shipName)} @@ -161,7 +158,7 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('shipType', 'Gerð:')} + {formatMessage(translationStrings.shipType)} @@ -173,7 +170,7 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('regno', 'Skráningarnúmer:')} + {formatMessage(translationStrings.regno)} @@ -185,7 +182,7 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('region', 'Umdæmi:')} + {formatMessage(translationStrings.region)} @@ -197,7 +194,7 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('portOfRegistry', 'Heimahöfn:')} + {formatMessage(translationStrings.portOfRegistry)} @@ -209,7 +206,7 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('regStatus', 'Skráningar staða:')} + {formatMessage(translationStrings.regStatus)} @@ -221,13 +218,15 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('grossTonnage', 'Brúttótonn:')}{' '} + {formatMessage(translationStrings.grossTonnage)}{' '} {numberFormatter.format(shipInformation.grossTonnage)}{' '} - {n('grossTonnageMeasurement', 't')} + {formatMessage( + translationStrings.grossTonnageMeasurement, + )} @@ -236,13 +235,13 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('length', 'Skráð lengd:')} + {formatMessage(translationStrings.length)} {numberFormatter.format(shipInformation.length)}{' '} - {n('lengthMeasurement', 'm')} + {formatMessage(translationStrings.lengthMeasurement)} @@ -252,14 +251,14 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('constructed', 'Smíðað:')} + {formatMessage(translationStrings.constructed)} {shipInformation.manufactionYear}{' '} {shipInformation.manufacturer && - n('manufacturedBy', 'af')}{' '} + formatMessage(translationStrings.manufacturedBy)}{' '} {shipInformation.manufacturer} @@ -270,7 +269,7 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { - {n('owners', 'Eigendur:')} + {formatMessage(translationStrings.owners)} @@ -280,16 +279,15 @@ const PublicShipSearch = ({ slice }: PublicShipSearchProps) => { {owner?.name}{' '} {owner?.nationalId - ? `${n('nationalIdPrefix', 'kt.')} ${ - owner.nationalId - }` + ? `${formatMessage( + translationStrings.nationalIdPrefix, + )} ${owner.nationalId}` : ''} {typeof owner?.sharePercentage === 'number' - ? `${owner.sharePercentage}% ${n( - 'sharePercentageProperty', - 'eign', + ? `${owner.sharePercentage}% ${formatMessage( + translationStrings.sharePercentageProperty, )}` : ''} diff --git a/apps/web/components/connected/vehicles/PublicShipSearch/translation.strings.ts b/apps/web/components/connected/vehicles/PublicShipSearch/translation.strings.ts new file mode 100644 index 000000000000..3fcc16c996d7 --- /dev/null +++ b/apps/web/components/connected/vehicles/PublicShipSearch/translation.strings.ts @@ -0,0 +1,104 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + inputPlaceholder: { + id: 'web.publicShipSearch:inputPlaceholder', + defaultMessage: 'Númer eða nafn skips', + description: 'Placeholder texti fyrir leit', + }, + noShipFound: { + id: 'web.publicShipSearch:noShipFound', + defaultMessage: 'Ekkert skip fannst', + description: 'Texti þegar ekkert skip fannst við leit', + }, + errorOccurredTitle: { + id: 'web.publicShipSearch:errorOccurredTitle', + defaultMessage: 'Villa kom upp', + description: 'Titill þegar villa kemur upp', + }, + errorOccurredMessage: { + id: 'web.publicShipSearch:errorOccurredMessage', + defaultMessage: 'Ekki tókst að sækja skip', + description: 'Skilaboð sem birtast þegar villa kemur upp', + }, + shipInformationTableHeaderText: { + id: 'web.publicShipSearch:shipInformationTableHeaderText', + defaultMessage: 'Niðurstaða leitar:', + description: 'Texti í töflu haus', + }, + shipName: { + id: 'web.publicShipSearch:shipName', + defaultMessage: 'Nafn:', + description: 'Nafn', + }, + shipType: { + id: 'web.publicShipSearch:shipType', + defaultMessage: 'Gerð:', + description: 'Gerð', + }, + regno: { + id: 'web.publicShipSearch:regno', + defaultMessage: 'Skráningarnúmer:', + description: 'Skráningarnúmer', + }, + region: { + id: 'web.publicShipSearch:region', + defaultMessage: 'Umdæmi:', + description: 'Umdæmi', + }, + portOfRegistry: { + id: 'web.publicShipSearch:portOfRegistry', + defaultMessage: 'Heimahöfn:', + description: 'Heimahöfn', + }, + regStatus: { + id: 'web.publicShipSearch:regStatus', + defaultMessage: 'Skráningar staða:', + description: 'Skráningar staða', + }, + grossTonnage: { + id: 'web.publicShipSearch:grossTonnage', + defaultMessage: 'Brúttótonn:', + description: 'Brúttótonn', + }, + grossTonnageMeasurement: { + id: 'web.publicShipSearch:grossTonnageMeasurement', + defaultMessage: 't', + description: 'Tonn mælineining', + }, + length: { + id: 'web.publicShipSearch:length', + defaultMessage: 'Skráð lengd:', + description: 'Skráð lengd', + }, + lengthMeasurement: { + id: 'web.publicShipSearch:lengthMeasurement', + defaultMessage: 'm', + description: 'Lengdar mælieining', + }, + constructed: { + id: 'web.publicShipSearch:constructed', + defaultMessage: 'Smíðað:', + description: 'Smíðað', + }, + manufacturedBy: { + id: 'web.publicShipSearch:manufacturedBy', + defaultMessage: 'af', + description: 'Smiðað af', + }, + owners: { + id: 'web.publicShipSearch:owners', + defaultMessage: 'Eigendur:', + description: 'Eigendur', + }, + nationalIdPrefix: { + id: 'web.publicShipSearch:nationalIdPrefix', + defaultMessage: 'kt.', + description: 'Kennitala forskeyti', + }, + sharePercentageProperty: { + id: 'web.publicShipSearch:sharePercentageProperty', + defaultMessage: 'eign', + description: 'eign', + }, +}) diff --git a/apps/web/components/connected/vehicles/PublicVehicleSearch.tsx b/apps/web/components/connected/vehicles/PublicVehicleSearch/PublicVehicleSearch.tsx similarity index 84% rename from apps/web/components/connected/vehicles/PublicVehicleSearch.tsx rename to apps/web/components/connected/vehicles/PublicVehicleSearch/PublicVehicleSearch.tsx index 48d8005f5c2e..7c8df97f52be 100644 --- a/apps/web/components/connected/vehicles/PublicVehicleSearch.tsx +++ b/apps/web/components/connected/vehicles/PublicVehicleSearch/PublicVehicleSearch.tsx @@ -1,7 +1,9 @@ -import { useLazyQuery } from '@apollo/client' import { useEffect, useRef, useState } from 'react' +import { useIntl } from 'react-intl' import isEqual from 'lodash/isEqual' import { useRouter } from 'next/router' +import { useLazyQuery } from '@apollo/client' + import { AlertMessage, AsyncSearchInput, @@ -9,14 +11,14 @@ import { Table, Text, } from '@island.is/island-ui/core' -import { PUBLIC_VEHICLE_SEARCH_QUERY } from '@island.is/web/screens/queries/PublicVehicleSearch' import { - ConnectedComponent, GetPublicVehicleSearchQuery, GetPublicVehicleSearchQueryVariables, } from '@island.is/web/graphql/schema' -import { useNamespace } from '@island.is/web/hooks' import { useDateUtils } from '@island.is/web/i18n/useDateUtils' +import { PUBLIC_VEHICLE_SEARCH_QUERY } from '@island.is/web/screens/queries/PublicVehicleSearch' + +import { translation as translationStrings } from './translation.strings' const numberFormatter = new Intl.NumberFormat('de-DE') @@ -40,18 +42,14 @@ const formatVehicleType = (vehicleInformation?: { }` } -interface PublicVehicleSearchProps { - slice: ConnectedComponent -} - -const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { +const PublicVehicleSearch = () => { const [hasFocus, setHasFocus] = useState(false) const [searchValue, setSearchValue] = useState('') const { format } = useDateUtils() const router = useRouter() const queryParamInitialized = useRef(false) - const n = useNamespace(slice?.json ?? {}) + const { formatMessage } = useIntl() const [search, { loading, data, error, called }] = useLazyQuery< GetPublicVehicleSearchQuery, @@ -117,12 +115,6 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { return ( - - {n( - 'inputEyebrowText', - 'Skráningarnúmer, fastanúmer eða verksmiðjunúmer:', - )} - { inputProps={{ name: 'public-vehicle-search', inputSize: 'large', - placeholder: n('inputPlaceholder', 'Leita í ökutækjaskrá'), + placeholder: formatMessage(translationStrings.inputPlaceholder), colored: true, onChange: (ev) => setSearchValue(ev.target.value.toUpperCase()), value: searchValue, @@ -153,15 +145,15 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { {called && !loading && !error && vehicleWasNotFound && ( - {n('noVehicleFound', 'Ekkert ökutæki fannst')} + {formatMessage(translationStrings.noVehicleFound)} )} {called && !loading && error && ( )} {vehicleInformation && ( @@ -170,7 +162,9 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('vehicleInformationTableHeaderText', 'Niðurstaða leitar:')} + {formatMessage( + translationStrings.vehicleInformationTableHeaderText, + )} @@ -180,7 +174,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('vehicleCommercialName', 'Tegund:')} + {formatMessage(translationStrings.vehicleCommercialName)} @@ -192,7 +186,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('regno', 'Skráningarnúmer:')} + {formatMessage(translationStrings.regno)} @@ -204,7 +198,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('permno', 'Fastanúmer:')} + {formatMessage(translationStrings.permno)} @@ -216,7 +210,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('vin', 'Verksmiðjunúmer:')} + {formatMessage(translationStrings.vin)} @@ -228,7 +222,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('firstRegDate', 'Fyrst skráð:')} + {formatMessage(translationStrings.firstRegDate)} @@ -240,7 +234,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('co2NEDC', 'CO2-gildi (NEDC):')} + {formatMessage(translationStrings.co2NEDC)} @@ -252,7 +246,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('weightedCo2NEDC', 'Vegið CO2-gildi (NEDC):')} + {formatMessage(translationStrings.weightedCo2NEDC)} @@ -264,7 +258,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('Co2WLTP', 'CO2-gildi (WLTP):')} + {formatMessage(translationStrings.Co2WLTP)} @@ -276,7 +270,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('weightedCo2WLTP', 'Vegið CO2-gildi (WLTP):')} + {formatMessage(translationStrings.weightedCo2WLTP)} @@ -288,7 +282,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('mass', 'Eigin þyngd:')} + {formatMessage(translationStrings.mass)} @@ -302,7 +296,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('massLaden', 'Leyfð heildarþyngd:')} + {formatMessage(translationStrings.massLaden)} @@ -316,7 +310,7 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('vehicleStatus', 'Staða:')} + {formatMessage(translationStrings.vehicleStatus)} @@ -328,7 +322,9 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { - {n('nextVehicleMainInspection', 'Næsta skoðun:')} + {formatMessage( + translationStrings.nextVehicleMainInspection, + )} @@ -340,14 +336,6 @@ const PublicVehicleSearch = ({ slice }: PublicVehicleSearchProps) => { )} - - - {n( - 'moreInfoText', - 'Hægt er að fletta upp bílnúmerum á Mínum síðum og fá þar ítarlegri upplýsingar', - )} - - ) } diff --git a/apps/web/components/connected/vehicles/PublicVehicleSearch/translation.strings.ts b/apps/web/components/connected/vehicles/PublicVehicleSearch/translation.strings.ts new file mode 100644 index 000000000000..fea8582d2cdf --- /dev/null +++ b/apps/web/components/connected/vehicles/PublicVehicleSearch/translation.strings.ts @@ -0,0 +1,94 @@ +import { defineMessages } from 'react-intl' + +export const translation = defineMessages({ + inputPlaceholder: { + id: 'web.publicVehicleSearch:inputPlaceholder', + defaultMessage: 'Leita í ökutækjaskrá', + description: 'Placeholder texti á leit', + }, + noVehicleFound: { + id: 'web.publicVehicleSearch:noVehicleFound', + defaultMessage: 'Ekkert ökutæki fannst', + description: 'Texti sem birtist þegar leit skilar engum niðurstöðum', + }, + errorOccurredTitle: { + id: 'web.publicVehicleSearch:errorOccurredTitle', + defaultMessage: 'Villa kom upp', + description: 'Titill á skilaboðum þegar villa kemur upp', + }, + errorOccurredMessage: { + id: 'web.publicVehicleSearch:errorOccurredMessage', + defaultMessage: 'Ekki tókst að sækja ökutæki', + description: 'Skilaboð þegar villa kemur upp', + }, + vehicleInformationTableHeaderText: { + id: 'web.publicVehicleSearch:vehicleInformationTableHeaderText', + defaultMessage: 'Niðurstaða leitar:', + description: 'Texti í töflu haus á niðurstöðum', + }, + vehicleCommercialName: { + id: 'web.publicVehicleSearch:vehicleCommercialName', + defaultMessage: 'Tegund:', + description: 'Tegund', + }, + regno: { + id: 'web.publicVehicleSearch:regno', + defaultMessage: 'Skráningarnúmer:', + description: 'Skráningarnúmer', + }, + permno: { + id: 'web.publicVehicleSearch:permno', + defaultMessage: 'Fastanúmer:', + description: 'Fastanúmer', + }, + vin: { + id: 'web.publicVehicleSearch:vin', + defaultMessage: 'Verksmiðjunúmer:', + description: 'Verksmiðjunúmer', + }, + firstRegDate: { + id: 'web.publicVehicleSearch:firstRegDate', + defaultMessage: 'Fyrst skráð:', + description: 'Fyrst skráð', + }, + co2NEDC: { + id: 'web.publicVehicleSearch:co2NEDC', + defaultMessage: 'CO2-gildi (NEDC):', + description: 'CO2-gildi (NEDC)', + }, + weightedCo2NEDC: { + id: 'web.publicVehicleSearch:weightedCo2NEDC', + defaultMessage: 'Vegið CO2-gildi (NEDC):', + description: 'Vegið CO2-gildi (NEDC)', + }, + Co2WLTP: { + id: 'web.publicVehicleSearch:Co2WLTP', + defaultMessage: 'CO2-gildi (WLTP):', + description: 'CO2-gildi (WLTP)', + }, + weightedCo2WLTP: { + id: 'web.publicVehicleSearch:weightedCo2WLTP', + defaultMessage: 'Vegið CO2-gildi (WLTP):', + description: 'Vegið CO2-gildi (WLTP)', + }, + mass: { + id: 'web.publicVehicleSearch:mass', + defaultMessage: 'Eigin þyngd:', + description: 'Eigin þyngd', + }, + massLaden: { + id: 'web.publicVehicleSearch:massLaden', + defaultMessage: 'Leyfð heildarþyngd:', + description: 'Leyfð heildarþyngd', + }, + vehicleStatus: { + id: 'web.publicVehicleSearch:vehicleStatus', + defaultMessage: 'Staða:', + description: 'Staða', + }, + nextVehicleMainInspection: { + id: 'web.publicVehicleSearch:nextVehicleMainInspection', + defaultMessage: 'Næsta skoðun:', + description: 'Næsta skoðun', + }, +}) diff --git a/apps/web/components/connected/vehicles/index.ts b/apps/web/components/connected/vehicles/index.ts index a18d2541d63b..6bf01a78476d 100644 --- a/apps/web/components/connected/vehicles/index.ts +++ b/apps/web/components/connected/vehicles/index.ts @@ -1,22 +1,31 @@ import dynamic from 'next/dynamic' export const PublicVehicleSearch = dynamic( - () => import('./PublicVehicleSearch'), + () => import('./PublicVehicleSearch/PublicVehicleSearch'), { ssr: false }, ) -export const AircraftSearch = dynamic(() => import('./AircraftSearch'), { - ssr: true, -}) +export const AircraftSearch = dynamic( + () => import('./AircraftSearch/AircraftSearch'), + { + ssr: true, + }, +) export const PlateAvailableSearch = dynamic( - () => import('./PlateAvailableSearch'), + () => import('./PlateAvailableSearch/PlateAvailableSearch'), { ssr: false }, ) -export const PublicShipSearch = dynamic(() => import('./PublicShipSearch'), { - ssr: false, -}) +export const PublicShipSearch = dynamic( + () => import('./PublicShipSearch/PublicShipSearch'), + { + ssr: false, + }, +) -export const KilometerFee = dynamic(() => import('./KilometerFee'), { - ssr: true, -}) +export const KilometerFee = dynamic( + () => import('./KilometerFee/KilometerFee'), + { + ssr: true, + }, +) diff --git a/apps/web/components/real.ts b/apps/web/components/real.ts index 0f63a858efdf..53c38bb58324 100644 --- a/apps/web/components/real.ts +++ b/apps/web/components/real.ts @@ -93,3 +93,4 @@ export * from './BorderAbove/BorderAbove' export * from './TableOfContents/TableOfContents' export * from './CardWithFeaturedItems/CardWithFeaturedItems' export * from './GenericList/GenericList' +export * from './GenericList/LatestGenericListItems/LatestGenericListItems' diff --git a/apps/web/project.json b/apps/web/project.json index a1a12fdb04f5..cc84a6ffdf79 100644 --- a/apps/web/project.json +++ b/apps/web/project.json @@ -82,7 +82,7 @@ "extract-strings": { "executor": "nx:run-commands", "options": { - "command": "yarn ts-node -P libs/localization/tsconfig.lib.json libs/localization/scripts/extract 'apps/web/screens/**/*.{js,ts,tsx}'" + "command": "yarn ts-node -P libs/localization/tsconfig.lib.json libs/localization/scripts/extract 'apps/web/{screens,components}/**/!(*.d).{js,ts,tsx}'" } }, "export": { diff --git a/apps/web/screens/queries/fragments.ts b/apps/web/screens/queries/fragments.ts index c37f8f2ac8fe..c59235111a0a 100644 --- a/apps/web/screens/queries/fragments.ts +++ b/apps/web/screens/queries/fragments.ts @@ -198,6 +198,7 @@ export const slices = gql` json configJson componentType: type + translationStrings } fragment StatisticsFields on Statistics { @@ -883,6 +884,41 @@ export const slices = gql` } } + fragment LatestGenericListItemsFields on LatestGenericListItems { + title + genericList { + itemType + } + seeMorePage { + ... on OrganizationSubpage { + id + title + slug + organizationPage { + slug + } + } + } + seeMoreLinkText + itemResponse { + items { + id + date + title + cardIntro { + ...HtmlFields + } + filterTags { + id + title + slug + } + slug + assetUrl + } + } + } + fragment BaseSlices on Slice { ...TimelineFields ...StoryFields @@ -927,6 +963,7 @@ export const slices = gql` ...ChartNumberBoxFields ...FeaturedEventsFields ...GenericListFields + ...LatestGenericListItemsFields } fragment AllSlices on Slice { diff --git a/apps/web/utils/richText.tsx b/apps/web/utils/richText.tsx index 45d4e0e9432b..62f9e1bee9ca 100644 --- a/apps/web/utils/richText.tsx +++ b/apps/web/utils/richText.tsx @@ -1,3 +1,6 @@ +import { PropsWithChildren } from 'react' +import { IntlConfig, IntlProvider } from 'react-intl' + import { FaqList, type FaqListProps, @@ -72,11 +75,12 @@ import { TeamList, TwoColumnText, } from '@island.is/web/graphql/schema' +import { useI18n } from '@island.is/web/i18n' import AdministrationOfOccupationalSafetyAndHealthCourses from '../components/connected/AdministrationOfOccupationalSafetyAndHealthCourses/AdministrationOfOccupationalSafetyAndHealthCourses' import { MonthlyStatistics } from '../components/connected/electronicRegistrationStatistics' import { GrindavikResidentialPropertyPurchaseCalculator } from '../components/connected/GrindavikResidentialPropertyPurchaseCalculator' -import HousingBenefitCalculator from '../components/connected/HousingBenefitCalculator/HousingBenefitCalculator' +import HousingBenefitCalculator from '../components/connected/HousingBenefitCalculator/HousingBenefitCalculator/HousingBenefitCalculator' import JourneymanList from '../components/connected/syslumenn/TableLists/JourneymanList/JourneymanList' import ProfessionRights from '../components/connected/syslumenn/TableLists/ProfessionRights/ProfessionRights' import { UmsCostOfLivingCalculator } from '../components/connected/UmbodsmadurSkuldara' @@ -84,67 +88,116 @@ import FeaturedEvents from '../components/FeaturedEvents/FeaturedEvents' import FeaturedSupportQNAs from '../components/FeaturedSupportQNAs/FeaturedSupportQNAs' import { EmbedSlice } from '../components/Organization/Slice/EmbedSlice/EmbedSlice' +interface TranslationNamespaceProviderProps { + messages: IntlConfig['messages'] +} + +const TranslationNamespaceProvider = ({ + messages, + children, +}: PropsWithChildren) => { + const { activeLocale } = useI18n() + + return ( + + {children} + + ) +} + export const webRenderConnectedComponent = ( slice: ConnectedComponent & { componentType?: string }, ) => { - const data = slice.json ?? {} + let connectedComponent = null switch (slice.componentType) { case 'Fiskistofa/ShipSearch': - return + connectedComponent = + break case 'Fiskistofa/ShipSearchSidebarInput': - return + connectedComponent = + break case 'Fiskistofa/StraddlingStockCalculator': - return + connectedComponent = + break case 'Fiskistofa/CatchQuotaCalculator': - return + connectedComponent = + break case 'Fiskistofa/SelectedShip': - return + connectedComponent = + break case 'ElectronicRegistrations/MonthlyStatistics': - return + connectedComponent = + break case 'Fiskistofa/ShipSearchBoxedInput': - return + connectedComponent = + break case 'Áfengisleyfi/AlcoholLicences': - return + connectedComponent = + break case 'Tækifærisleyfi/TemporaryEventLicences': - return + connectedComponent = + break case 'Verðbréfamiðlarar/Brokers': - return + connectedComponent = + break case 'PublicVehicleSearch': - return + connectedComponent = + break case 'AircraftSearch': - return + connectedComponent = + break case 'DrivingInstructorList': - return + connectedComponent = + break case 'PlateAvailableSearch': - return + connectedComponent = + break case 'HousingBenefitCalculator': - return + connectedComponent = + break case 'PublicShipSearch': - return + connectedComponent = + break case 'Meistaraleyfi/MasterLicences': - return + connectedComponent = + break case 'Vinnueftirlitid/Namskeid': - return ( + connectedComponent = ( ) + break case 'KilometerFee': - return + connectedComponent = + break case 'SpecificHousingBenefitSupportCalculator': - return + connectedComponent = + break case 'GrindavikResidentialPropertyPurchaseCalculator': - return + connectedComponent = ( + + ) + break case 'Sveinslisti/JourneymanList': - return + connectedComponent = + break case 'Starfsrettindi/ProfessionRights': - return + connectedComponent = + break case 'Ums/CostOfLivingCalculator': - return - default: + connectedComponent = break + default: + connectedComponent = renderConnectedComponent(slice) } - return renderConnectedComponent(slice) + return ( + + {connectedComponent} + + ) } const defaultRenderComponent = { diff --git a/charts/identity-server/values.staging.yaml b/charts/identity-server/values.staging.yaml index 03040f931550..235d50eef758 100644 --- a/charts/identity-server/values.staging.yaml +++ b/charts/identity-server/values.staging.yaml @@ -375,7 +375,7 @@ services-auth-ids-api: LOG_LEVEL: 'info' NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1' NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token' - NATIONAL_REGISTRY_B2C_PATH: 'IS-DEV/GOV/10001/SKRA-Cloud-Protected/Midlun-v1' + NATIONAL_REGISTRY_B2C_PATH: 'IS-TEST/GOV/10001/SKRA-Cloud-Protected/Midlun-v1' NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default' NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' NOVA_ACCEPT_UNAUTHORIZED: 'false' diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index c37074a33485..af8f99c4d36f 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -1285,6 +1285,7 @@ endorsement-system-api: EMAIL_FROM_ADDRESS: 'development@island.is' EMAIL_FROM_NAME: 'devland.is' EMAIL_REGION: 'eu-west-1' + ENDORSEMENT_SYSTEM_EXPORTS_BUCKET_NAME: 'island-is-dev-exports-endorsement-system' IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/endorsement' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is' LOG_LEVEL: 'info' @@ -2957,6 +2958,8 @@ user-notification: env: AUTH_DELEGATION_API_URL: 'http://web-services-auth-delegation-api.identity-server-delegation.svc.cluster.local' AUTH_DELEGATION_MACHINE_CLIENT_SCOPE: '["@island.is/auth/delegations/index:system"]' + COMPANY_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' + COMPANY_REGISTRY_XROAD_PROVIDER_ID: 'IS-DEV/GOV/10006/Skatturinn/ft-v1' DB_HOST: 'postgres-applications.internal' DB_NAME: 'user_notification' DB_REPLICAS_HOST: 'postgres-applications-reader.internal' @@ -3152,6 +3155,8 @@ user-notification-worker: env: AUTH_DELEGATION_API_URL: 'http://web-services-auth-delegation-api.identity-server-delegation.svc.cluster.local' AUTH_DELEGATION_MACHINE_CLIENT_SCOPE: '["@island.is/auth/delegations/index:system"]' + COMPANY_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' + COMPANY_REGISTRY_XROAD_PROVIDER_ID: 'IS-DEV/GOV/10006/Skatturinn/ft-v1' CONTENTFUL_HOST: 'preview.contentful.com' DB_HOST: 'postgres-applications.internal' DB_NAME: 'user_notification' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index 6ccb3917b3bd..c47adc3b38ed 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -1280,6 +1280,7 @@ endorsement-system-api: EMAIL_FROM_ADDRESS: 'noreply@island.is' EMAIL_FROM_NAME: 'island.is' EMAIL_REGION: 'eu-west-1' + ENDORSEMENT_SYSTEM_EXPORTS_BUCKET_NAME: 'island-is-prod-exports-endorsement-system' IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/endorsement' IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is' LOG_LEVEL: 'info' @@ -2834,6 +2835,8 @@ user-notification: env: AUTH_DELEGATION_API_URL: 'https://auth-delegation-api.internal.innskra.island.is' AUTH_DELEGATION_MACHINE_CLIENT_SCOPE: '["@island.is/auth/delegations/index:system"]' + COMPANY_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' + COMPANY_REGISTRY_XROAD_PROVIDER_ID: 'IS/GOV/5402696029/Skatturinn/ft-v1' DB_HOST: 'postgres-applications.internal' DB_NAME: 'user_notification' DB_REPLICAS_HOST: 'postgres-applications.internal' @@ -3029,6 +3032,8 @@ user-notification-worker: env: AUTH_DELEGATION_API_URL: 'https://auth-delegation-api.internal.innskra.island.is' AUTH_DELEGATION_MACHINE_CLIENT_SCOPE: '["@island.is/auth/delegations/index:system"]' + COMPANY_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' + COMPANY_REGISTRY_XROAD_PROVIDER_ID: 'IS/GOV/5402696029/Skatturinn/ft-v1' CONTENTFUL_HOST: 'cdn.contentful.com' DB_HOST: 'postgres-applications.internal' DB_NAME: 'user_notification' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index d779344442f4..b25e6403014a 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -326,82 +326,82 @@ api: USER_NOTIFICATION_API_URL: 'http://web-user-notification.user-notification.svc.cluster.local' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_ADR_MACHINE_LICENSE_PATH: 'IS-TEST/GOV/4201810439/Vinnueftirlitid-Protected/rettindi-token-v1' - XROAD_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10056/LBHI-Protected/brautskraning-v1' - XROAD_AIRCRAFT_REGISTRY_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Loftfaraskra-V1' + XROAD_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10056/LBHI-Protected/brautskraning-v1' + XROAD_AIRCRAFT_REGISTRY_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Loftfaraskra-V1' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.staging01.devland.is/r1/IS-TEST' - XROAD_BIFROST_UNIVERSITY_PATH: 'IS-DEV/EDU/10057/Bifrost-Protected/brautskraning-v1' - XROAD_CHARGE_FJS_V2_PATH: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2' + XROAD_BIFROST_UNIVERSITY_PATH: 'IS-TEST/EDU/10057/Bifrost-Protected/brautskraning-v1' + XROAD_CHARGE_FJS_V2_PATH: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2' XROAD_CHARGE_FJS_V2_TIMEOUT: '20000' XROAD_CLIENT_ID: 'IS-TEST/GOV/5501692829/island-is-client' - XROAD_COURT_BANKRUPTCY_CERT_PATH: 'IS-DEV/GOV/10019/Domstolasyslan/JusticePortal-v1' + XROAD_COURT_BANKRUPTCY_CERT_PATH: 'IS-TEST/GOV/10019/Domstolasyslan/JusticePortal-v1' XROAD_CRIMINAL_RECORD_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/Sakaskra-v1' - XROAD_DIGITAL_TACHOGRAPH_DRIVERS_CARD_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Okuritar-V1' - XROAD_DIRECTORATE_OF_IMMIGRATION_PATH: 'IS-DEV/GOV/10011/UTL-Protected/Utl-Umsokn-v1' + XROAD_DIGITAL_TACHOGRAPH_DRIVERS_CARD_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Okuritar-V1' + XROAD_DIRECTORATE_OF_IMMIGRATION_PATH: 'IS-TEST/GOV/10011/UTL-Protected/Utl-Umsokn-v1' XROAD_DISABILITY_LICENSE_PATH: 'IS-TEST/GOV/5012130120/TR-Protected/oryrki-v1' - XROAD_DISTRICT_COMMISSIONERS_LICENSES_PATH: 'IS-DEV/GOV/10016/Syslumenn-Protected/RettindiIslandis' - XROAD_DISTRICT_COMMISSIONERS_P_CARD_PATH: 'IS-DEV/GOV/10016/Syslumenn-Protected/IslandMinarSidur' + XROAD_DISTRICT_COMMISSIONERS_LICENSES_PATH: 'IS-TEST/GOV/10016/Syslumenn-Protected/RettindiIslandis' + XROAD_DISTRICT_COMMISSIONERS_P_CARD_PATH: 'IS-TEST/GOV/10016/Syslumenn-Protected/IslandMinarSidur' XROAD_DRIVING_LICENSE_BOOK_TIMEOUT: '20000' XROAD_DRIVING_LICENSE_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/RafraentOkuskirteini-v1' XROAD_DRIVING_LICENSE_V2_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/RafraentOkuskirteini-v2' XROAD_DRIVING_LICENSE_V4_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/Okuskirteini-v4' XROAD_DRIVING_LICENSE_V5_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/Okuskirteini-v5' - XROAD_ENERGY_FUNDS_PATH: 'IS-DEV/GOV/10021/FJS-Public/ElectricCarSubSidyService_v1' - XROAD_FINANCES_PATH: 'IS-DEV/GOV/10021/FJS-Public/financeIsland' + XROAD_ENERGY_FUNDS_PATH: 'IS-TEST/GOV/10021/FJS-Public/ElectricCarSubSidyService_v1' + XROAD_FINANCES_PATH: 'IS-TEST/GOV/10021/FJS-Public/financeIsland' XROAD_FINANCES_TIMEOUT: '20000' - XROAD_FINANCES_V2_PATH: 'IS-DEV/GOV/10021/FJS-Public/financeServicesFJS_v2' + XROAD_FINANCES_V2_PATH: 'IS-TEST/GOV/10021/FJS-Public/financeServicesFJS_v2' XROAD_FINANCIAL_AID_BACKEND_PATH: 'IS-TEST/MUN/5502694739/samband-sveitarfelaga/financial-aid-backend' XROAD_FIREARM_LICENSE_PATH: 'IS/GOV/5309672079/Logreglan-Protected/island-api-v1' - XROAD_HEALTH_DIRECTORATE_PATH: 'IS-DEV/GOV/10015/EmbaettiLandlaeknis-Protected/landlaeknir' + XROAD_HEALTH_DIRECTORATE_PATH: 'IS-TEST/GOV/10015/EmbaettiLandlaeknis-Protected/landlaeknir' XROAD_HEALTH_INSURANCE_ID: 'IS-TEST/GOV/4804080550/SJUKRA-Protected' XROAD_HEALTH_INSURANCE_MY_PAGES_PATH: 'IS-TEST/GOV/4804080550/SJUKRA-Protected/minarsidur' XROAD_HEALTH_INSURANCE_WSDLURL: 'https://test-huld.sjukra.is/islandrg?wsdl' XROAD_HMS_HOUSING_BENEFITS_PATH: 'IS-TEST/GOV/5812191480/HMS-Protected/husbot-v1' XROAD_HMS_LOANS_PATH: 'IS-TEST/GOV/5812191480/HMS-Protected/libra-v1' - XROAD_HOLAR_UNIVERSITY_PATH: 'IS-DEV/EDU/10055/Holar-Protected/brautskraning-v1' + XROAD_HOLAR_UNIVERSITY_PATH: 'IS-TEST/EDU/10055/Holar-Protected/brautskraning-v1' XROAD_HOUSING_BENEFIT_CALCULATOR_PATH: 'IS-TEST/GOV/5812191480/HMS-Protected/calc-v1' - XROAD_HUNTING_LICENSE_PATH: 'IS-DEV/GOV/10009/Umhverfisstofnun-Protected/api' - XROAD_ICELANDIC_GOVERNMENT_INSTITUTION_VACANCIES_PATH: 'IS-DEV/GOV/10021/FJS-Protected/recruitment-v1' - XROAD_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-DEV/EDU/10049/LHI-Protected/brautskraning-v1' + XROAD_HUNTING_LICENSE_PATH: 'IS-TEST/GOV/10009/Umhverfisstofnun-Protected/api' + XROAD_ICELANDIC_GOVERNMENT_INSTITUTION_VACANCIES_PATH: 'IS-TEST/GOV/10021/FJS-Protected/recruitment-v1' + XROAD_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-TEST/EDU/10049/LHI-Protected/brautskraning-v1' XROAD_INNA_PATH: 'IS-TEST/GOV/6601241280/MMS-Protected/inna-v1' XROAD_INTELLECTUAL_PROPERTIES_PATH: 'IS-TEST/GOV/6501912189/WebAPI-Public/HUG-webAPI/' - XROAD_MMS_FRIGG_PATH: 'IS-DEV/GOV/10066/MMS-Protected/frigg-api' + XROAD_MMS_FRIGG_PATH: 'IS-TEST/GOV/10066/MMS-Protected/frigg-api' XROAD_MMS_GRADE_SERVICE_ID: 'IS-TEST/GOV/6601241280/MMS-Protected/grade-api-v1' XROAD_MMS_LICENSE_SERVICE_ID: 'IS-TEST/GOV/6601241280/MMS-Protected/license-api-v1' XROAD_NATIONAL_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' XROAD_NATIONAL_REGISTRY_SERVICE_PATH: 'IS-TEST/GOV/6503760649/SKRA-Protected/Einstaklingar-v1' XROAD_NATIONAL_REGISTRY_TIMEOUT: '20000' - XROAD_OFFICIAL_JOURNAL_APPLICATION_PATH: 'IS-DEV/GOV/10014/DMR-Protected/official-journal-application' - XROAD_OFFICIAL_JOURNAL_PATH: 'IS-DEV/GOV/10014/DMR-Protected/official-journal' + XROAD_OFFICIAL_JOURNAL_APPLICATION_PATH: 'IS-TEST/GOV/10014/DMR-Protected/official-journal-application' + XROAD_OFFICIAL_JOURNAL_PATH: 'IS-TEST/GOV/10014/DMR-Protected/official-journal' XROAD_PASSPORT_LICENSE_PATH: 'IS-TEST/GOV/6503760649/SKRA-Cloud-Protected/Forskraning-V1' XROAD_PAYMENT_ADDITION_CALLBACK_URL: '/' XROAD_PAYMENT_BASE_CALLBACK_URL: 'XROAD:' XROAD_PAYMENT_PROVIDER_ID: 'IS-TEST/GOV/10021/FJS-DEV-Public' - XROAD_PAYMENT_SCHEDULE_PATH: 'IS-DEV/GOV/10021/FJS-Public/paymentSchedule_v1' + XROAD_PAYMENT_SCHEDULE_PATH: 'IS-TEST/GOV/10021/FJS-Public/paymentSchedule_v1' XROAD_PROPERTIES_SERVICE_V2_PATH: 'IS-TEST/GOV/5812191480/HMS-Protected/Fasteignir-v1' XROAD_PROPERTIES_TIMEOUT: '35000' XROAD_RSK_PROCURING_PATH: 'IS-TEST/GOV/5402696029/Skatturinn/relationships-v1' XROAD_RSK_PROCURING_REDIS_NODES: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' XROAD_RSK_PROCURING_SCOPE: '["@rsk.is/prokura","@rsk.is/prokura:admin"]' - XROAD_SHIP_REGISTRY_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/skipaskra-V1' + XROAD_SHIP_REGISTRY_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/skipaskra-V1' XROAD_SIGNATURE_COLLECTION_PATH: 'IS-TEST/GOV/6503760649/SKRA-Cloud-Protected/Medmaeli-v1' XROAD_TJODSKRA_API_PATH: '/SKRA-Protected/Einstaklingar-v1' XROAD_TJODSKRA_MEMBER_CODE: '6503760649' XROAD_TLS_BASE_PATH: 'https://securityserver.staging01.devland.is' XROAD_TLS_BASE_PATH_WITH_ENV: 'https://securityserver.staging01.devland.is/r1/IS-TEST' XROAD_TR_PATH: 'IS-TEST/GOV/5012130120/TR-Protected/external-v1' - XROAD_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-DEV/EDU/10054/UNAK-Protected/brautskraning-v1' - XROAD_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10010/HI-Protected/brautskraning-v1' + XROAD_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-TEST/EDU/10054/UNAK-Protected/brautskraning-v1' + XROAD_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10010/HI-Protected/brautskraning-v1' XROAD_VEHICLES_MILEAGE_PATH: 'IS/GOV/5405131040/Samgongustofa-Protected/Vehicle-Mileagereading-V1' XROAD_VEHICLES_PATH: 'IS/GOV/5405131040/Samgongustofa-Protected/Mitt-Svaedi-V1' - XROAD_VEHICLE_CODETABLES_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Codetables-V1' - XROAD_VEHICLE_INFOLOCKS_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Infolocks-V1' - XROAD_VEHICLE_OPERATORS_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Operators-V3' - XROAD_VEHICLE_OWNER_CHANGE_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Ownerchange-V2' - XROAD_VEHICLE_PLATE_ORDERING_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOrdering-V1' - XROAD_VEHICLE_PLATE_RENEWAL_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOwnership-V1' - XROAD_VEHICLE_PRINTING_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Printing-V1' - XROAD_VEHICLE_SERVICE_FJS_V1_PATH: 'IS-DEV/GOV/10021/FJS-Public/VehicleServiceFJS_v1' + XROAD_VEHICLE_CODETABLES_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Codetables-V1' + XROAD_VEHICLE_INFOLOCKS_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Infolocks-V1' + XROAD_VEHICLE_OPERATORS_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Operators-V3' + XROAD_VEHICLE_OWNER_CHANGE_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Ownerchange-V2' + XROAD_VEHICLE_PLATE_ORDERING_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOrdering-V1' + XROAD_VEHICLE_PLATE_RENEWAL_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOwnership-V1' + XROAD_VEHICLE_PRINTING_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Printing-V1' + XROAD_VEHICLE_SERVICE_FJS_V1_PATH: 'IS-TEST/GOV/10021/FJS-Public/VehicleServiceFJS_v1' XROAD_VMST_API_PATH: '/VMST-ParentalLeave-Protected/ParentalLeaveApplication-v1' XROAD_VMST_MEMBER_CODE: '7005942039' XROAD_WORK_MACHINE_LICENSE_PATH: 'IS-TEST/GOV/4201810439/Vinnueftirlitid-Protected/vinnuvelar-token' @@ -610,46 +610,46 @@ application-system-api: SERVICE_DOCUMENTS_BASEPATH: 'http://web-services-documents.services-documents.svc.cluster.local' SERVICE_USER_PROFILE_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' UNIVERSITY_GATEWAY_API_URL: 'http://web-services-university-gateway.services-university-gateway.svc.cluster.local' - WORKPOINT_ARBORG_SERVICE_PATH: 'IS-DEV/MUN/10036/Arborg-Protected/tengill-application-v1' - XROAD_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10056/LBHI-Protected/brautskraning-v1' - XROAD_ALTHINGI_OMBUDSMAN_SERVICE_PATH: 'IS-DEV/GOV/10047/UA-Protected/kvortun-v1/' + WORKPOINT_ARBORG_SERVICE_PATH: 'IS-TEST/MUN/10036/Arborg-Protected/tengill-application-v1' + XROAD_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10056/LBHI-Protected/brautskraning-v1' + XROAD_ALTHINGI_OMBUDSMAN_SERVICE_PATH: 'IS-TEST/GOV/10047/UA-Protected/kvortun-v1/' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.staging01.devland.is/r1/IS-TEST' - XROAD_BIFROST_UNIVERSITY_PATH: 'IS-DEV/EDU/10057/Bifrost-Protected/brautskraning-v1' - XROAD_CHARGE_FJS_V2_PATH: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2' + XROAD_BIFROST_UNIVERSITY_PATH: 'IS-TEST/EDU/10057/Bifrost-Protected/brautskraning-v1' + XROAD_CHARGE_FJS_V2_PATH: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2' XROAD_CHARGE_FJS_V2_TIMEOUT: '20000' XROAD_CLIENT_ID: 'IS-TEST/GOV/5501692829/island-is-client' - XROAD_COURT_BANKRUPTCY_CERT_PATH: 'IS-DEV/GOV/10019/Domstolasyslan/JusticePortal-v1' + XROAD_COURT_BANKRUPTCY_CERT_PATH: 'IS-TEST/GOV/10019/Domstolasyslan/JusticePortal-v1' XROAD_CRIMINAL_RECORD_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/Sakaskra-v1' - XROAD_DIGITAL_TACHOGRAPH_DRIVERS_CARD_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Okuritar-V1' - XROAD_DIRECTORATE_OF_IMMIGRATION_PATH: 'IS-DEV/GOV/10011/UTL-Protected/Utl-Umsokn-v1' + XROAD_DIGITAL_TACHOGRAPH_DRIVERS_CARD_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Okuritar-V1' + XROAD_DIRECTORATE_OF_IMMIGRATION_PATH: 'IS-TEST/GOV/10011/UTL-Protected/Utl-Umsokn-v1' XROAD_DRIVING_LICENSE_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/RafraentOkuskirteini-v1' XROAD_DRIVING_LICENSE_V2_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/RafraentOkuskirteini-v2' XROAD_DRIVING_LICENSE_V4_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/Okuskirteini-v4' XROAD_DRIVING_LICENSE_V5_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/Okuskirteini-v5' - XROAD_ENERGY_FUNDS_PATH: 'IS-DEV/GOV/10021/FJS-Public/ElectricCarSubSidyService_v1' - XROAD_FINANCES_PATH: 'IS-DEV/GOV/10021/FJS-Public/financeIsland' - XROAD_FINANCES_V2_PATH: 'IS-DEV/GOV/10021/FJS-Public/financeServicesFJS_v2' + XROAD_ENERGY_FUNDS_PATH: 'IS-TEST/GOV/10021/FJS-Public/ElectricCarSubSidyService_v1' + XROAD_FINANCES_PATH: 'IS-TEST/GOV/10021/FJS-Public/financeIsland' + XROAD_FINANCES_V2_PATH: 'IS-TEST/GOV/10021/FJS-Public/financeServicesFJS_v2' XROAD_FINANCIAL_AID_BACKEND_PATH: 'IS-TEST/MUN/5502694739/samband-sveitarfelaga/financial-aid-backend' - XROAD_HEALTH_DIRECTORATE_PATH: 'IS-DEV/GOV/10015/EmbaettiLandlaeknis-Protected/landlaeknir' + XROAD_HEALTH_DIRECTORATE_PATH: 'IS-TEST/GOV/10015/EmbaettiLandlaeknis-Protected/landlaeknir' XROAD_HEALTH_INSURANCE_ID: 'IS-TEST/GOV/4804080550/SJUKRA-Protected' XROAD_HEALTH_INSURANCE_MY_PAGES_PATH: 'IS-TEST/GOV/4804080550/SJUKRA-Protected/minarsidur' XROAD_HEALTH_INSURANCE_WSDLURL: 'https://test-huld.sjukra.is/islandrg?wsdl' XROAD_HMS_HOUSING_BENEFITS_PATH: 'IS-TEST/GOV/5812191480/HMS-Protected/husbot-v1' XROAD_HMS_LOANS_PATH: 'IS-TEST/GOV/5812191480/HMS-Protected/libra-v1' - XROAD_HOLAR_UNIVERSITY_PATH: 'IS-DEV/EDU/10055/Holar-Protected/brautskraning-v1' - XROAD_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-DEV/EDU/10049/LHI-Protected/brautskraning-v1' + XROAD_HOLAR_UNIVERSITY_PATH: 'IS-TEST/EDU/10055/Holar-Protected/brautskraning-v1' + XROAD_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-TEST/EDU/10049/LHI-Protected/brautskraning-v1' XROAD_INNA_PATH: 'IS-TEST/GOV/6601241280/MMS-Protected/inna-v1' - XROAD_MMS_FRIGG_PATH: 'IS-DEV/GOV/10066/MMS-Protected/frigg-api' + XROAD_MMS_FRIGG_PATH: 'IS-TEST/GOV/10066/MMS-Protected/frigg-api' XROAD_NATIONAL_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' XROAD_NATIONAL_REGISTRY_SERVICE_PATH: 'IS-TEST/GOV/6503760649/SKRA-Protected/Einstaklingar-v1' - XROAD_OFFICIAL_JOURNAL_APPLICATION_PATH: 'IS-DEV/GOV/10014/DMR-Protected/official-journal-application' - XROAD_OFFICIAL_JOURNAL_PATH: 'IS-DEV/GOV/10014/DMR-Protected/official-journal' + XROAD_OFFICIAL_JOURNAL_APPLICATION_PATH: 'IS-TEST/GOV/10014/DMR-Protected/official-journal-application' + XROAD_OFFICIAL_JOURNAL_PATH: 'IS-TEST/GOV/10014/DMR-Protected/official-journal' XROAD_PASSPORT_LICENSE_PATH: 'IS-TEST/GOV/6503760649/SKRA-Cloud-Protected/Forskraning-V1' XROAD_PAYMENT_ADDITION_CALLBACK_URL: '/' XROAD_PAYMENT_BASE_CALLBACK_URL: 'XROAD:' XROAD_PAYMENT_PROVIDER_ID: 'IS-TEST/GOV/10021/FJS-DEV-Public' - XROAD_PAYMENT_SCHEDULE_PATH: 'IS-DEV/GOV/10021/FJS-Public/paymentSchedule_v1' + XROAD_PAYMENT_SCHEDULE_PATH: 'IS-TEST/GOV/10021/FJS-Public/paymentSchedule_v1' XROAD_PROPERTIES_SERVICE_V2_PATH: 'IS-TEST/GOV/5812191480/HMS-Protected/Fasteignir-v1' XROAD_SIGNATURE_COLLECTION_PATH: 'IS-TEST/GOV/6503760649/SKRA-Cloud-Protected/Medmaeli-v1' XROAD_TJODSKRA_API_PATH: '/SKRA-Protected/Einstaklingar-v1' @@ -657,18 +657,18 @@ application-system-api: XROAD_TLS_BASE_PATH: 'https://securityserver.staging01.devland.is' XROAD_TLS_BASE_PATH_WITH_ENV: 'https://securityserver.staging01.devland.is/r1/IS-TEST' XROAD_TR_PATH: 'IS-TEST/GOV/5012130120/TR-Protected/external-v1' - XROAD_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-DEV/EDU/10054/UNAK-Protected/brautskraning-v1' - XROAD_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10010/HI-Protected/brautskraning-v1' + XROAD_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-TEST/EDU/10054/UNAK-Protected/brautskraning-v1' + XROAD_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10010/HI-Protected/brautskraning-v1' XROAD_VEHICLES_MILEAGE_PATH: 'IS/GOV/5405131040/Samgongustofa-Protected/Vehicle-Mileagereading-V1' XROAD_VEHICLES_PATH: 'IS/GOV/5405131040/Samgongustofa-Protected/Mitt-Svaedi-V1' - XROAD_VEHICLE_CODETABLES_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Codetables-V1' - XROAD_VEHICLE_INFOLOCKS_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Infolocks-V1' - XROAD_VEHICLE_OPERATORS_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Operators-V3' - XROAD_VEHICLE_OWNER_CHANGE_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Ownerchange-V2' - XROAD_VEHICLE_PLATE_ORDERING_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOrdering-V1' - XROAD_VEHICLE_PLATE_RENEWAL_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOwnership-V1' - XROAD_VEHICLE_PRINTING_PATH: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Printing-V1' - XROAD_VEHICLE_SERVICE_FJS_V1_PATH: 'IS-DEV/GOV/10021/FJS-Public/VehicleServiceFJS_v1' + XROAD_VEHICLE_CODETABLES_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Codetables-V1' + XROAD_VEHICLE_INFOLOCKS_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Infolocks-V1' + XROAD_VEHICLE_OPERATORS_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Operators-V3' + XROAD_VEHICLE_OWNER_CHANGE_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Ownerchange-V2' + XROAD_VEHICLE_PLATE_ORDERING_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOrdering-V1' + XROAD_VEHICLE_PLATE_RENEWAL_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOwnership-V1' + XROAD_VEHICLE_PRINTING_PATH: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Printing-V1' + XROAD_VEHICLE_SERVICE_FJS_V1_PATH: 'IS-TEST/GOV/10021/FJS-Public/VehicleServiceFJS_v1' XROAD_VMST_API_PATH: '/VMST-ParentalLeave-Protected/ParentalLeaveApplication-v1' XROAD_VMST_MEMBER_CODE: '7005942039' XROAD_WORK_MACHINE_LICENSE_PATH: 'IS-TEST/GOV/4201810439/Vinnueftirlitid-Protected/vinnuvelar-token' @@ -829,7 +829,7 @@ application-system-api-worker: SERVERSIDE_FEATURES_ON: '' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.staging01.devland.is/r1/IS-TEST' - XROAD_CHARGE_FJS_V2_PATH: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2' + XROAD_CHARGE_FJS_V2_PATH: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2' XROAD_CLIENT_ID: 'IS-TEST/GOV/5501692829/island-is-client' XROAD_INNA_PATH: 'IS-TEST/GOV/6601241280/MMS-Protected/inna-v1' XROAD_PAYMENT_ADDITION_CALLBACK_URL: '/' @@ -1050,28 +1050,28 @@ download-service: NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' REGULATIONS_ADMIN_URL: 'http://web-regulations-admin-backend.regulations-admin.svc.cluster.local' SERVERSIDE_FEATURES_ON: '' - XROAD_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10056/LBHI-Protected/brautskraning-v1' + XROAD_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10056/LBHI-Protected/brautskraning-v1' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.staging01.devland.is/r1/IS-TEST' - XROAD_BIFROST_UNIVERSITY_PATH: 'IS-DEV/EDU/10057/Bifrost-Protected/brautskraning-v1' + XROAD_BIFROST_UNIVERSITY_PATH: 'IS-TEST/EDU/10057/Bifrost-Protected/brautskraning-v1' XROAD_CLIENT_ID: 'IS-TEST/GOV/5501692829/island-is-client' - XROAD_DISTRICT_COMMISSIONERS_LICENSES_PATH: 'IS-DEV/GOV/10016/Syslumenn-Protected/RettindiIslandis' - XROAD_DISTRICT_COMMISSIONERS_P_CARD_PATH: 'IS-DEV/GOV/10016/Syslumenn-Protected/IslandMinarSidur' - XROAD_FINANCES_PATH: 'IS-DEV/GOV/10021/FJS-Public/financeIsland' - XROAD_FINANCES_V2_PATH: 'IS-DEV/GOV/10021/FJS-Public/financeServicesFJS_v2' + XROAD_DISTRICT_COMMISSIONERS_LICENSES_PATH: 'IS-TEST/GOV/10016/Syslumenn-Protected/RettindiIslandis' + XROAD_DISTRICT_COMMISSIONERS_P_CARD_PATH: 'IS-TEST/GOV/10016/Syslumenn-Protected/IslandMinarSidur' + XROAD_FINANCES_PATH: 'IS-TEST/GOV/10021/FJS-Public/financeIsland' + XROAD_FINANCES_V2_PATH: 'IS-TEST/GOV/10021/FJS-Public/financeServicesFJS_v2' XROAD_HEALTH_INSURANCE_ID: 'IS-TEST/GOV/4804080550/SJUKRA-Protected' XROAD_HEALTH_INSURANCE_MY_PAGES_PATH: 'IS-TEST/GOV/4804080550/SJUKRA-Protected/minarsidur' XROAD_HEALTH_INSURANCE_WSDLURL: 'https://test-huld.sjukra.is/islandrg?wsdl' XROAD_HMS_HOUSING_BENEFITS_PATH: 'IS-TEST/GOV/5812191480/HMS-Protected/husbot-v1' XROAD_HMS_LOANS_PATH: 'IS-TEST/GOV/5812191480/HMS-Protected/libra-v1' - XROAD_HOLAR_UNIVERSITY_PATH: 'IS-DEV/EDU/10055/Holar-Protected/brautskraning-v1' - XROAD_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-DEV/EDU/10049/LHI-Protected/brautskraning-v1' + XROAD_HOLAR_UNIVERSITY_PATH: 'IS-TEST/EDU/10055/Holar-Protected/brautskraning-v1' + XROAD_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-TEST/EDU/10049/LHI-Protected/brautskraning-v1' XROAD_MMS_GRADE_SERVICE_ID: 'IS-TEST/GOV/6601241280/MMS-Protected/grade-api-v1' XROAD_MMS_LICENSE_SERVICE_ID: 'IS-TEST/GOV/6601241280/MMS-Protected/license-api-v1' XROAD_TLS_BASE_PATH: 'https://securityserver.staging01.devland.is' XROAD_TLS_BASE_PATH_WITH_ENV: 'https://securityserver.staging01.devland.is/r1/IS-TEST' - XROAD_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-DEV/EDU/10054/UNAK-Protected/brautskraning-v1' - XROAD_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10010/HI-Protected/brautskraning-v1' + XROAD_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-TEST/EDU/10054/UNAK-Protected/brautskraning-v1' + XROAD_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10010/HI-Protected/brautskraning-v1' XROAD_VEHICLES_PATH: 'IS/GOV/5405131040/Samgongustofa-Protected/Mitt-Svaedi-V1' XROAD_WORK_MACHINE_LICENSE_PATH: 'IS-TEST/GOV/4201810439/Vinnueftirlitid-Protected/vinnuvelar-token' grantNamespaces: @@ -1156,6 +1156,7 @@ endorsement-system-api: EMAIL_FROM_ADDRESS: 'development@island.is' EMAIL_FROM_NAME: 'devland.is' EMAIL_REGION: 'eu-west-1' + ENDORSEMENT_SYSTEM_EXPORTS_BUCKET_NAME: 'island-is-staging-exports-endorsement-system' IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/endorsement' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is' LOG_LEVEL: 'info' @@ -1427,7 +1428,7 @@ license-api: XROAD_DRIVING_LICENSE_V4_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/Okuskirteini-v4' XROAD_DRIVING_LICENSE_V5_PATH: 'r1/IS/GOV/5309672079/Logreglan-Protected/Okuskirteini-v5' XROAD_FIREARM_LICENSE_PATH: 'IS/GOV/5309672079/Logreglan-Protected/island-api-v1' - XROAD_HUNTING_LICENSE_PATH: 'IS-DEV/GOV/10009/Umhverfisstofnun-Protected/api' + XROAD_HUNTING_LICENSE_PATH: 'IS-TEST/GOV/10009/Umhverfisstofnun-Protected/api' XROAD_TLS_BASE_PATH: 'https://securityserver.staging01.devland.is' XROAD_TLS_BASE_PATH_WITH_ENV: 'https://securityserver.staging01.devland.is/r1/IS-TEST' grantNamespaces: @@ -2337,13 +2338,13 @@ services-university-gateway: XROAD_CLIENT_ID: 'IS-TEST/GOV/5501692829/island-is-client' XROAD_TLS_BASE_PATH: 'https://securityserver.staging01.devland.is' XROAD_TLS_BASE_PATH_WITH_ENV: 'https://securityserver.staging01.devland.is/r1/IS-TEST' - XROAD_UNIVERSITY_GATEWAY_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10056/LBHI-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_BIFROST_UNIVERSITY_PATH: 'IS-DEV/EDU/10057/Bifrost-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_HOLAR_UNIVERSITY_PATH: 'IS-DEV/EDU/10055/Holar-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-DEV/EDU/10049/LHI-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10056/LBHI-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_BIFROST_UNIVERSITY_PATH: 'IS-TEST/EDU/10057/Bifrost-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_HOLAR_UNIVERSITY_PATH: 'IS-TEST/EDU/10055/Holar-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-TEST/EDU/10049/LHI-Protected/umsoknir-v1' XROAD_UNIVERSITY_GATEWAY_REYKJAVIK_UNIVERSITY_PATH: 'IS-TEST/EDU/5101054190/RvkUni-Hvin-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-DEV/EDU/10054/UNAK-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10010/HI-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-TEST/EDU/10054/UNAK-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10010/HI-Protected/umsoknir-v1' grantNamespaces: - 'islandis' - 'nginx-ingress-internal' @@ -2466,13 +2467,13 @@ services-university-gateway-worker: XROAD_CLIENT_ID: 'IS-TEST/GOV/5501692829/island-is-client' XROAD_TLS_BASE_PATH: 'https://securityserver.staging01.devland.is' XROAD_TLS_BASE_PATH_WITH_ENV: 'https://securityserver.staging01.devland.is/r1/IS-TEST' - XROAD_UNIVERSITY_GATEWAY_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10056/LBHI-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_BIFROST_UNIVERSITY_PATH: 'IS-DEV/EDU/10057/Bifrost-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_HOLAR_UNIVERSITY_PATH: 'IS-DEV/EDU/10055/Holar-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-DEV/EDU/10049/LHI-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10056/LBHI-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_BIFROST_UNIVERSITY_PATH: 'IS-TEST/EDU/10057/Bifrost-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_HOLAR_UNIVERSITY_PATH: 'IS-TEST/EDU/10055/Holar-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: 'IS-TEST/EDU/10049/LHI-Protected/umsoknir-v1' XROAD_UNIVERSITY_GATEWAY_REYKJAVIK_UNIVERSITY_PATH: 'IS-TEST/EDU/5101054190/RvkUni-Hvin-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-DEV/EDU/10054/UNAK-Protected/umsoknir-v1' - XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_ICELAND_PATH: 'IS-DEV/EDU/10010/HI-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_AKUREYRI_PATH: 'IS-TEST/EDU/10054/UNAK-Protected/umsoknir-v1' + XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_ICELAND_PATH: 'IS-TEST/EDU/10010/HI-Protected/umsoknir-v1' grantNamespaces: - 'islandis' - 'nginx-ingress-internal' @@ -2700,6 +2701,8 @@ user-notification: env: AUTH_DELEGATION_API_URL: 'http://web-services-auth-delegation-api.identity-server-delegation.svc.cluster.local' AUTH_DELEGATION_MACHINE_CLIENT_SCOPE: '["@island.is/auth/delegations/index:system"]' + COMPANY_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' + COMPANY_REGISTRY_XROAD_PROVIDER_ID: 'IS-TEST/GOV/5402696029/Skatturinn/ft-v1' DB_HOST: 'postgres-applications.internal' DB_NAME: 'user_notification' DB_REPLICAS_HOST: 'postgres-applications.internal' @@ -2895,6 +2898,8 @@ user-notification-worker: env: AUTH_DELEGATION_API_URL: 'http://web-services-auth-delegation-api.identity-server-delegation.svc.cluster.local' AUTH_DELEGATION_MACHINE_CLIENT_SCOPE: '["@island.is/auth/delegations/index:system"]' + COMPANY_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' + COMPANY_REGISTRY_XROAD_PROVIDER_ID: 'IS-TEST/GOV/5402696029/Skatturinn/ft-v1' CONTENTFUL_HOST: 'cdn.contentful.com' DB_HOST: 'postgres-applications.internal' DB_NAME: 'user_notification' diff --git a/infra/package.json b/infra/package.json index 1342647c11bc..db86f28487b8 100644 --- a/infra/package.json +++ b/infra/package.json @@ -31,7 +31,7 @@ "glob": "10.3.3", "js-yaml": "4.0.0", "lodash": "4.17.21", - "yargs": "17.2.1" + "yargs": "17.7.2" }, "volta": { "node": "18.12.1" diff --git a/infra/src/dsl/xroad.ts b/infra/src/dsl/xroad.ts index 6bc6fc0750ac..a61c0e718b27 100644 --- a/infra/src/dsl/xroad.ts +++ b/infra/src/dsl/xroad.ts @@ -194,12 +194,12 @@ export const Finance = new XroadConf({ env: { XROAD_FINANCES_PATH: { dev: 'IS-DEV/GOV/10021/FJS-Public/financeIsland', - staging: 'IS-DEV/GOV/10021/FJS-Public/financeIsland', + staging: 'IS-TEST/GOV/10021/FJS-Public/financeIsland', prod: 'IS/GOV/5402697509/FJS-Public/financeIsland', }, XROAD_FINANCES_V2_PATH: { dev: 'IS-DEV/GOV/10021/FJS-Public/financeServicesFJS_v2', - staging: 'IS-DEV/GOV/10021/FJS-Public/financeServicesFJS_v2', + staging: 'IS-TEST/GOV/10021/FJS-Public/financeServicesFJS_v2', prod: 'IS/GOV/5402697509/FJS-Public/financeServicesFJS_v2', }, XROAD_HMS_LOANS_PATH: { @@ -255,7 +255,7 @@ export const JudicialAdministration = new XroadConf({ env: { XROAD_COURT_BANKRUPTCY_CERT_PATH: { dev: 'IS-DEV/GOV/10019/Domstolasyslan/JusticePortal-v1', - staging: 'IS-DEV/GOV/10019/Domstolasyslan/JusticePortal-v1', + staging: 'IS-TEST/GOV/10019/Domstolasyslan/JusticePortal-v1', prod: 'IS/GOV/4707171140/Domstolasyslan/JusticePortal-v1', }, }, @@ -269,7 +269,7 @@ export const OccupationalLicenses = new XroadConf({ env: { XROAD_HEALTH_DIRECTORATE_PATH: { dev: 'IS-DEV/GOV/10015/EmbaettiLandlaeknis-Protected/landlaeknir', - staging: 'IS-DEV/GOV/10015/EmbaettiLandlaeknis-Protected/landlaeknir', + staging: 'IS-TEST/GOV/10015/EmbaettiLandlaeknis-Protected/landlaeknir', prod: 'IS/GOV/7101695009/EmbaettiLandlaeknis-Protected/landlaeknir', }, }, @@ -279,7 +279,7 @@ export const DistrictCommissionersPCard = new XroadConf({ env: { XROAD_DISTRICT_COMMISSIONERS_P_CARD_PATH: { dev: 'IS-DEV/GOV/10016/Syslumenn-Protected/IslandMinarSidur', - staging: 'IS-DEV/GOV/10016/Syslumenn-Protected/IslandMinarSidur', + staging: 'IS-TEST/GOV/10016/Syslumenn-Protected/IslandMinarSidur', prod: 'IS/GOV/5512201410/Syslumenn-Protected/IslandMinarSidur', }, }, @@ -288,7 +288,7 @@ export const DistrictCommissionersLicenses = new XroadConf({ env: { XROAD_DISTRICT_COMMISSIONERS_LICENSES_PATH: { dev: 'IS-DEV/GOV/10016/Syslumenn-Protected/RettindiIslandis', - staging: 'IS-DEV/GOV/10016/Syslumenn-Protected/RettindiIslandis', + staging: 'IS-TEST/GOV/10016/Syslumenn-Protected/RettindiIslandis', prod: 'IS/GOV/5512201410/Syslumenn-Protected/RettindiIslandis', }, }, @@ -308,7 +308,7 @@ export const Hunting = new XroadConf({ env: { XROAD_HUNTING_LICENSE_PATH: { dev: 'IS-DEV/GOV/10009/Umhverfisstofnun-Protected/api', - staging: 'IS-DEV/GOV/10009/Umhverfisstofnun-Protected/api', + staging: 'IS-TEST/GOV/10009/Umhverfisstofnun-Protected/api', prod: 'IS/GOV/7010022880/Umhverfisstofnun-Protected/api', }, }, @@ -338,32 +338,32 @@ export const UniversityCareers = new XroadConf({ env: { XROAD_UNIVERSITY_OF_AKUREYRI_PATH: { dev: 'IS-DEV/EDU/10054/UNAK-Protected/brautskraning-v1', - staging: 'IS-DEV/EDU/10054/UNAK-Protected/brautskraning-v1', + staging: 'IS-TEST/EDU/10054/UNAK-Protected/brautskraning-v1', prod: 'IS/EDU/5206871229/UNAK-Protected/brautskraning-v1', }, XROAD_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: { dev: 'IS-DEV/EDU/10056/LBHI-Protected/brautskraning-v1', - staging: 'IS-DEV/EDU/10056/LBHI-Protected/brautskraning-v1', + staging: 'IS-TEST/EDU/10056/LBHI-Protected/brautskraning-v1', prod: 'IS/EDU/4112043590/LBHI-Protected/brautskraning-v1', }, XROAD_BIFROST_UNIVERSITY_PATH: { dev: 'IS-DEV/EDU/10057/Bifrost-Protected/brautskraning-v1', - staging: 'IS-DEV/EDU/10057/Bifrost-Protected/brautskraning-v1', + staging: 'IS-TEST/EDU/10057/Bifrost-Protected/brautskraning-v1', prod: 'IS/EDU/5502690239/Bifrost-Protected/brautskraning-v1', }, XROAD_HOLAR_UNIVERSITY_PATH: { dev: 'IS-DEV/EDU/10055/Holar-Protected/brautskraning-v1', - staging: 'IS-DEV/EDU/10055/Holar-Protected/brautskraning-v1', + staging: 'IS-TEST/EDU/10055/Holar-Protected/brautskraning-v1', prod: 'IS/EDU/5001694359/Holar-Protected/brautskraning-v1', }, XROAD_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: { dev: 'IS-DEV/EDU/10049/LHI-Protected/brautskraning-v1', - staging: 'IS-DEV/EDU/10049/LHI-Protected/brautskraning-v1', + staging: 'IS-TEST/EDU/10049/LHI-Protected/brautskraning-v1', prod: 'IS/EDU/4210984099/LHI-Protected/brautskraning-v1', }, XROAD_UNIVERSITY_OF_ICELAND_PATH: { dev: 'IS-DEV/EDU/10010/HI-Protected/brautskraning-v1', - staging: 'IS-DEV/EDU/10010/HI-Protected/brautskraning-v1', + staging: 'IS-TEST/EDU/10010/HI-Protected/brautskraning-v1', prod: 'IS/EDU/6001692039/HI-Protected/brautskraning-v1', }, }, @@ -458,7 +458,7 @@ export const NationalRegistryAuthB2C = new XroadConf({ }, NATIONAL_REGISTRY_B2C_PATH: { dev: 'IS-DEV/GOV/10001/SKRA-Cloud-Protected/Midlun-v1', - staging: 'IS-DEV/GOV/10001/SKRA-Cloud-Protected/Midlun-v1', + staging: 'IS-TEST/GOV/10001/SKRA-Cloud-Protected/Midlun-v1', prod: 'IS/GOV/6503760649/SKRA-Cloud-Protected/Midlun-v1', }, }, @@ -506,7 +506,7 @@ export const PaymentSchedule = new XroadConf({ env: { XROAD_PAYMENT_SCHEDULE_PATH: { dev: 'IS-DEV/GOV/10021/FJS-Public/paymentSchedule_v1', - staging: 'IS-DEV/GOV/10021/FJS-Public/paymentSchedule_v1', + staging: 'IS-TEST/GOV/10021/FJS-Public/paymentSchedule_v1', prod: 'IS/GOV/5402697509/FJS-Public/paymentSchedule_v1', }, }, @@ -634,7 +634,7 @@ export const ChargeFjsV2 = new XroadConf({ env: { XROAD_CHARGE_FJS_V2_PATH: { dev: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2', - staging: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2', + staging: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2', prod: 'IS/GOV/5402697509/FJS-Public/chargeFJS_v2', }, }, @@ -644,7 +644,7 @@ export const EnergyFunds = new XroadConf({ env: { XROAD_ENERGY_FUNDS_PATH: { dev: 'IS-DEV/GOV/10021/FJS-Public/ElectricCarSubSidyService_v1', - staging: 'IS-DEV/GOV/10021/FJS-Public/ElectricCarSubSidyService_v1', + staging: 'IS-TEST/GOV/10021/FJS-Public/ElectricCarSubSidyService_v1', prod: 'IS/GOV/5402697509/FJS-Public/ElectricCarSubSidyService_v1', }, }, @@ -654,7 +654,7 @@ export const VehicleServiceFjsV1 = new XroadConf({ env: { XROAD_VEHICLE_SERVICE_FJS_V1_PATH: { dev: 'IS-DEV/GOV/10021/FJS-Public/VehicleServiceFJS_v1', - staging: 'IS-DEV/GOV/10021/FJS-Public/VehicleServiceFJS_v1', + staging: 'IS-TEST/GOV/10021/FJS-Public/VehicleServiceFJS_v1', prod: 'IS/GOV/5402697509/FJS-Public/VehicleServiceFJS_v1', }, }, @@ -664,45 +664,46 @@ export const TransportAuthority = new XroadConf({ env: { XROAD_VEHICLE_CODETABLES_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Codetables-V1', - staging: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Codetables-V1', + staging: + 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Codetables-V1', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/Vehicle-Codetables-V1', }, XROAD_VEHICLE_INFOLOCKS_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Infolocks-V1', - staging: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Infolocks-V1', + staging: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Infolocks-V1', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/Vehicle-Infolocks-V1', }, XROAD_VEHICLE_OPERATORS_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Operators-V3', - staging: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Operators-V3', + staging: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Operators-V3', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/Vehicle-Operators-V3', }, XROAD_VEHICLE_OWNER_CHANGE_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Ownerchange-V2', staging: - 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Ownerchange-V2', + 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Ownerchange-V2', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/Vehicle-Ownerchange-V2', }, XROAD_VEHICLE_PLATE_ORDERING_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOrdering-V1', staging: - 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOrdering-V1', + 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOrdering-V1', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/Vehicle-PlateOrdering-V1', }, XROAD_VEHICLE_PLATE_RENEWAL_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOwnership-V1', staging: - 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOwnership-V1', + 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-PlateOwnership-V1', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/Vehicle-PlateOwnership-V1', }, XROAD_VEHICLE_PRINTING_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Printing-V1', - staging: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Vehicle-Printing-V1', + staging: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Vehicle-Printing-V1', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/Vehicle-Printing-V1', }, XROAD_DIGITAL_TACHOGRAPH_DRIVERS_CARD_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Okuritar-V1', - staging: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Okuritar-V1', + staging: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Okuritar-V1', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/Okuritar-V1', }, }, @@ -712,7 +713,7 @@ export const IcelandicGovernmentInstitutionVacancies = new XroadConf({ env: { XROAD_ICELANDIC_GOVERNMENT_INSTITUTION_VACANCIES_PATH: { dev: 'IS-DEV/GOV/10021/FJS-Protected/recruitment-v1', - staging: 'IS-DEV/GOV/10021/FJS-Protected/recruitment-v1', + staging: 'IS-TEST/GOV/10021/FJS-Protected/recruitment-v1', prod: 'IS/GOV/5402697509/FJS-Protected/recruitment-v1', }, }, @@ -728,7 +729,7 @@ export const AircraftRegistry = new XroadConf({ env: { XROAD_AIRCRAFT_REGISTRY_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Loftfaraskra-V1', - staging: 'IS-DEV/GOV/10017/Samgongustofa-Protected/Loftfaraskra-V1', + staging: 'IS-TEST/GOV/10017/Samgongustofa-Protected/Loftfaraskra-V1', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/Loftfaraskra-V1', }, }, @@ -754,7 +755,7 @@ export const ShipRegistry = new XroadConf({ env: { XROAD_SHIP_REGISTRY_PATH: { dev: 'IS-DEV/GOV/10017/Samgongustofa-Protected/skipaskra-V1', - staging: 'IS-DEV/GOV/10017/Samgongustofa-Protected/skipaskra-V1', + staging: 'IS-TEST/GOV/10017/Samgongustofa-Protected/skipaskra-V1', prod: 'IS/GOV/5405131040/Samgongustofa-Protected/skipaskra-V1', }, }, @@ -764,7 +765,7 @@ export const DirectorateOfImmigration = new XroadConf({ env: { XROAD_DIRECTORATE_OF_IMMIGRATION_PATH: { dev: 'IS-DEV/GOV/10011/UTL-Protected/Utl-Umsokn-v1', - staging: 'IS-DEV/GOV/10011/UTL-Protected/Utl-Umsokn-v1', + staging: 'IS-TEST/GOV/10011/UTL-Protected/Utl-Umsokn-v1', prod: 'IS/GOV/6702696399/UTL-Protected/Utl-Umsokn-v1', }, }, @@ -774,7 +775,7 @@ export const UniversityGatewayUniversityOfIceland = new XroadConf({ env: { XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_ICELAND_PATH: { dev: 'IS-DEV/EDU/10010/HI-Protected/umsoknir-v1', - staging: 'IS-DEV/EDU/10010/HI-Protected/umsoknir-v1', + staging: 'IS-TEST/EDU/10010/HI-Protected/umsoknir-v1', prod: 'IS/EDU/6001692039/HI-Protected/umsoknir-v1', }, }, @@ -784,7 +785,7 @@ export const UniversityGatewayUniversityOfAkureyri = new XroadConf({ env: { XROAD_UNIVERSITY_GATEWAY_UNIVERSITY_OF_AKUREYRI_PATH: { dev: 'IS-DEV/EDU/10054/UNAK-Protected/umsoknir-v1', - staging: 'IS-DEV/EDU/10054/UNAK-Protected/umsoknir-v1', + staging: 'IS-TEST/EDU/10054/UNAK-Protected/umsoknir-v1', prod: 'IS/EDU/5206871229/UNAK-Protected/umsoknir-v1', }, }, @@ -794,7 +795,7 @@ export const UniversityGatewayBifrostUniversity = new XroadConf({ env: { XROAD_UNIVERSITY_GATEWAY_BIFROST_UNIVERSITY_PATH: { dev: 'IS-DEV/EDU/10057/Bifrost-Protected/umsoknir-v1', - staging: 'IS-DEV/EDU/10057/Bifrost-Protected/umsoknir-v1', + staging: 'IS-TEST/EDU/10057/Bifrost-Protected/umsoknir-v1', prod: 'IS/EDU/5502690239/Bifrost-Protected/umsoknir-v1', }, }, @@ -804,7 +805,7 @@ export const UniversityGatewayIcelandUniversityOfTheArts = new XroadConf({ env: { XROAD_UNIVERSITY_GATEWAY_ICELAND_UNIVERSITY_OF_THE_ARTS_PATH: { dev: 'IS-DEV/EDU/10049/LHI-Protected/umsoknir-v1', - staging: 'IS-DEV/EDU/10049/LHI-Protected/umsoknir-v1', + staging: 'IS-TEST/EDU/10049/LHI-Protected/umsoknir-v1', prod: 'IS/EDU/4210984099/LHI-Protected/umsoknir-v1', }, }, @@ -814,7 +815,7 @@ export const UniversityGatewayAgriculturalUniversityOfIceland = new XroadConf({ env: { XROAD_UNIVERSITY_GATEWAY_AGRICULTURAL_UNIVERSITY_OF_ICELAND_PATH: { dev: 'IS-DEV/EDU/10056/LBHI-Protected/umsoknir-v1', - staging: 'IS-DEV/EDU/10056/LBHI-Protected/umsoknir-v1', + staging: 'IS-TEST/EDU/10056/LBHI-Protected/umsoknir-v1', prod: 'IS/EDU/4112043590/LBHI-Protected/umsoknir-v1', }, }, @@ -824,7 +825,7 @@ export const UniversityGatewayHolarUniversity = new XroadConf({ env: { XROAD_UNIVERSITY_GATEWAY_HOLAR_UNIVERSITY_PATH: { dev: 'IS-DEV/EDU/10055/Holar-Protected/umsoknir-v1', - staging: 'IS-DEV/EDU/10055/Holar-Protected/umsoknir-v1', + staging: 'IS-TEST/EDU/10055/Holar-Protected/umsoknir-v1', prod: 'IS/EDU/5001694359/Holar-Protected/umsoknir-v1', }, }, @@ -854,7 +855,7 @@ export const ArborgWorkpoint = new XroadConf({ env: { WORKPOINT_ARBORG_SERVICE_PATH: { dev: 'IS-DEV/MUN/10036/Arborg-Protected/tengill-application-v1', - staging: 'IS-DEV/MUN/10036/Arborg-Protected/tengill-application-v1', + staging: 'IS-TEST/MUN/10036/Arborg-Protected/tengill-application-v1', prod: 'IS/MUN/10036/Arborg-Protected/tengill-application-v1', }, }, @@ -864,7 +865,7 @@ export const OfficialJournalOfIceland = new XroadConf({ env: { XROAD_OFFICIAL_JOURNAL_PATH: { dev: 'IS-DEV/GOV/10014/DMR-Protected/official-journal', - staging: 'IS-DEV/GOV/10014/DMR-Protected/official-journal', + staging: 'IS-TEST/GOV/10014/DMR-Protected/official-journal', prod: 'IS/GOV/10014/DMR-Protected/official-journal', }, }, @@ -874,7 +875,7 @@ export const OfficialJournalOfIcelandApplication = new XroadConf({ env: { XROAD_OFFICIAL_JOURNAL_APPLICATION_PATH: { dev: 'IS-DEV/GOV/10014/DMR-Protected/official-journal-application', - staging: 'IS-DEV/GOV/10014/DMR-Protected/official-journal-application', + staging: 'IS-TEST/GOV/10014/DMR-Protected/official-journal-application', prod: 'IS/GOV/10014/DMR-Protected/official-journal-application', }, }, @@ -884,7 +885,7 @@ export const Frigg = new XroadConf({ env: { XROAD_MMS_FRIGG_PATH: { dev: 'IS-DEV/GOV/10066/MMS-Protected/frigg-api', - staging: 'IS-DEV/GOV/10066/MMS-Protected/frigg-api', + staging: 'IS-TEST/GOV/10066/MMS-Protected/frigg-api', prod: 'IS/GOV/10066/MMS-Protected/frigg-api', }, }, diff --git a/infra/src/feature-env.ts b/infra/src/feature-env.ts index c6e3a79f2826..8f5dfaff157a 100644 --- a/infra/src/feature-env.ts +++ b/infra/src/feature-env.ts @@ -1,5 +1,6 @@ import yargs from 'yargs' import AWS from 'aws-sdk' +import { Kubernetes } from './dsl/kubernetes-runtime' import { Envs } from './environments' import { ExcludedFeatureDeploymentServices, @@ -39,8 +40,8 @@ interface Arguments { const writeToOutput = async (data: string, output?: string) => { if (output) { if (output.startsWith('s3://')) { - const Bucket = output.substring(5).split('/')[0] - const Key = output.substring(5).split(/\/(.+)/)[1] + const Bucket = output.substr(5).split('/')[0] + const Key = output.substr(5).split(/\/(.+)/)[1] const objectParams = { Bucket, Key, @@ -52,16 +53,14 @@ const writeToOutput = async (data: string, output?: string) => { } const s3 = new AWS.S3(config) try { - // TODO: Migrate to AWS SDK v3. - // `ncc` is failing when changing to v3 😓 await s3.putObject(objectParams).promise() - logger.debug(`Successfully uploaded data to ${output}`) + console.log(`Successfully uploaded data to ${output}`) } catch (err) { - logger.debug('Error', err) + console.log('Error', err) } } } else { - logger.debug(data) + console.log(data) } } diff --git a/infra/src/secrets.ts b/infra/src/secrets.ts index e177706161d2..abdd6d2331bb 100644 --- a/infra/src/secrets.ts +++ b/infra/src/secrets.ts @@ -1,4 +1,4 @@ -import { SSM } from '@aws-sdk/client-ssm' +import AWS from 'aws-sdk' import yargs from 'yargs' import { OpsEnv } from './dsl/types/input-types' import { Envs } from './environments' @@ -12,6 +12,8 @@ import { import { renderHelmServices } from './dsl/exports/helm' import { logger } from './common' +const { hideBin } = require('yargs/helpers') + interface GetArguments { key: string } @@ -28,14 +30,13 @@ const config = { region: 'eu-west-1', } -const ssm = new SSM(config) -yargs(process.argv.slice(2)) +const ssm = new AWS.SSM(config) +yargs(hideBin(process.argv)) .command( 'get-all-required-secrets', 'get all required secrets from all charts', { env: { type: 'string', demand: true, choices: OpsEnvNames } }, async (p) => { - logger.info(`Listing all secrets for env:${p.env}`) const services = ( await Promise.all( Object.entries(Charts) @@ -44,7 +45,6 @@ yargs(process.argv.slice(2)) chartName: chartName as ChartName, })) .flatMap(async ({ services, chartName }) => { - logger.info(`Getting secrets for ${chartName} in ${p.env}`) return Object.values( ( await renderHelmServices( @@ -62,23 +62,20 @@ yargs(process.argv.slice(2)) const secrets = services.flatMap((s) => { return Object.values(s.secrets) }) - // Actually log to stdout - console.log([...new Set(secrets)].join('\n')) + logger.debug([...new Set(secrets)].join('\n')) }, ) .command( 'get ', 'get secret', - { - key: { type: 'string', demand: true }, - }, + () => {}, async ({ key }: GetArguments) => { const parameterInput = { Name: key, WithDecryption: true, } - const { Parameter } = await ssm.getParameter(parameterInput) + const { Parameter } = await ssm.getParameter(parameterInput).promise() if (Parameter) { if (Parameter.Value && Parameter.Value.length > 0) { console.log(Parameter.Value) @@ -93,16 +90,15 @@ yargs(process.argv.slice(2)) .command( 'store ', 'store secret', - { - key: { type: 'string', demand: true }, - secret: { type: 'string', demand: true }, - }, + () => {}, async ({ key, secret }: StoreArguments) => { - await ssm.putParameter({ - Name: key, - Value: secret, - Type: 'SecureString', - }) + await ssm + .putParameter({ + Name: key, + Value: secret, + Type: 'SecureString', + }) + .promise() logger.debug('Done!') }, ) @@ -110,15 +106,15 @@ yargs(process.argv.slice(2)) .command( 'delete ', 'delete secrets by prefix', - { - prefix: { type: 'string', demand: true }, - }, + () => {}, async ({ prefix }: DeleteArguments) => { - const { Parameters } = await ssm.describeParameters({ - ParameterFilters: [ - { Key: 'Name', Option: 'BeginsWith', Values: [prefix] }, - ], - }) + const { Parameters } = await ssm + .describeParameters({ + ParameterFilters: [ + { Key: 'Name', Option: 'BeginsWith', Values: [prefix] }, + ], + }) + .promise() if (Parameters && Parameters.length > 0) { logger.debug( `Parameters to destroy: ${Parameters.map(({ Name }) => Name)}`, @@ -126,7 +122,7 @@ yargs(process.argv.slice(2)) await Promise.all( Parameters.map(({ Name }) => Name - ? ssm.deleteParameter({ Name }) + ? ssm.deleteParameter({ Name }).promise() : new Promise((resolve) => resolve(true)), ), ) diff --git a/infra/yarn.lock b/infra/yarn.lock index 5f27b58473e3..c97ff807bf19 100644 --- a/infra/yarn.lock +++ b/infra/yarn.lock @@ -6544,7 +6544,7 @@ __metadata: js-yaml: 4.0.0 lodash: 4.17.21 typescript: 4.6.4 - yargs: 17.2.1 + yargs: 17.7.2 languageName: unknown linkType: soft @@ -10481,18 +10481,18 @@ __metadata: languageName: node linkType: hard -"yargs@npm:17.2.1": - version: 17.2.1 - resolution: "yargs@npm:17.2.1" +"yargs@npm:17.7.2, yargs@npm:^17.6.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: - cliui: ^7.0.2 + cliui: ^8.0.1 escalade: ^3.1.1 get-caller-file: ^2.0.5 require-directory: ^2.1.1 - string-width: ^4.2.0 + string-width: ^4.2.3 y18n: ^5.0.5 - yargs-parser: ^20.2.2 - checksum: 451aac46f82da776f436018feed0244bc0e7b4355f7e397bcb53d34c691b177c0d71db3dda9653760e1bc240254d8b763a252ff918ef9e235a8d202e2909c4eb + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a languageName: node linkType: hard @@ -10511,21 +10511,6 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.6.2": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" - dependencies: - cliui: ^8.0.1 - escalade: ^3.1.1 - get-caller-file: ^2.0.5 - require-directory: ^2.1.1 - string-width: ^4.2.3 - y18n: ^5.0.5 - yargs-parser: ^21.1.1 - checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a - languageName: node - linkType: hard - "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" diff --git a/libs/api-catalogue/elastic/src/lib/elastic.service.ts b/libs/api-catalogue/elastic/src/lib/elastic.service.ts index 7a612917b524..dd1f38c89b8b 100644 --- a/libs/api-catalogue/elastic/src/lib/elastic.service.ts +++ b/libs/api-catalogue/elastic/src/lib/elastic.service.ts @@ -1,37 +1,38 @@ import { Injectable } from '@nestjs/common' import { Client, ApiResponse } from '@elastic/elasticsearch' +import * as AWS from 'aws-sdk' +import AwsConnector from 'aws-elasticsearch-connector' import { environment } from '../environments/environments' import { Service } from '@island.is/api-catalogue/types' import { SearchResponse } from '@island.is/shared/types' import { searchQuery } from './queries/search.model' import { logger } from '@island.is/logging' -import { - createAWSConnection, - awsGetCredentials, -} from '@acuris/aws-es-connection' const { elastic } = environment -type RequestBodyType> = T | string | Buffer +type RequestBodyType> = T | string | Buffer @Injectable() export class ElasticService { - private client?: Client + private client: Client private indexName = 'apicatalogue' + constructor() { + this.client = this.createEsClient() + } + /** * Tries to delete the index. * If the index does not exists it does nothing. */ async deleteIndex(): Promise { logger.info('Deleting index', this.indexName) - const client = await this.getClient() - const { body } = await client.indices.exists({ + const { body } = await this.client.indices.exists({ index: this.indexName, }) if (body) { - await client.indices.delete({ index: this.indexName }) + await this.client.indices.delete({ index: this.indexName }) logger.info(`Index ${this.indexName} deleted`) } else { logger.info('No index to delete', this.indexName) @@ -45,19 +46,18 @@ export class ElasticService { logger.info('Bulk insert', services) if (services.length) { - const bulk: (Service | { index: { _index: string; _id: string } })[] = - services.flatMap((service) => [ - { - index: { - _index: this.indexName, - _id: service.id, - }, + const bulk: Array = [] + services.forEach((service) => { + bulk.push({ + index: { + _index: this.indexName, + _id: service.id, }, - service, - ]) + }) + bulk.push(service) + }) - const client = await this.getClient() - await client.bulk({ + await this.client.bulk({ body: bulk, index: this.indexName, }) @@ -103,8 +103,7 @@ export class ElasticService { query: RequestBody, ) { logger.debug('Searching for', query) - const client = await this.getClient() - return await client.search({ + return await this.client.search({ body: query, index: this.indexName, }) @@ -116,8 +115,7 @@ export class ElasticService { } logger.info('Deleting based on indexes', { ids }) - const client = await this.getClient() - return await client.delete_by_query({ + return await this.client.delete_by_query({ index: this.indexName, body: { query: { @@ -131,8 +129,7 @@ export class ElasticService { async deleteAllExcept(excludeIds: Array) { logger.info('Deleting everything except', { excludeIds }) - const client = await this.getClient() - return await client.delete_by_query({ + return await this.client.delete_by_query({ index: this.indexName, body: { query: { @@ -145,23 +142,14 @@ export class ElasticService { } async ping() { - const client = await this.getClient() - const result = await client.ping().catch((error) => { + const result = await this.client.ping().catch((error) => { logger.error('Error in ping', error) }) logger.info('Got elasticsearch ping response') return result } - private async getClient(): Promise { - if (this.client) { - return this.client - } - this.client = await this.createEsClient() - return this.client - } - - private async createEsClient(): Promise { + private createEsClient(): Client { const hasAWS = 'AWS_WEB_IDENTITY_TOKEN_FILE' in process.env || 'AWS_SECRET_ACCESS_KEY' in process.env @@ -173,7 +161,7 @@ export class ElasticService { } return new Client({ - ...createAWSConnection(await awsGetCredentials()), + ...AwsConnector(AWS.config), node: elastic.node, }) } diff --git a/libs/api-catalogue/elastic/src/lib/queries/search.model.ts b/libs/api-catalogue/elastic/src/lib/queries/search.model.ts index e9513606cbdc..03153533ffba 100644 --- a/libs/api-catalogue/elastic/src/lib/queries/search.model.ts +++ b/libs/api-catalogue/elastic/src/lib/queries/search.model.ts @@ -57,16 +57,7 @@ export const searchQuery = ({ }) } - const result: { - query: { - bool: { - must: Array - } - } - sort: Array - size: number - search_after?: typeof searchAfter - } = { + const result: any = { query: { bool: { must: [ @@ -94,7 +85,7 @@ export const searchQuery = ({ } if (searchAfter?.length) { - result.search_after = searchAfter + result['search_after'] = searchAfter } return result diff --git a/libs/api/domains/application/src/lib/application.model.ts b/libs/api/domains/application/src/lib/application.model.ts index e62e4668e89f..80ccfe2ff36a 100644 --- a/libs/api/domains/application/src/lib/application.model.ts +++ b/libs/api/domains/application/src/lib/application.model.ts @@ -46,6 +46,9 @@ class PendingAction { @Field(() => String, { nullable: true }) content?: string + + @Field(() => String, { nullable: true }) + button?: string } @ObjectType() @@ -76,11 +79,15 @@ class ActionCardMetaData { @Field(() => [ApplicationHistory], { nullable: true }) history?: ApplicationHistory[] + @Field(() => Number, { nullable: true }) draftFinishedSteps?: number @Field(() => Number, { nullable: true }) draftTotalSteps?: number + + @Field(() => String, { nullable: true }) + historyButton?: string } @ObjectType() diff --git a/libs/api/domains/application/src/lib/application.service.ts b/libs/api/domains/application/src/lib/application.service.ts index 6e5182418f3a..fbfbab4cdc08 100644 --- a/libs/api/domains/application/src/lib/application.service.ts +++ b/libs/api/domains/application/src/lib/application.service.ts @@ -200,7 +200,6 @@ export class ApplicationService { async deleteApplication(input: DeleteApplicationInput, auth: Auth) { return this.applicationApiWithAuth(auth).applicationControllerDelete({ id: input.id, - authorization: auth.authorization, }) } diff --git a/libs/api/domains/education/src/index.ts b/libs/api/domains/education/src/index.ts index 1cd8bd060753..f26cea140246 100644 --- a/libs/api/domains/education/src/index.ts +++ b/libs/api/domains/education/src/index.ts @@ -1 +1,2 @@ export * from './lib/education.module' +export * from './lib/educationV2.module' diff --git a/libs/api/domains/education/src/lib/education.utils.ts b/libs/api/domains/education/src/lib/education.utils.ts index 56e42a562df0..2d4ce1743baa 100644 --- a/libs/api/domains/education/src/lib/education.utils.ts +++ b/libs/api/domains/education/src/lib/education.utils.ts @@ -1,3 +1,5 @@ +import getYear from 'date-fns/getYear' + export const getYearInterval = (dates: string[]) => { const sortedDates = dates .map((date) => new Date(date)) @@ -6,3 +8,22 @@ export const getYearInterval = (dates: string[]) => { const endYear = sortedDates.slice(-1)[0].getFullYear() return startYear === endYear ? `${startYear}` : `${startYear} - ${endYear}` } + +export const generateExamDateSpan = ( + firstDate?: Date, + lastDate?: Date, +): string | null => { + if (firstDate && lastDate) { + return `${getYear(firstDate)} - ${getYear(lastDate)}` + } + + if (firstDate) { + return `${getYear(firstDate)}` + } + + if (lastDate) { + return `${getYear(lastDate)}` + } + + return null +} diff --git a/libs/api/domains/education/src/lib/educationMapper.ts b/libs/api/domains/education/src/lib/educationMapper.ts new file mode 100644 index 000000000000..80998ecac3d4 --- /dev/null +++ b/libs/api/domains/education/src/lib/educationMapper.ts @@ -0,0 +1,58 @@ +import { StudentAssessmentsDto } from '@island.is/clients/mms/grade' +import { generateExamDateSpan } from './education.utils' +import { StudentCareer } from './models/studentCareer.model' + +export const mapCareer = ( + data: StudentAssessmentsDto, + isChildOfUser?: boolean, +): StudentCareer | undefined => { + const { firstAssessmentYear, lastAssessmentYear } = + data.assessmentsOverview.assessmentsYearSpan + + return { + nationalId: data?.nationalId, + name: data?.name, + isChildOfUser, + examDateSpan: + generateExamDateSpan(firstAssessmentYear, lastAssessmentYear) ?? '', + examResults: (data?.assessmentsOverview.assessments ?? []).map((a) => ({ + gradeLevel: a.gradeLevel, + coursesExamResults: a.courses.map((c) => ({ + label: c.title, + totalGrade: { + compulsorySchoolGrade: c.gradeHistory.compulsorySchoolGrade, + serialGrade: c.gradeHistory.nationalCoordinationGrade, + }, + competence: { + competencyGrade: c.competencyGrade, + competencyStatus: c.competencyGradeStatus, + }, + gradeCategories: [ + ...c.grades.map((g) => ({ + label: g.title, + grade: { + compulsorySchoolGrade: { + grade: g.compulsorySchoolGrade.grade, + weight: g.compulsorySchoolGrade.weight, + label: g.compulsorySchoolGrade.title, + }, + serialGrade: { + grade: g.nationalCoordinationGrade.grade, + weight: g.nationalCoordinationGrade.weight, + label: g.nationalCoordinationGrade.title, + }, + }, + })), + { + label: c.wordsAndNumberProblemsGrade.title, + text: c.wordsAndNumberProblemsGrade.grade, + }, + { + label: c.improvement.title, + text: c.improvement.grade, + }, + ], + })), + })), + } +} diff --git a/libs/api/domains/education/src/lib/educationV2.module.ts b/libs/api/domains/education/src/lib/educationV2.module.ts new file mode 100644 index 000000000000..a47d0cbd0d37 --- /dev/null +++ b/libs/api/domains/education/src/lib/educationV2.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common' +import { GradesClientModule } from '@island.is/clients/mms/grade' +import { GradesResolver } from './graphql/grades.resolver' +import { EducationServiceV2 } from './educationV2.service' + +@Module({ + imports: [GradesClientModule], + providers: [GradesResolver, EducationServiceV2], +}) +export class EducationV2Module {} diff --git a/libs/api/domains/education/src/lib/educationV2.service.ts b/libs/api/domains/education/src/lib/educationV2.service.ts new file mode 100644 index 000000000000..ada2e43ed00d --- /dev/null +++ b/libs/api/domains/education/src/lib/educationV2.service.ts @@ -0,0 +1,40 @@ +import { Injectable, Inject } from '@nestjs/common' +import { GradeClientService } from '@island.is/clients/mms/grade' +import { User } from '@island.is/auth-nest-tools' +import { FamilyCompulsorySchoolCareer } from './models/familyCareer.model' +import { mapCareer } from './educationMapper' +import { isDefined } from '@island.is/shared/utils' +import { LOGGER_PROVIDER, type Logger } from '@island.is/logging' + +@Injectable() +export class EducationServiceV2 { + constructor( + private readonly gradeService: GradeClientService, + @Inject(LOGGER_PROVIDER) private readonly logger: Logger, + ) {} + + async familyCareers( + user: User, + ): Promise { + const data = await this.gradeService.getUserFamilyStudentAssessments(user) + + if (!data) { + return null + } + + const userData = data.find((d) => d.nationalId === user.nationalId) + const familyData = data.filter((d) => d.nationalId !== user.nationalId) + + if (!userData?.nationalId || !userData?.name) { + return null + } + + return { + userCareer: mapCareer(userData, false), + familyMemberCareers: familyData + .filter((d) => d.nationalId !== user.nationalId) + .map((data) => mapCareer(data, true)) + .filter(isDefined), + } + } +} diff --git a/libs/api/domains/education/src/lib/graphql/grades.resolver.ts b/libs/api/domains/education/src/lib/graphql/grades.resolver.ts new file mode 100644 index 000000000000..4722039fea98 --- /dev/null +++ b/libs/api/domains/education/src/lib/graphql/grades.resolver.ts @@ -0,0 +1,30 @@ +import { ApiScope } from '@island.is/auth/scopes' +import { Query, Resolver } from '@nestjs/graphql' +import { UseGuards } from '@nestjs/common' + +import type { User } from '@island.is/auth-nest-tools' +import { + IdsUserGuard, + ScopesGuard, + CurrentUser, + Scopes, +} from '@island.is/auth-nest-tools' +import { Audit } from '@island.is/nest/audit' +import { EducationServiceV2 } from '../educationV2.service' +import { FamilyCompulsorySchoolCareer } from '../models/familyCareer.model' + +@UseGuards(IdsUserGuard, ScopesGuard) +@Audit({ namespace: '@island.is/api/education/grade' }) +@Resolver() +export class GradesResolver { + constructor(private readonly educationService: EducationServiceV2) {} + + @Query(() => FamilyCompulsorySchoolCareer) + @Scopes(ApiScope.education) + @Audit() + userFamilyExamResults( + @CurrentUser() user: User, + ): Promise { + return this.educationService.familyCareers(user) + } +} diff --git a/libs/api/domains/education/src/lib/models/competence.model.ts b/libs/api/domains/education/src/lib/models/competence.model.ts new file mode 100644 index 000000000000..d486cedb9510 --- /dev/null +++ b/libs/api/domains/education/src/lib/models/competence.model.ts @@ -0,0 +1,10 @@ +import { Field, ObjectType } from '@nestjs/graphql' + +@ObjectType('EducationCompulsorySchoolCourseCompetence') +export class CourseCompetence { + @Field() + competencyGrade!: string + + @Field({ nullable: true }) + competenceStatus?: string +} diff --git a/libs/api/domains/education/src/lib/models/course.model.ts b/libs/api/domains/education/src/lib/models/course.model.ts new file mode 100644 index 000000000000..3094802acc99 --- /dev/null +++ b/libs/api/domains/education/src/lib/models/course.model.ts @@ -0,0 +1,19 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { CourseCompetence } from './competence.model' +import { GradeCategory } from './gradeCategory.model' +import { Grade } from './grade.model' + +@ObjectType('EducationCompulsorySchoolCourse') +export class Course { + @Field() + label!: string + + @Field(() => Grade, { nullable: true }) + totalGrade?: Grade + + @Field(() => CourseCompetence) + competence!: CourseCompetence + + @Field(() => [GradeCategory], { nullable: true }) + gradeCategories!: Array +} diff --git a/libs/api/domains/education/src/lib/models/familyCareer.model.ts b/libs/api/domains/education/src/lib/models/familyCareer.model.ts new file mode 100644 index 000000000000..9bda063bf814 --- /dev/null +++ b/libs/api/domains/education/src/lib/models/familyCareer.model.ts @@ -0,0 +1,11 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { StudentCareer } from './studentCareer.model' + +@ObjectType('EducationUserFamilyCompulsorySchoolCareer') +export class FamilyCompulsorySchoolCareer { + @Field(() => StudentCareer, { nullable: true }) + userCareer?: StudentCareer + + @Field(() => [StudentCareer], { nullable: true }) + familyMemberCareers?: Array +} diff --git a/libs/api/domains/education/src/lib/models/grade.model.ts b/libs/api/domains/education/src/lib/models/grade.model.ts new file mode 100644 index 000000000000..0dbb2cd1951d --- /dev/null +++ b/libs/api/domains/education/src/lib/models/grade.model.ts @@ -0,0 +1,11 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { GradeDetail } from './gradeDetail.model' + +@ObjectType('EducationCompulsorySchoolGrade') +export class Grade { + @Field(() => GradeDetail) + compulsorySchoolGrade!: GradeDetail + + @Field(() => GradeDetail, { description: 'National standardised test grade' }) + serialGrade!: GradeDetail +} diff --git a/libs/api/domains/education/src/lib/models/gradeCategory.model.ts b/libs/api/domains/education/src/lib/models/gradeCategory.model.ts new file mode 100644 index 000000000000..2597be8f1882 --- /dev/null +++ b/libs/api/domains/education/src/lib/models/gradeCategory.model.ts @@ -0,0 +1,17 @@ +import { Field, InterfaceType } from '@nestjs/graphql' +import { GradeCategoryText } from './gradeCategoryText.model' +import { GradeCategoryWeighted } from './gradeCategoryWeighted.model' + +@InterfaceType('EducationCompulsorySchoolGradeCategory', { + resolveType(res) { + if (res.text) { + return GradeCategoryText + } + + return GradeCategoryWeighted + }, +}) +export abstract class GradeCategory { + @Field() + label!: string +} diff --git a/libs/api/domains/education/src/lib/models/gradeCategoryText.model.ts b/libs/api/domains/education/src/lib/models/gradeCategoryText.model.ts new file mode 100644 index 000000000000..c120ae8fc17f --- /dev/null +++ b/libs/api/domains/education/src/lib/models/gradeCategoryText.model.ts @@ -0,0 +1,13 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { GradeCategory } from './gradeCategory.model' + +@ObjectType('EducationCompulsorySchoolGradeCategoryText', { + implements: () => GradeCategory, +}) +export class GradeCategoryText implements GradeCategory { + @Field() + label!: string + + @Field() + text!: string +} diff --git a/libs/api/domains/education/src/lib/models/gradeCategoryWeighted.model.ts b/libs/api/domains/education/src/lib/models/gradeCategoryWeighted.model.ts new file mode 100644 index 000000000000..4bd955b95066 --- /dev/null +++ b/libs/api/domains/education/src/lib/models/gradeCategoryWeighted.model.ts @@ -0,0 +1,14 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { GradeCategory } from './gradeCategory.model' +import { Grade } from './grade.model' + +@ObjectType('EducationCompulsorySchoolGradeCategoryWeighted', { + implements: () => GradeCategory, +}) +export class GradeCategoryWeighted implements GradeCategory { + @Field() + label!: string + + @Field(() => Grade) + grade!: Grade +} diff --git a/libs/api/domains/education/src/lib/models/gradeDetail.model.ts b/libs/api/domains/education/src/lib/models/gradeDetail.model.ts new file mode 100644 index 000000000000..a90dbc4e0e0f --- /dev/null +++ b/libs/api/domains/education/src/lib/models/gradeDetail.model.ts @@ -0,0 +1,13 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql' + +@ObjectType('EducationCompulsorySchoolGradeDetail') +export class GradeDetail { + @Field() + grade!: string + + @Field(() => Int, { nullable: true }) + weight?: number + + @Field({ nullable: true }) + label?: string +} diff --git a/libs/api/domains/education/src/lib/models/gradeLevelExamResults.model.ts b/libs/api/domains/education/src/lib/models/gradeLevelExamResults.model.ts new file mode 100644 index 000000000000..534cc36ed1c1 --- /dev/null +++ b/libs/api/domains/education/src/lib/models/gradeLevelExamResults.model.ts @@ -0,0 +1,11 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql' +import { Course } from './course.model' + +@ObjectType('EducationCompulsorySchoolGradeLevelExamResults') +export class CompulsorySchoolGradeLevelExamResults { + @Field(() => Int) + gradeLevel!: number + + @Field(() => [Course], { nullable: true }) + coursesExamResults?: Array +} diff --git a/libs/api/domains/education/src/lib/models/studentCareer.model.ts b/libs/api/domains/education/src/lib/models/studentCareer.model.ts new file mode 100644 index 000000000000..cd01a2fe12f6 --- /dev/null +++ b/libs/api/domains/education/src/lib/models/studentCareer.model.ts @@ -0,0 +1,20 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { CompulsorySchoolGradeLevelExamResults } from './gradeLevelExamResults.model' + +@ObjectType('EducationCompulsorySchoolStudentCareer') +export class StudentCareer { + @Field() + nationalId!: string + + @Field() + name!: string + + @Field({ nullable: true }) + isChildOfUser?: boolean + + @Field({ nullable: true }) + examDateSpan?: string + + @Field(() => [CompulsorySchoolGradeLevelExamResults], { nullable: true }) + examResults?: Array +} diff --git a/libs/api/domains/education/src/lib/s3.service.ts b/libs/api/domains/education/src/lib/s3.service.ts index 737789e269ff..f49a723e6c1b 100644 --- a/libs/api/domains/education/src/lib/s3.service.ts +++ b/libs/api/domains/education/src/lib/s3.service.ts @@ -1,64 +1,64 @@ -import { Injectable, Inject } from '@nestjs/common' -import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' -import { Upload } from '@aws-sdk/lib-storage' -import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import { Injectable } from '@nestjs/common' +import { Inject } from '@nestjs/common' +import { S3 } from 'aws-sdk' import { Response } from 'node-fetch' -import { Readable } from 'stream' +import stream from 'stream' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' -import { CompleteMultipartUploadOutput } from '@aws-sdk/client-s3' type S3Location = { fileName: string bucket: string } +type FileUpload = { + passThrough: stream.PassThrough + promise: Promise +} + @Injectable() export class S3Service { - private readonly s3Client: S3Client + private readonly s3: S3 constructor( @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) { - this.s3Client = new S3Client({ apiVersion: '2006-03-01' }) + this.s3 = new S3({ apiVersion: '2006-03-01' }) } - private async uploadFromStream( + private uploadFromStream( fileResponse: Response, { fileName, bucket }: S3Location, - ): Promise { - const upload = new Upload({ - client: this.s3Client, - params: { + ): FileUpload { + const passThrough = new stream.PassThrough() + const promise = this.s3 + .upload({ Bucket: bucket, Key: fileName, ContentType: 'application/pdf', - Body: fileResponse.body as Readable, - }, - }) - - this.logger.debug(`Uploading to bucket`, { - fileName, - bucket, - }) - return await upload.done() + Body: passThrough, + }) + .promise() + return { passThrough, promise } } async uploadFileFromStream( stream: Response, s3Location: S3Location, ): Promise { - return this.uploadFromStream(stream, s3Location) - .then(async () => { + const { passThrough, promise } = this.uploadFromStream(stream, s3Location) + + stream.body.pipe(passThrough) + + return promise + .then((result) => { const oneMinutePlus = 65 // leave extra 5 seconds for network delay - const command = new GetObjectCommand({ + return this.s3.getSignedUrlPromise('getObject', { Bucket: s3Location.bucket, - Key: s3Location.fileName, - }) - return getSignedUrl(this.s3Client, command, { - expiresIn: oneMinutePlus, + Key: result.Key, + Expires: oneMinutePlus, }) }) .catch((err) => { diff --git a/libs/api/domains/endorsement-system/src/lib/dto/exportEndorsementList.input.ts b/libs/api/domains/endorsement-system/src/lib/dto/exportEndorsementList.input.ts new file mode 100644 index 000000000000..786b8f151a61 --- /dev/null +++ b/libs/api/domains/endorsement-system/src/lib/dto/exportEndorsementList.input.ts @@ -0,0 +1,10 @@ +import { Field, InputType } from '@nestjs/graphql' + +@InputType() +export class ExportEndorsementListInput { + @Field() + listId!: string + + @Field() + fileType!: string +} diff --git a/libs/api/domains/endorsement-system/src/lib/dto/exportUrl.response.ts b/libs/api/domains/endorsement-system/src/lib/dto/exportUrl.response.ts new file mode 100644 index 000000000000..2643444b05b3 --- /dev/null +++ b/libs/api/domains/endorsement-system/src/lib/dto/exportUrl.response.ts @@ -0,0 +1,7 @@ +import { Field, ObjectType } from '@nestjs/graphql' + +@ObjectType() +export class ExportUrlResponse { + @Field(() => String) + url!: string +} diff --git a/libs/api/domains/endorsement-system/src/lib/dto/paginatedEndorsement.response.ts b/libs/api/domains/endorsement-system/src/lib/dto/paginatedEndorsement.response.ts index 05506e9818f3..29331425822a 100644 --- a/libs/api/domains/endorsement-system/src/lib/dto/paginatedEndorsement.response.ts +++ b/libs/api/domains/endorsement-system/src/lib/dto/paginatedEndorsement.response.ts @@ -1,13 +1,16 @@ import { Field, ObjectType } from '@nestjs/graphql' import { PageInfoResponse } from './pageInfo.response' import { Endorsement } from '../models/endorsement.model' +import { CacheField } from '@island.is/nest/graphql' @ObjectType() export class PaginatedEndorsementResponse { @Field() totalCount!: number - @Field(() => [Endorsement]) + + @CacheField(() => [Endorsement]) data!: Endorsement[] - @Field(() => PageInfoResponse) + + @CacheField(() => PageInfoResponse) pageInfo!: PageInfoResponse } diff --git a/libs/api/domains/endorsement-system/src/lib/dto/paginatedEndorsementList.response.ts b/libs/api/domains/endorsement-system/src/lib/dto/paginatedEndorsementList.response.ts index 8dd52de6bcce..7962e4b88174 100644 --- a/libs/api/domains/endorsement-system/src/lib/dto/paginatedEndorsementList.response.ts +++ b/libs/api/domains/endorsement-system/src/lib/dto/paginatedEndorsementList.response.ts @@ -1,13 +1,16 @@ import { Field, ObjectType } from '@nestjs/graphql' import { PageInfoResponse } from './pageInfo.response' import { EndorsementList } from '../models/endorsementList.model' +import { CacheField } from '@island.is/nest/graphql' @ObjectType() export class PaginatedEndorsementListResponse { @Field() totalCount!: number - @Field(() => [EndorsementList]) + + @CacheField(() => [EndorsementList]) data!: EndorsementList[] - @Field(() => PageInfoResponse) + + @CacheField(() => PageInfoResponse) pageInfo!: PageInfoResponse } diff --git a/libs/api/domains/endorsement-system/src/lib/endorsementSystem.resolver.ts b/libs/api/domains/endorsement-system/src/lib/endorsementSystem.resolver.ts index acbbcd33b5b0..293946450d93 100644 --- a/libs/api/domains/endorsement-system/src/lib/endorsementSystem.resolver.ts +++ b/libs/api/domains/endorsement-system/src/lib/endorsementSystem.resolver.ts @@ -31,6 +31,9 @@ import { sendPdfEmailInput } from './dto/sendPdfEmail.input' import { CacheControl, CacheControlOptions } from '@island.is/nest/graphql' import { CACHE_CONTROL_MAX_AGE } from '@island.is/shared/constants' +import { ExportUrlResponse } from './dto/exportUrl.response' +import { ExportEndorsementListInput } from './dto/exportEndorsementList.input' + const defaultCache: CacheControlOptions = { maxAge: CACHE_CONTROL_MAX_AGE } @UseGuards(IdsUserGuard) @@ -254,4 +257,15 @@ export class EndorsementSystemResolver { user, ) } + + @Mutation(() => ExportUrlResponse) + async endorsementSystemExportList( + @Args('input') input: ExportEndorsementListInput, + @CurrentUser() user: User, + ): Promise { + return await this.endorsementSystemService.endorsementListControllerExportList( + input, + user, + ) + } } diff --git a/libs/api/domains/endorsement-system/src/lib/endorsementSystem.service.ts b/libs/api/domains/endorsement-system/src/lib/endorsementSystem.service.ts index 2258d9046bd9..eab84ca67cb8 100644 --- a/libs/api/domains/endorsement-system/src/lib/endorsementSystem.service.ts +++ b/libs/api/domains/endorsement-system/src/lib/endorsementSystem.service.ts @@ -20,9 +20,12 @@ import { EndorsementListControllerLockRequest, EndorsementListControllerUnlockRequest, EndorsementListControllerEmailEndorsementsPDFRequest, + EndorsementListControllerExportEndorsementListFileTypeEnum, } from '../../gen/fetch' import { Auth, AuthMiddleware } from '@island.is/auth-nest-tools' import type { Logger } from '@island.is/logging' +import { ExportUrlResponse } from './dto/exportUrl.response' +import { ExportEndorsementListInput } from './dto/exportEndorsementList.input' @Injectable() export class EndorsementSystemService { @@ -219,4 +222,17 @@ export class EndorsementSystemService { .endorsementListControllerEmailEndorsementsPDF(endorsementList) .catch(this.handleError.bind(this)) } + + async endorsementListControllerExportList( + input: ExportEndorsementListInput, + auth: Auth, + ): Promise { + return await this.endorsementListApiWithAuth(auth) + .endorsementListControllerExportEndorsementList({ + listId: input.listId, + fileType: + input.fileType as EndorsementListControllerExportEndorsementListFileTypeEnum, + }) + .catch(this.handleError.bind(this)) + } } diff --git a/libs/api/domains/endorsement-system/src/lib/models/endorsement.model.ts b/libs/api/domains/endorsement-system/src/lib/models/endorsement.model.ts index 7381d2c45959..ab0c8b1e022e 100644 --- a/libs/api/domains/endorsement-system/src/lib/models/endorsement.model.ts +++ b/libs/api/domains/endorsement-system/src/lib/models/endorsement.model.ts @@ -1,6 +1,7 @@ import { Field, ObjectType, ID } from '@nestjs/graphql' import { EndorsementListOpen } from './endorsementListOpen.model' import { EndorsementMetadata } from './endorsementMetadata.model' +import { CacheField } from '@island.is/nest/graphql' @ObjectType() export class Endorsement { @@ -13,10 +14,10 @@ export class Endorsement { @Field() endorsementListId!: string - @Field(() => EndorsementListOpen, { nullable: true }) + @CacheField(() => EndorsementListOpen, { nullable: true }) endorsementList?: EndorsementListOpen - @Field(() => EndorsementMetadata) + @CacheField(() => EndorsementMetadata) meta!: EndorsementMetadata @Field() diff --git a/libs/api/domains/endorsement-system/src/lib/models/endorsementList.model.ts b/libs/api/domains/endorsement-system/src/lib/models/endorsementList.model.ts index 2c03818d359a..dc5e2c55c16d 100644 --- a/libs/api/domains/endorsement-system/src/lib/models/endorsementList.model.ts +++ b/libs/api/domains/endorsement-system/src/lib/models/endorsementList.model.ts @@ -1,6 +1,7 @@ import { Field, ObjectType, ID } from '@nestjs/graphql' import graphqlTypeJson from 'graphql-type-json' import { EndorsementListTagsEnum } from '../enums/endorsementListTags.enum' +import { CacheField } from '@island.is/nest/graphql' @ObjectType() export class EndorsementList { @@ -22,10 +23,10 @@ export class EndorsementList { @Field() adminLock!: boolean - @Field(() => [EndorsementListTagsEnum]) + @CacheField(() => [EndorsementListTagsEnum]) tags!: EndorsementListTagsEnum[] - @Field(() => graphqlTypeJson) + @CacheField(() => graphqlTypeJson) meta!: object @Field() diff --git a/libs/api/domains/endorsement-system/src/lib/models/endorsementListOpen.model.ts b/libs/api/domains/endorsement-system/src/lib/models/endorsementListOpen.model.ts index 17b7610d1c66..1a84dfda3713 100644 --- a/libs/api/domains/endorsement-system/src/lib/models/endorsementListOpen.model.ts +++ b/libs/api/domains/endorsement-system/src/lib/models/endorsementListOpen.model.ts @@ -1,5 +1,6 @@ import { Field, ObjectType, ID } from '@nestjs/graphql' import { EndorsementListOpenTagsEnum } from '../enums/endorsementListOpenTagsEnum' +import { CacheField } from '@island.is/nest/graphql' @ObjectType() export class EndorsementListOpen { @@ -12,7 +13,7 @@ export class EndorsementListOpen { @Field(() => String, { nullable: true }) description!: string | null - @Field(() => [EndorsementListOpenTagsEnum], { nullable: true }) + @CacheField(() => [EndorsementListOpenTagsEnum], { nullable: true }) tags?: EndorsementListOpenTagsEnum[] @Field(() => Date) diff --git a/libs/api/domains/file-upload/src/lib/file-upload.resolver.ts b/libs/api/domains/file-upload/src/lib/file-upload.resolver.ts index 8be7a55416e8..fa2d482c50f5 100644 --- a/libs/api/domains/file-upload/src/lib/file-upload.resolver.ts +++ b/libs/api/domains/file-upload/src/lib/file-upload.resolver.ts @@ -1,4 +1,5 @@ import { Args, Resolver, Mutation } from '@nestjs/graphql' +import { S3 } from 'aws-sdk' import { PresignedPost } from './presignedPost.model' import { FileStorageService } from '@island.is/file-storage' @@ -7,9 +8,9 @@ export class FileUploadResolver { constructor(private fileStorageService: FileStorageService) {} @Mutation(() => PresignedPost) - async createUploadUrl( + createUploadUrl( @Args('filename') filename: string, - ): Promise { + ): Promise { return this.fileStorageService.generatePresignedPost(filename) } } diff --git a/libs/api/domains/occupational-licenses/src/lib/models/occupationalLicense.model.ts b/libs/api/domains/occupational-licenses/src/lib/models/occupationalLicense.model.ts index 8f98b15c0258..a67fa3b79992 100644 --- a/libs/api/domains/occupational-licenses/src/lib/models/occupationalLicense.model.ts +++ b/libs/api/domains/occupational-licenses/src/lib/models/occupationalLicense.model.ts @@ -46,6 +46,7 @@ export abstract class OccupationalLicense { @Field(() => ID) id!: string | number + @Field(() => String) type!: string diff --git a/libs/api/domains/occupational-licenses/src/lib/occupationalLicenses.service.ts b/libs/api/domains/occupational-licenses/src/lib/occupationalLicenses.service.ts index 8f08316cc97c..2eef0fad1cf6 100644 --- a/libs/api/domains/occupational-licenses/src/lib/occupationalLicenses.service.ts +++ b/libs/api/domains/occupational-licenses/src/lib/occupationalLicenses.service.ts @@ -98,6 +98,7 @@ export class OccupationalLicensesService { .map((license) => ({ institution: OccupationalLicenseType.HEALTH, id: license.id.toString(), + licenseNumber: license.licenseNumber, legalEntityId: license.legalEntityId, holderName: license.licenseHolderName, profession: license.profession, diff --git a/libs/api/domains/social-insurance/src/lib/dtos/temporaryCalculation.input.ts b/libs/api/domains/social-insurance/src/lib/dtos/temporaryCalculation.input.ts new file mode 100644 index 000000000000..02e191306d34 --- /dev/null +++ b/libs/api/domains/social-insurance/src/lib/dtos/temporaryCalculation.input.ts @@ -0,0 +1,70 @@ +import { InputType, Field, Int } from '@nestjs/graphql' + +@InputType('SocialInsuranceIncomeType') +class IncomeType { + @Field(() => Int, { nullable: true }) + incomeTypeNumber?: number + + @Field(() => String, { nullable: true }) + incomeTypeCode?: string + + @Field(() => String, { nullable: true }) + incomeTypeName?: string + + @Field(() => String, { nullable: true }) + currencyCode?: string + + @Field(() => Int, { nullable: true }) + incomeCategoryNumber?: number + + @Field(() => String, { nullable: true }) + incomeCategoryCode?: string + + @Field(() => String, { nullable: true }) + incomeCategoryName?: string + + @Field(() => Int, { nullable: true }) + amountJan?: number + + @Field(() => Int, { nullable: true }) + amountFeb?: number + + @Field(() => Int, { nullable: true }) + amountMar?: number + + @Field(() => Int, { nullable: true }) + amountApr?: number + + @Field(() => Int, { nullable: true }) + amountMay?: number + + @Field(() => Int, { nullable: true }) + amountJun?: number + + @Field(() => Int, { nullable: true }) + amountJul?: number + + @Field(() => Int, { nullable: true }) + amountAug?: number + + @Field(() => Int, { nullable: true }) + amountSep?: number + + @Field(() => Int, { nullable: true }) + amountOct?: number + + @Field(() => Int, { nullable: true }) + amountNov?: number + + @Field(() => Int, { nullable: true }) + amountDec?: number +} + +@InputType('SocialInsuranceTemporaryCalculationInput') +export class TemporaryCalculationInput { + @Field(() => Int) + incomeYear!: number + + @Field(() => [IncomeType], { nullable: true }) + incomeTypes?: Array +} diff --git a/libs/api/domains/social-insurance/src/lib/models/income/category.model.ts b/libs/api/domains/social-insurance/src/lib/models/income/category.model.ts new file mode 100644 index 000000000000..a7c82ef08629 --- /dev/null +++ b/libs/api/domains/social-insurance/src/lib/models/income/category.model.ts @@ -0,0 +1,13 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql' + +@ObjectType('SocialInsuranceIncomePlanIncomeCategory') +export class IncomeCategory { + @Field() + name!: string + + @Field(() => Int) + annualSum!: number + + @Field({ nullable: true }) + currency?: string +} diff --git a/libs/api/domains/social-insurance/src/lib/models/income/incomePlan.model.ts b/libs/api/domains/social-insurance/src/lib/models/income/incomePlan.model.ts new file mode 100644 index 000000000000..176cd4906713 --- /dev/null +++ b/libs/api/domains/social-insurance/src/lib/models/income/incomePlan.model.ts @@ -0,0 +1,26 @@ +import { + Field, + GraphQLISODateTime, + ObjectType, + registerEnumType, +} from '@nestjs/graphql' +import { IncomeCategory } from './category.model' +import { IncomePlanStatus } from '../../socialInsurance.type' +import { IncomePlanEligbility } from './incomePlanEligibility.model' + +registerEnumType(IncomePlanStatus, { name: 'SocialInsuranceIncomePlanStatus' }) + +@ObjectType('SocialInsuranceIncomePlan') +export class IncomePlan { + @Field(() => GraphQLISODateTime) + registrationDate!: Date + + @Field(() => IncomePlanStatus) + status!: IncomePlanStatus + + @Field(() => IncomePlanEligbility, { nullable: true }) + isEligibleForChange?: IncomePlanEligbility + + @Field(() => [IncomeCategory]) + incomeCategories!: Array +} diff --git a/libs/api/domains/social-insurance/src/lib/models/income/incomePlanEligibility.model.ts b/libs/api/domains/social-insurance/src/lib/models/income/incomePlanEligibility.model.ts new file mode 100644 index 000000000000..2fa9b36d9d8e --- /dev/null +++ b/libs/api/domains/social-insurance/src/lib/models/income/incomePlanEligibility.model.ts @@ -0,0 +1,10 @@ +import { Field, ObjectType } from '@nestjs/graphql' + +@ObjectType('SocialInsuranceIncomePlanEligbility') +export class IncomePlanEligbility { + @Field({ nullable: true }) + isEligible?: boolean + + @Field({ nullable: true }) + reason?: string +} diff --git a/libs/api/domains/social-insurance/src/lib/models/payment.model.ts b/libs/api/domains/social-insurance/src/lib/models/payments/payment.model.ts similarity index 100% rename from libs/api/domains/social-insurance/src/lib/models/payment.model.ts rename to libs/api/domains/social-insurance/src/lib/models/payments/payment.model.ts diff --git a/libs/api/domains/social-insurance/src/lib/models/paymentGroup.model.ts b/libs/api/domains/social-insurance/src/lib/models/payments/paymentGroup.model.ts similarity index 100% rename from libs/api/domains/social-insurance/src/lib/models/paymentGroup.model.ts rename to libs/api/domains/social-insurance/src/lib/models/payments/paymentGroup.model.ts diff --git a/libs/api/domains/social-insurance/src/lib/models/paymentGroupType.model.ts b/libs/api/domains/social-insurance/src/lib/models/payments/paymentGroupType.model.ts similarity index 100% rename from libs/api/domains/social-insurance/src/lib/models/paymentGroupType.model.ts rename to libs/api/domains/social-insurance/src/lib/models/payments/paymentGroupType.model.ts diff --git a/libs/api/domains/social-insurance/src/lib/models/paymentMonth.model.ts b/libs/api/domains/social-insurance/src/lib/models/payments/paymentMonth.model.ts similarity index 100% rename from libs/api/domains/social-insurance/src/lib/models/paymentMonth.model.ts rename to libs/api/domains/social-insurance/src/lib/models/payments/paymentMonth.model.ts diff --git a/libs/api/domains/social-insurance/src/lib/models/paymentPlan.model.ts b/libs/api/domains/social-insurance/src/lib/models/payments/paymentPlan.model.ts similarity index 100% rename from libs/api/domains/social-insurance/src/lib/models/paymentPlan.model.ts rename to libs/api/domains/social-insurance/src/lib/models/payments/paymentPlan.model.ts diff --git a/libs/api/domains/social-insurance/src/lib/models/payments.model.ts b/libs/api/domains/social-insurance/src/lib/models/payments/payments.model.ts similarity index 100% rename from libs/api/domains/social-insurance/src/lib/models/payments.model.ts rename to libs/api/domains/social-insurance/src/lib/models/payments/payments.model.ts diff --git a/libs/api/domains/social-insurance/src/lib/models/pensionCalculation.model.ts b/libs/api/domains/social-insurance/src/lib/models/pension/pensionCalculation.model.ts similarity index 100% rename from libs/api/domains/social-insurance/src/lib/models/pensionCalculation.model.ts rename to libs/api/domains/social-insurance/src/lib/models/pension/pensionCalculation.model.ts diff --git a/libs/api/domains/social-insurance/src/lib/models/temporaryCalculation.model.ts b/libs/api/domains/social-insurance/src/lib/models/temporaryCalculation.model.ts new file mode 100644 index 000000000000..c8e55c1b1d12 --- /dev/null +++ b/libs/api/domains/social-insurance/src/lib/models/temporaryCalculation.model.ts @@ -0,0 +1,55 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql' + +@ObjectType('SocialInsuranceTemporaryCalculationRow') +class TemporaryCalculationRow { + @Field(() => String, { nullable: true }) + name?: string + + @Field(() => Int, { nullable: true }) + total?: number + + @Field(() => [TemporaryCalculationMonth], { nullable: true }) + months?: Array +} + +@ObjectType('SocialInsuranceTemporaryCalculationMonth') +class TemporaryCalculationMonth { + @Field(() => Int) + month!: number + + @Field(() => Int, { nullable: true }) + amount?: number +} + +@ObjectType('SocialInsuranceTemporaryCalculationGroup') +class TemporaryCalculationGroup { + @Field(() => String, { nullable: true }) + group?: string + + @Field(() => Int, { nullable: true }) + groupId?: number + + @Field(() => Int, { nullable: true }) + total?: number + + @Field(() => [TemporaryCalculationMonth], { nullable: true }) + monthTotals?: Array + + @Field(() => [TemporaryCalculationRow], { nullable: true }) + rows?: Array +} + +@ObjectType('SocialInsuranceTemporaryCalculation') +export class TemporaryCalculation { + @Field(() => Int, { nullable: true }) + totalPayment?: number + + @Field(() => Int, { nullable: true }) + subtracted?: number + + @Field(() => Int, { nullable: true }) + paidOut?: number + + @Field(() => [TemporaryCalculationGroup], { nullable: true }) + groups?: Array +} diff --git a/libs/api/domains/social-insurance/src/lib/resolvers/incomePlan.resolver.ts b/libs/api/domains/social-insurance/src/lib/resolvers/incomePlan.resolver.ts new file mode 100644 index 000000000000..43e5e106720f --- /dev/null +++ b/libs/api/domains/social-insurance/src/lib/resolvers/incomePlan.resolver.ts @@ -0,0 +1,46 @@ +import { Audit } from '@island.is/nest/audit' +import { Query, ResolveField, Resolver } from '@nestjs/graphql' +import { + CurrentUser, + IdsUserGuard, + Scopes, + ScopesGuard, + type User, +} from '@island.is/auth-nest-tools' +import { Inject, UseGuards } from '@nestjs/common' +import { SocialInsuranceService } from '../socialInsurance.service' +import { + FeatureFlag, + FeatureFlagGuard, + Features, +} from '@island.is/nest/feature-flags' +import { IncomePlan } from '../models/income/incomePlan.model' +import { ApiScope } from '@island.is/auth/scopes' +import { IncomePlanEligbility } from '../models/income/incomePlanEligibility.model' +import { LOGGER_PROVIDER, type Logger } from '@island.is/logging' + +@Resolver(() => IncomePlan) +@UseGuards(IdsUserGuard, ScopesGuard, FeatureFlagGuard) +@Audit({ namespace: '@island.is/api/social-insurance' }) +export class IncomePlanResolver { + constructor( + @Inject(LOGGER_PROVIDER) private readonly logger: Logger, + private readonly service: SocialInsuranceService, + ) {} + + @Query(() => IncomePlan, { + name: 'socialInsuranceIncomePlan', + nullable: true, + }) + @FeatureFlag(Features.servicePortalSocialInsuranceIncomePlanPageEnabled) + @Scopes(ApiScope.internal) + @Audit() + async incomePlan(@CurrentUser() user: User) { + return this.service.getIncomePlan(user) + } + + @ResolveField('isEligibleForChange', () => IncomePlanEligbility) + async resolveField(@CurrentUser() user: User): Promise { + return this.service.getIncomePlanChangeEligibility(user) + } +} diff --git a/libs/api/domains/social-insurance/src/lib/resolvers/index.ts b/libs/api/domains/social-insurance/src/lib/resolvers/index.ts new file mode 100644 index 000000000000..395d8a83cfba --- /dev/null +++ b/libs/api/domains/social-insurance/src/lib/resolvers/index.ts @@ -0,0 +1,3 @@ +export { IncomePlanResolver } from './incomePlan.resolver' +export { PaymentPlanResolver } from './paymentPlan.resolver' +export { PensionResolver } from './pension.resolver' diff --git a/libs/api/domains/social-insurance/src/lib/socialInsurance.resolver.ts b/libs/api/domains/social-insurance/src/lib/resolvers/paymentPlan.resolver.ts similarity index 66% rename from libs/api/domains/social-insurance/src/lib/socialInsurance.resolver.ts rename to libs/api/domains/social-insurance/src/lib/resolvers/paymentPlan.resolver.ts index afa323100377..dd41459cf2e1 100644 --- a/libs/api/domains/social-insurance/src/lib/socialInsurance.resolver.ts +++ b/libs/api/domains/social-insurance/src/lib/resolvers/paymentPlan.resolver.ts @@ -7,24 +7,23 @@ import { Scopes, CurrentUser, type User, - BypassAuth, } from '@island.is/auth-nest-tools' import { UseGuards } from '@nestjs/common' -import { SocialInsuranceService } from './socialInsurance.service' -import { PaymentPlan } from './models/paymentPlan.model' +import { SocialInsuranceService } from '../socialInsurance.service' +import { PaymentPlan } from '../models/payments/paymentPlan.model' import { FeatureFlagGuard, FeatureFlag, Features, } from '@island.is/nest/feature-flags' -import { PensionCalculationInput } from './dtos/pensionCalculation.input' -import { PensionCalculationResponse } from './models/pensionCalculation.model' -import { Payments } from './models/payments.model' +import { Payments } from '../models/payments/payments.model' +import { TemporaryCalculation } from '../models/temporaryCalculation.model' +import { TemporaryCalculationInput } from '../dtos/temporaryCalculation.input' @Resolver() @UseGuards(IdsUserGuard, ScopesGuard, FeatureFlagGuard) @Audit({ namespace: '@island.is/api/social-insurance' }) -export class SocialInsuranceResolver { +export class PaymentPlanResolver { constructor(private readonly service: SocialInsuranceService) {} @Query(() => PaymentPlan, { @@ -49,9 +48,11 @@ export class SocialInsuranceResolver { return this.service.getPayments(user) } - @Query(() => PensionCalculationResponse) - @BypassAuth() - async getPensionCalculation(@Args('input') input: PensionCalculationInput) { - return this.service.getPensionCalculation(input) + @Query(() => TemporaryCalculation) + async getTemporaryCalculations( + @Args('input') input: TemporaryCalculationInput, + @CurrentUser() user: User, + ) { + return this.service.getTemporaryCalculations(user, input) } } diff --git a/libs/api/domains/social-insurance/src/lib/resolvers/pension.resolver.ts b/libs/api/domains/social-insurance/src/lib/resolvers/pension.resolver.ts new file mode 100644 index 000000000000..3f5688bae31a --- /dev/null +++ b/libs/api/domains/social-insurance/src/lib/resolvers/pension.resolver.ts @@ -0,0 +1,25 @@ +import { Audit } from '@island.is/nest/audit' +import { Args, Query, Resolver } from '@nestjs/graphql' +import { + IdsUserGuard, + ScopesGuard, + BypassAuth, +} from '@island.is/auth-nest-tools' +import { UseGuards } from '@nestjs/common' +import { SocialInsuranceService } from '../socialInsurance.service' +import { FeatureFlagGuard } from '@island.is/nest/feature-flags' +import { PensionCalculationInput } from '../dtos/pensionCalculation.input' +import { PensionCalculationResponse } from '../models/pension/pensionCalculation.model' + +@Resolver() +@UseGuards(IdsUserGuard, ScopesGuard, FeatureFlagGuard) +@Audit({ namespace: '@island.is/api/social-insurance' }) +export class PensionResolver { + constructor(private readonly service: SocialInsuranceService) {} + + @Query(() => PensionCalculationResponse) + @BypassAuth() + async getPensionCalculation(@Args('input') input: PensionCalculationInput) { + return this.service.getPensionCalculation(input) + } +} diff --git a/libs/api/domains/social-insurance/src/lib/socialInsurance.module.ts b/libs/api/domains/social-insurance/src/lib/socialInsurance.module.ts index 669621eb7e0a..df3a607b4af0 100644 --- a/libs/api/domains/social-insurance/src/lib/socialInsurance.module.ts +++ b/libs/api/domains/social-insurance/src/lib/socialInsurance.module.ts @@ -2,8 +2,12 @@ import { Module } from '@nestjs/common' import { SocialInsuranceAdministrationClientModule } from '@island.is/clients/social-insurance-administration' import { FeatureFlagModule } from '@island.is/nest/feature-flags' import { CmsModule } from '@island.is/cms' -import { SocialInsuranceResolver } from './socialInsurance.resolver' import { SocialInsuranceService } from './socialInsurance.service' +import { + IncomePlanResolver, + PaymentPlanResolver, + PensionResolver, +} from './resolvers' @Module({ imports: [ @@ -11,6 +15,11 @@ import { SocialInsuranceService } from './socialInsurance.service' FeatureFlagModule, CmsModule, ], - providers: [SocialInsuranceResolver, SocialInsuranceService], + providers: [ + PaymentPlanResolver, + PensionResolver, + IncomePlanResolver, + SocialInsuranceService, + ], }) export class SocialInsuranceModule {} diff --git a/libs/api/domains/social-insurance/src/lib/socialInsurance.service.ts b/libs/api/domains/social-insurance/src/lib/socialInsurance.service.ts index f9c4526b58ff..5b53e46c6cc3 100644 --- a/libs/api/domains/social-insurance/src/lib/socialInsurance.service.ts +++ b/libs/api/domains/social-insurance/src/lib/socialInsurance.service.ts @@ -1,6 +1,10 @@ import { User } from '@island.is/auth-nest-tools' import { handle404 } from '@island.is/clients/middlewares' -import { SocialInsuranceAdministrationClientService } from '@island.is/clients/social-insurance-administration' +import { + SocialInsuranceAdministrationClientService, + TrWebCommonsExternalPortalsApiModelsPaymentPlanPaymentPlanDto, + IncomePlanStatus as IncomeStatus, +} from '@island.is/clients/social-insurance-administration' import { CmsElasticsearchService, CustomPageUniqueIdentifier, @@ -10,16 +14,20 @@ import { LOGGER_PROVIDER } from '@island.is/logging' import { isDefined } from '@island.is/shared/utils' import { Inject, Injectable } from '@nestjs/common' import { PensionCalculationInput } from './dtos/pensionCalculation.input' -import { PensionCalculationResponse } from './models/pensionCalculation.model' +import { PensionCalculationResponse } from './models/pension/pensionCalculation.model' import { getPensionCalculationHighlightedItems, groupPensionCalculationItems, mapPensionCalculationInput, } from './utils' -import { PaymentGroup } from './models/paymentGroup.model' -import { PaymentPlan } from './models/paymentPlan.model' -import { Payments } from './models/payments.model' -import { mapToPaymentGroupType } from './models/paymentGroupType.model' +import { PaymentGroup } from './models/payments/paymentGroup.model' +import { PaymentPlan } from './models/payments/paymentPlan.model' +import { Payments } from './models/payments/payments.model' +import { mapToPaymentGroupType } from './models/payments/paymentGroupType.model' +import { IncomePlan } from './models/income/incomePlan.model' +import { IncomePlanStatus, LOG_CATEGORY } from './socialInsurance.type' +import { IncomePlanEligbility } from './models/income/incomePlanEligibility.model' +import { TemporaryCalculationInput } from './dtos/temporaryCalculation.input' @Injectable() export class SocialInsuranceService { @@ -121,6 +129,58 @@ export class SocialInsuranceService { } } + async getIncomePlan(user: User): Promise { + const data = await this.socialInsuranceApi.getLatestIncomePlan(user) + + if (!data?.registrationDate || !data?.status || !data.incomeTypeLines) { + this.logger.info('Income plan incomplete, returning', { + category: LOG_CATEGORY, + }) + return + } + + let hasIncompleteLines = false + const incomeCategories = data.incomeTypeLines + .map((i) => { + if (!i.incomeCategoryName || !i.totalSum || !i.currency) { + hasIncompleteLines = true + return undefined + } + return { + name: i.incomeCategoryName, + annualSum: i.totalSum, + currency: i.currency, + } + }) + .filter(isDefined) + + if (hasIncompleteLines) { + this.logger.info( + 'Income category data filtered out some incomplete lines', + { + category: LOG_CATEGORY, + }, + ) + } + + return { + registrationDate: data.registrationDate, + status: this.parseIncomePlanStatus(data.status), + incomeCategories, + } + } + + async getIncomePlanChangeEligibility( + user: User, + ): Promise { + const data = await this.socialInsuranceApi.getIsEligible(user, 'incomeplan') + + return { + isEligible: data.isEligible ?? undefined, + reason: data.reason ?? undefined, + } + } + async getPensionCalculation( input: PensionCalculationInput, ): Promise { @@ -145,4 +205,24 @@ export class SocialInsuranceService { groups, } } + + async getTemporaryCalculations( + user: User, + input: TemporaryCalculationInput, + ): Promise { + return await this.socialInsuranceApi.getTemporaryCalculations(user, input) + } + + parseIncomePlanStatus = (status: IncomeStatus): IncomePlanStatus => { + switch (status) { + case 'Accepted': + return IncomePlanStatus.ACCEPTED + case 'InProgress': + return IncomePlanStatus.IN_PROGRESS + case 'Cancelled': + return IncomePlanStatus.CANCELLED + default: + return IncomePlanStatus.UNKNOWN + } + } } diff --git a/libs/api/domains/social-insurance/src/lib/socialInsurance.type.ts b/libs/api/domains/social-insurance/src/lib/socialInsurance.type.ts new file mode 100644 index 000000000000..e980dc067dfc --- /dev/null +++ b/libs/api/domains/social-insurance/src/lib/socialInsurance.type.ts @@ -0,0 +1,8 @@ +export enum IncomePlanStatus { + ACCEPTED, + CANCELLED, + IN_PROGRESS, + UNKNOWN, +} + +export const LOG_CATEGORY = 'api-domains-social-insurance-service' diff --git a/libs/api/domains/social-insurance/src/lib/utils.ts b/libs/api/domains/social-insurance/src/lib/utils.ts index 183116506153..cbdd1c2d3dd7 100644 --- a/libs/api/domains/social-insurance/src/lib/utils.ts +++ b/libs/api/domains/social-insurance/src/lib/utils.ts @@ -13,7 +13,7 @@ import addYears from 'date-fns/addYears' import differenceInMonths from 'date-fns/differenceInMonths' import differenceInYears from 'date-fns/differenceInYears' import { CustomPage } from '@island.is/cms' -import { PensionCalculationResponse } from './models/pensionCalculation.model' +import { PensionCalculationResponse } from './models/pension/pensionCalculation.model' const basePensionTypeMapping: Record = { [BasePensionType.Retirement]: 1, // Ellilífeyrir diff --git a/libs/api/domains/syslumenn/src/lib/models/professionRights.ts b/libs/api/domains/syslumenn/src/lib/models/professionRights.ts index f12947b906e5..1b20028f87f4 100644 --- a/libs/api/domains/syslumenn/src/lib/models/professionRights.ts +++ b/libs/api/domains/syslumenn/src/lib/models/professionRights.ts @@ -8,6 +8,9 @@ class ProfessionRight { @Field({ nullable: true }) profession?: string + + @Field({ nullable: true }) + nationalId?: string } @ObjectType() diff --git a/libs/api/domains/university-careers/src/lib/mapper.ts b/libs/api/domains/university-careers/src/lib/mapper.ts index a170fa20e102..09166bcdb54c 100644 --- a/libs/api/domains/university-careers/src/lib/mapper.ts +++ b/libs/api/domains/university-careers/src/lib/mapper.ts @@ -18,9 +18,7 @@ export const mapToStudent = ( !data?.graduationDate || !data?.trackNumber || !data?.school || - !data?.faculty || - !data?.studyProgram || - !data?.degree + !data?.faculty ) { return null } diff --git a/libs/api/domains/university-careers/src/lib/models/institution.model.ts b/libs/api/domains/university-careers/src/lib/models/institution.model.ts index f244087c285e..b58054665da9 100644 --- a/libs/api/domains/university-careers/src/lib/models/institution.model.ts +++ b/libs/api/domains/university-careers/src/lib/models/institution.model.ts @@ -12,9 +12,9 @@ export class Institution { @Field(() => String) shortId!: UniversityIdShort - @Field(() => String, { nullable: true }) + @Field({ nullable: true }) displayName?: string - @Field(() => String, { nullable: true }) + @Field({ nullable: true }) logoUrl?: string } diff --git a/libs/api/domains/university-careers/src/lib/models/studentFile.model.ts b/libs/api/domains/university-careers/src/lib/models/studentFile.model.ts index 25ee567492ba..ad05cca835f6 100644 --- a/libs/api/domains/university-careers/src/lib/models/studentFile.model.ts +++ b/libs/api/domains/university-careers/src/lib/models/studentFile.model.ts @@ -2,15 +2,15 @@ import { ObjectType, Field } from '@nestjs/graphql' @ObjectType('UniversityCareersStudentFile') export class StudentFile { - @Field(() => String) + @Field() type!: string - @Field(() => String) + @Field() locale!: string - @Field(() => String) + @Field() displayName!: string - @Field(() => String) + @Field() fileName!: string } diff --git a/libs/api/domains/university-careers/src/lib/models/studentTrack.model.ts b/libs/api/domains/university-careers/src/lib/models/studentTrack.model.ts index 06e707c8bc92..16c2a7cf7b20 100644 --- a/libs/api/domains/university-careers/src/lib/models/studentTrack.model.ts +++ b/libs/api/domains/university-careers/src/lib/models/studentTrack.model.ts @@ -16,6 +16,6 @@ export class StudentTrack { @Field(() => StudentTrackMetadata) metadata!: StudentTrackMetadata - @Field(() => String, { nullable: true }) + @Field({ nullable: true }) downloadServiceURL?: string } diff --git a/libs/api/domains/university-careers/src/lib/models/studentTrackMetadata.ts b/libs/api/domains/university-careers/src/lib/models/studentTrackMetadata.ts index eb41db8582c8..d45057eb0d4f 100644 --- a/libs/api/domains/university-careers/src/lib/models/studentTrackMetadata.ts +++ b/libs/api/domains/university-careers/src/lib/models/studentTrackMetadata.ts @@ -2,12 +2,12 @@ import { ObjectType, Field } from '@nestjs/graphql' @ObjectType('UniversityCareersStudentTrackMetadata') export class StudentTrackMetadata { - @Field(() => String) + @Field() description!: string - @Field(() => String) + @Field() footer!: string - @Field(() => String, { nullable: true }) + @Field({ nullable: true }) unconfirmedData?: string } diff --git a/libs/api/domains/university-careers/src/lib/models/studentTrackTranscript.model.ts b/libs/api/domains/university-careers/src/lib/models/studentTrackTranscript.model.ts index 468dd44118ad..68c339218ba2 100644 --- a/libs/api/domains/university-careers/src/lib/models/studentTrackTranscript.model.ts +++ b/libs/api/domains/university-careers/src/lib/models/studentTrackTranscript.model.ts @@ -1,32 +1,32 @@ -import { ObjectType, Field } from '@nestjs/graphql' +import { ObjectType, Field, Int } from '@nestjs/graphql' import { Institution } from './institution.model' @ObjectType('UniversityCareersStudentTrackTranscript') export class StudentTrackTranscript { - @Field(() => String) + @Field() name!: string - @Field(() => String, { nullable: true }) + @Field({ nullable: true }) nationalId?: string - @Field(() => String) + @Field() graduationDate!: string - @Field(() => Number) + @Field(() => Int) trackNumber!: number @Field(() => Institution) institution!: Institution - @Field(() => String) + @Field() school!: string - @Field(() => String) + @Field() faculty!: string - @Field(() => String) - studyProgram!: string + @Field({ nullable: true }) + studyProgram?: string - @Field(() => String) - degree!: string + @Field({ nullable: true }) + degree?: string } diff --git a/libs/api/domains/work-machines/src/lib/models/getWorkMachines.ts b/libs/api/domains/work-machines/src/lib/models/getWorkMachines.ts index 15c38882cb06..0a907fc9d8e4 100644 --- a/libs/api/domains/work-machines/src/lib/models/getWorkMachines.ts +++ b/libs/api/domains/work-machines/src/lib/models/getWorkMachines.ts @@ -1,4 +1,10 @@ -import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql' +import { + Field, + GraphQLISODateTime, + ID, + ObjectType, + registerEnumType, +} from '@nestjs/graphql' import { Action, ExternalLink } from '../workMachines.types' import { PaginatedResponse } from '@island.is/nest/pagination' @@ -24,8 +30,8 @@ export class WorkMachine { @Field(() => String, { nullable: true }) subCategory?: string | null - @Field(() => String, { nullable: true }) - dateLastInspection?: string | null + @Field(() => GraphQLISODateTime, { nullable: true }) + dateLastInspection?: Date @Field(() => String, { nullable: true }) registrationNumber?: string | null diff --git a/libs/api/domains/work-machines/src/lib/workMachines.resolver.ts b/libs/api/domains/work-machines/src/lib/workMachines.resolver.ts index 00810c5d5785..de0f5add00b0 100644 --- a/libs/api/domains/work-machines/src/lib/workMachines.resolver.ts +++ b/libs/api/domains/work-machines/src/lib/workMachines.resolver.ts @@ -177,7 +177,12 @@ export class WorkMachinesResolver { async getTechnicalInfoInputs( @CurrentUser() auth: User, @Args('parentCategory') parentCategory: string, + @Args('subCategory') subCategory: string, ) { - return this.workMachinesService.getTechnicalInfoInputs(auth, parentCategory) + return this.workMachinesService.getTechnicalInfoInputs( + auth, + parentCategory, + subCategory, + ) } } diff --git a/libs/api/domains/work-machines/src/lib/workMachines.service.ts b/libs/api/domains/work-machines/src/lib/workMachines.service.ts index 662426fc302d..cee1ca0ffc09 100644 --- a/libs/api/domains/work-machines/src/lib/workMachines.service.ts +++ b/libs/api/domains/work-machines/src/lib/workMachines.service.ts @@ -79,6 +79,9 @@ export class WorkMachinesService { (v) => ({ ...v, + dateLastInspection: v.dateLastInspection + ? new Date(v.dateLastInspection) + : undefined, ownerName: v.owner, supervisorName: v.supervisor, } as WorkMachine), @@ -132,6 +135,9 @@ export class WorkMachinesService { return { ...data, + dateLastInspection: data.dateLastInspection + ? new Date(data.dateLastInspection) + : undefined, links, } } @@ -187,7 +193,11 @@ export class WorkMachinesService { async getTechnicalInfoInputs( auth: User, parentCategory: string, + subCategory: string, ): Promise { - return this.machineService.getTechnicalInfoInputs(auth, { parentCategory }) + return this.machineService.getTechnicalInfoInputs(auth, { + parentCategory, + subCategory, + }) } } diff --git a/libs/application/api/files/src/lib/file.service.spec.ts b/libs/application/api/files/src/lib/file.service.spec.ts index 3d73ecf13ce8..b435792757ae 100644 --- a/libs/application/api/files/src/lib/file.service.spec.ts +++ b/libs/application/api/files/src/lib/file.service.spec.ts @@ -13,7 +13,6 @@ import { LoggingModule } from '@island.is/logging' import { NotFoundException } from '@nestjs/common' import { defineConfig, ConfigModule } from '@island.is/nest/config' import { FileStorageConfig } from '@island.is/file-storage' -import { GetObjectCommandOutput } from '@aws-sdk/client-s3' describe('FileService', () => { let service: FileService @@ -130,12 +129,9 @@ describe('FileService', () => { awsService = module.get(AwsService) - jest.spyOn(awsService, 'getFile').mockImplementation( - async () => - ({ - Body: { transformToString: () => 'body' }, - } as unknown as GetObjectCommandOutput), - ) + jest + .spyOn(awsService, 'getFile') + .mockImplementation(() => Promise.resolve({ Body: 'body' })) jest.spyOn(awsService, 'fileExists').mockResolvedValue(false) @@ -199,11 +195,10 @@ describe('FileService', () => { PdfTypes.CHILDREN_RESIDENCE_CHANGE, ) - expect(awsService.getFile).toHaveBeenCalledWith({ - bucket: bucket, - fileName: `children-residence-change/${application.id}.pdf`, - encoding: 'binary', - }) + expect(awsService.getFile).toHaveBeenCalledWith( + bucket, + `children-residence-change/${application.id}.pdf`, + ) expect(signingService.requestSignature).toHaveBeenCalledWith( parentAWithContactInfo.phoneNumber, @@ -225,12 +220,9 @@ describe('FileService', () => { it('should throw error for request file signature since file content is missing', async () => { const application = createApplication() - jest.spyOn(awsService, 'getFile').mockImplementation( - async () => - ({ - Body: { transformToString: () => '' }, - } as unknown as GetObjectCommandOutput), - ) + jest + .spyOn(awsService, 'getFile') + .mockImplementation(() => Promise.resolve({ Body: '' })) const act = async () => await service.requestFileSignature( @@ -240,11 +232,10 @@ describe('FileService', () => { await expect(act).rejects.toThrowError(NotFoundException) - expect(awsService.getFile).toHaveBeenCalledWith({ - bucket: bucket, - fileName: `children-residence-change/${applicationId}.pdf`, - encoding: 'binary', - }) + expect(awsService.getFile).toHaveBeenCalledWith( + bucket, + `children-residence-change/${applicationId}.pdf`, + ) expect(signingService.requestSignature).not.toHaveBeenCalled() }) @@ -306,8 +297,6 @@ describe('FileService', () => { PdfTypes.CHILDREN_RESIDENCE_CHANGE, ) - // The resolves are `undefined` 🥹 - // await expect(act()).resolves.toBeTruthy() await expect(act).resolves }) @@ -334,7 +323,7 @@ describe('FileService', () => { PdfTypes.CHILDREN_RESIDENCE_CHANGE, ) - await expect(act()).resolves.toBeTruthy() + await expect(act).resolves }) it('should throw error for getPresignedUrl since application type is not supported', async () => { diff --git a/libs/application/api/files/src/lib/file.service.ts b/libs/application/api/files/src/lib/file.service.ts index 9c04fc543059..de85db0e2ab2 100644 --- a/libs/application/api/files/src/lib/file.service.ts +++ b/libs/application/api/files/src/lib/file.service.ts @@ -148,11 +148,8 @@ export class FileService { ) { const bucket = this.getBucketName() const s3FileName = `${BucketTypePrefix[pdfType]}/${application.id}.pdf` - const fileContent = await this.awsService.getFileEncoded({ - bucket, - fileName: s3FileName, - encoding: 'binary', - }) + const s3File = await this.awsService.getFile(bucket, s3FileName) + const fileContent = s3File.Body?.toString('binary') const { phoneNumber, name, title } = this.getSigningOptionsFromApplication( application, diff --git a/libs/application/api/payment/src/lib/payment.controller.ts b/libs/application/api/payment/src/lib/payment.controller.ts index 1bb0612d8ea8..10635784a92b 100644 --- a/libs/application/api/payment/src/lib/payment.controller.ts +++ b/libs/application/api/payment/src/lib/payment.controller.ts @@ -6,7 +6,13 @@ import { ParseUUIDPipe, } from '@nestjs/common' -import { ApiParam, ApiTags, ApiHeader, ApiOkResponse } from '@nestjs/swagger' +import { + ApiParam, + ApiTags, + ApiHeader, + ApiOkResponse, + ApiBearerAuth, +} from '@nestjs/swagger' import type { User } from '@island.is/auth-nest-tools' import { IdsUserGuard, @@ -20,10 +26,7 @@ import { PaymentStatusResponseDto } from './dto/paymentStatusResponse.dto' @UseGuards(IdsUserGuard, ScopesGuard) @ApiTags('payments') -@ApiHeader({ - name: 'authorization', - description: 'Bearer token authorization', -}) +@ApiBearerAuth() @ApiHeader({ name: 'locale', description: 'Front-end language selected', diff --git a/libs/application/api/payment/src/lib/payment.service.ts b/libs/application/api/payment/src/lib/payment.service.ts index d4970d144755..579f2f82b4fb 100644 --- a/libs/application/api/payment/src/lib/payment.service.ts +++ b/libs/application/api/payment/src/lib/payment.service.ts @@ -124,7 +124,7 @@ export class PaymentService { return `${this.config.arkBaseUrl}/quickpay/pay?doc_num=${docNum}` } - private async setUser4( + async setUser4( applicationId: string, paymentId: string, user4: string, diff --git a/libs/application/core/src/lib/ApplicationTemplateHelper.ts b/libs/application/core/src/lib/ApplicationTemplateHelper.ts index 856e105b2e59..f7d35edcd394 100644 --- a/libs/application/core/src/lib/ApplicationTemplateHelper.ts +++ b/libs/application/core/src/lib/ApplicationTemplateHelper.ts @@ -71,6 +71,7 @@ export class ApplicationTemplateHelper< title?: StaticText description?: StaticText tag: { variant?: string; label?: StaticText } + historyButton?: StaticText } { const actionCard = this.template.stateMachineConfig.states[stateKey]?.meta?.actionCard @@ -79,6 +80,7 @@ export class ApplicationTemplateHelper< title: actionCard?.title, description: actionCard?.description, tag: { variant: actionCard?.tag?.variant, label: actionCard?.tag?.label }, + historyButton: actionCard?.historyButton, } } @@ -281,6 +283,7 @@ export class ApplicationTemplateHelper< displayStatus: action.displayStatus, content: action.content ? formatMessage(action.content) : undefined, title: action.title ? formatMessage(action.title) : undefined, + button: action.button ? formatMessage(action.button) : undefined, } } return { @@ -291,6 +294,9 @@ export class ApplicationTemplateHelper< content: pendingAction.content ? formatMessage(pendingAction.content) : undefined, + button: pendingAction.button + ? formatMessage(pendingAction.button) + : undefined, } } diff --git a/libs/application/core/src/lib/fieldBuilders.ts b/libs/application/core/src/lib/fieldBuilders.ts index 9bfd3ff57602..049cba433e2f 100644 --- a/libs/application/core/src/lib/fieldBuilders.ts +++ b/libs/application/core/src/lib/fieldBuilders.ts @@ -145,10 +145,11 @@ export function buildDescriptionField( space, marginBottom, marginTop, + doesNotRequireAnswer = true, } = data return { ...extractCommonFields(data), - doesNotRequireAnswer: true, + doesNotRequireAnswer, children: undefined, description, titleVariant, @@ -277,6 +278,8 @@ export function buildTextField( rows, required, maxLength, + max, + min, readOnly, rightAlign, onChange, @@ -294,6 +297,8 @@ export function buildTextField( maxLength, readOnly, rightAlign, + max, + min, onChange, type: FieldTypes.TEXT, component: FieldComponents.TEXT, diff --git a/libs/application/graphql/src/lib/fragments/application.ts b/libs/application/graphql/src/lib/fragments/application.ts index 5e64efc54701..0762f3efb34e 100644 --- a/libs/application/graphql/src/lib/fragments/application.ts +++ b/libs/application/graphql/src/lib/fragments/application.ts @@ -20,6 +20,7 @@ export const ApplicationFragment = gql` displayStatus content title + button } history { log @@ -28,6 +29,7 @@ export const ApplicationFragment = gql` deleteButton draftTotalSteps draftFinishedSteps + historyButton } typeId answers diff --git a/libs/application/template-api-modules/README.md b/libs/application/template-api-modules/README.md index a999621114d9..4a868c3b6fe2 100644 --- a/libs/application/template-api-modules/README.md +++ b/libs/application/template-api-modules/README.md @@ -417,3 +417,29 @@ import { SharedApi } from '@island.is/application/types' }, ``` + +## Enabling payment mocking + +To enable payment mocking on dev and local you need to add `enableMockPayment: true` to the arguments object passed to the +`buildExternalDataProvider` function when constructing your `approveExternalData` field. + +A simple example of this can be found in `libs/application/templates/example-payment/src/forms/draft.ts` + +```typescript +buildExternalDataProvider({ + title: m.draft.externalDataTitle, + id: 'approveExternalData', + subTitle: m.draft.externalDataTitle, + checkboxLabel: m.draft.externalDataTitle, + enableMockPayment: true, + dataProviders: [ + buildDataProviderItem({ + provider: PaymentCatalogApi, + title: m.draft.feeInfo, + }), + ], + }), + +``` + +This will cause a checkbox saying "Enable mock payment" to be shown that if checked will cause the payment step to be skipped. diff --git a/libs/application/template-api-modules/src/lib/modules/shared/api/payment/payment.service.ts b/libs/application/template-api-modules/src/lib/modules/shared/api/payment/payment.service.ts index 96c22bc975a5..c6ceb34a56e1 100644 --- a/libs/application/template-api-modules/src/lib/modules/shared/api/payment/payment.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/shared/api/payment/payment.service.ts @@ -3,24 +3,34 @@ import { PaymentCatalogItem, PaymentCatalogParameters, } from '@island.is/application/types' -import { TemplateApiModuleActionProps } from '../../../../types' +import { + BaseTemplateAPIModuleConfig, + TemplateApiModuleActionProps, +} from '../../../../types' import { ChargeFjsV2ClientService } from '@island.is/clients/charge-fjs-v2' -import { Injectable } from '@nestjs/common' +import { Inject, Injectable } from '@nestjs/common' import { BaseTemplateApiService } from '../../../base-template-api.service' import { PaymentService as PaymentModelService } from '@island.is/application/api/payment' import { TemplateApiError } from '@island.is/nest/problem' +import { getSlugFromType } from '@island.is/application/core' +import { getConfigValue } from '../../shared.utils' +import { ConfigService } from '@nestjs/config' +import { uuid } from 'uuidv4' @Injectable() export class PaymentService extends BaseTemplateApiService { constructor( private chargeFjsV2ClientService: ChargeFjsV2ClientService, private readonly paymentModelService: PaymentModelService, + @Inject(ConfigService) + private readonly configService: ConfigService, ) { super('Payment') } async paymentCatalog({ params, + application, }: TemplateApiModuleActionProps): Promise< PaymentCatalogItem[] > { @@ -40,6 +50,48 @@ export class PaymentService extends BaseTemplateApiService { params, }: TemplateApiModuleActionProps) { const { organizationId, chargeItemCodes, extraData } = params ?? {} + const { shouldUseMockPayment } = application.answers + + if (shouldUseMockPayment) { + const list = [ + { + performingOrgID: organizationId ?? 'string', + chargeType: ' string', + chargeItemCode: 'string', + chargeItemName: 'string', + priceAmount: 123123, + }, + ] + + const result = await this.paymentModelService.createPaymentModel( + list, + application.id, + organizationId ?? 'string', + ) + + await this.paymentModelService.setUser4( + application.id, + result.id, + 'newser4', + ) + + await this.paymentModelService.fulfillPayment( + result.id, + result.reference_id ?? uuid(), + application.id, + ) + + const slug = getSlugFromType(application.typeId) + const clientLocationOrigin = getConfigValue( + this.configService, + 'clientLocationOrigin', + ) as string + + return { + id: result.id, + paymentUrl: `${clientLocationOrigin}/${slug}/${application.id}`, + } + } if (!organizationId) throw Error('Missing performing organization ID') if (!chargeItemCodes) throw Error('No selected charge item code') diff --git a/libs/application/template-api-modules/src/lib/modules/shared/services/attachment-s3.service.ts b/libs/application/template-api-modules/src/lib/modules/shared/services/attachment-s3.service.ts index 7170bab64de2..f1b5adde4890 100644 --- a/libs/application/template-api-modules/src/lib/modules/shared/services/attachment-s3.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/shared/services/attachment-s3.service.ts @@ -1,7 +1,8 @@ import { getValueViaPath } from '@island.is/application/core' import { ApplicationWithAttachments as Application } from '@island.is/application/types' +import { S3 } from 'aws-sdk' +import AmazonS3URI from 'amazon-s3-uri' import { logger } from '@island.is/logging' -import { AwsService } from '@island.is/nest/aws' import { Injectable } from '@nestjs/common' export interface AttachmentData { @@ -13,33 +14,22 @@ export interface AttachmentData { @Injectable() export class AttachmentS3Service { - constructor(private readonly awsService: AwsService) {} + private readonly s3: AWS.S3 + constructor() { + this.s3 = new S3() + } - /** - * This function retrieves files from an application based on provided attachment keys. - * It iterates over the attachment keys, retrieves the corresponding answers from the application, - * and converts them into a list of document data. The resulting list of document data is then returned. - * - * @param {Application} application - The application from which to retrieve files. - * @param {string[]} attachmentAnswerKeys - The keys of the attachments to retrieve. - * @returns {Promise} - A promise that resolves to an array of attachment data. - */ public async getFiles( application: Application, - attachmentAnswerKeys: string | string[], + attachmentAnswerKeys: string[], ): Promise { - if (!Array.isArray(attachmentAnswerKeys)) { - attachmentAnswerKeys = [attachmentAnswerKeys] - } const attachments: AttachmentData[] = [] for (const key of attachmentAnswerKeys) { - const answers = getValueViaPath< - Array<{ - key: string - name: string - }> - >(application.answers, key) + const answers = getValueViaPath(application.answers, key) as Array<{ + key: string + name: string + }> if (!answers) continue const list = await this.toDocumentDataList(answers, key, application) attachments.push(...list) @@ -78,8 +68,17 @@ export class AttachmentS3Service { private async getApplicationFilecontentAsBase64( fileName: string, ): Promise { + const { bucket, key } = AmazonS3URI(fileName) + const uploadBucket = bucket try { - return this.awsService.getFileBase64({ s3Uri: fileName }) + const file = await this.s3 + .getObject({ + Bucket: uploadBucket, + Key: key, + }) + .promise() + const fileContent = file.Body as Buffer + return fileContent?.toString('base64') } catch (error) { logger.error(error) return undefined diff --git a/libs/application/template-api-modules/src/lib/modules/shared/shared.module.ts b/libs/application/template-api-modules/src/lib/modules/shared/shared.module.ts index b9df3f440074..2473e3838fec 100644 --- a/libs/application/template-api-modules/src/lib/modules/shared/shared.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/shared/shared.module.ts @@ -2,7 +2,7 @@ import { DynamicModule } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' import { EmailModule } from '@island.is/email-service' import { ApplicationApiCoreModule } from '@island.is/application/api/core' -import { AwsModule, AwsService } from '@island.is/nest/aws' +import { AwsModule } from '@island.is/nest/aws' import { BaseTemplateAPIModuleConfig, BaseTemplateApiApplicationService, @@ -35,7 +35,6 @@ export class SharedTemplateAPIModule { useClass: config.applicationService, }, AttachmentS3Service, - AwsService, ], exports: [SharedTemplateApiService, AttachmentS3Service], } diff --git a/libs/application/template-api-modules/src/lib/modules/shared/shared.service.ts b/libs/application/template-api-modules/src/lib/modules/shared/shared.service.ts index a454da870f95..398c9537a598 100644 --- a/libs/application/template-api-modules/src/lib/modules/shared/shared.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/shared/shared.service.ts @@ -19,13 +19,15 @@ import { getConfigValue } from './shared.utils' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' import { SmsService } from '@island.is/nova-sms' +import { S3 } from 'aws-sdk' +import AmazonS3URI from 'amazon-s3-uri' import { PaymentService } from '@island.is/application/api/payment' import { User } from '@island.is/auth-nest-tools' import { ExtraData } from '@island.is/clients/charge-fjs-v2' -import { AwsService, Encoding } from '@island.is/nest/aws' @Injectable() export class SharedTemplateApiService { + s3: S3 constructor( @Inject(LOGGER_PROVIDER) private readonly logger: Logger, @@ -38,8 +40,9 @@ export class SharedTemplateApiService { @Inject(BaseTemplateApiApplicationService) private readonly applicationService: BaseTemplateApiApplicationService, private readonly paymentService: PaymentService, - private readonly awsService: AwsService, - ) {} + ) { + this.s3 = new S3() + } async createAssignToken(application: Application, expiresIn: number) { const token = await this.applicationService.createAssignToken( @@ -150,13 +153,10 @@ export class SharedTemplateApiService { async sendEmailWithAttachment( templateGenerator: AttachmentEmailTemplateGenerator, application: Application, - fileContent: Parameters[1], + fileContent: string, recipientEmail: string, locale = 'is', ) { - this.logger.debug('Sending email with attachment', { - applicationId: application.id, - }) const clientLocationOrigin = getConfigValue( this.configService, 'clientLocationOrigin', @@ -245,33 +245,29 @@ export class SharedTemplateApiService { async getS3File( application: ApplicationWithAttachments, attachmentKey: string, - ): Promise> - async getS3File( - application: ApplicationWithAttachments, - attachmentKey: string, - encoding: Encoding, - ): Promise - async getS3File( - application: ApplicationWithAttachments, - attachmentKey: string, - encoding?: Encoding, - ): Promise | string | undefined> { + ) { const fileName = ( application.attachments as { [key: string]: string } )[attachmentKey] - if (encoding) - return await this.awsService.getFileEncoded({ fileName, encoding }) - return await this.awsService.getFile({ fileName }) + const { bucket, key } = AmazonS3URI(fileName) + const file = await this.s3 + .getObject({ + Bucket: bucket, + Key: key, + }) + .promise() + return file } async getAttachmentContentAsBase64( application: ApplicationWithAttachments, attachmentKey: string, ): Promise { - const fileContent = (await this.getS3File(application, attachmentKey))?.Body - return fileContent?.transformToString('base64') || '' + const fileContent = (await this.getS3File(application, attachmentKey)) + ?.Body as Buffer + return fileContent?.toString('base64') || '' } async getAttachmentContentAsBlob( @@ -279,11 +275,26 @@ export class SharedTemplateApiService { attachmentKey: string, ): Promise { const file = await this.getS3File(application, attachmentKey) - if (!file.Body) { - return new Blob([], { type: file.ContentType }) + return new Blob([file.Body as ArrayBuffer], { type: file.ContentType }) + } + + async getAttachmentUrl(key: string, expiration: number): Promise { + if (expiration <= 0) { + return Promise.reject('expiration must be positive') } - return new Blob([await file.Body.transformToByteArray()], { - type: file.ContentType, + + const bucket = this.configService.get('attachmentBucket') as + | string + | undefined + + if (bucket == undefined) { + return Promise.reject('could not find s3 bucket') + } + + return this.s3.getSignedUrlPromise('getObject', { + Bucket: bucket, + Key: key, + Expires: expiration, }) } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.module.ts index dd40badb54a3..c3d21f0fbc8f 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.module.ts @@ -6,7 +6,8 @@ import { AccidentNotificationService } from './accident-notification.service' import { HealthInsuranceV2ClientModule } from '@island.is/clients/icelandic-health-insurance/health-insurance' import { ApplicationAttachmentService } from './attachments/applicationAttachment.service' import { AccidentNotificationAttachmentProvider } from './attachments/applicationAttachmentProvider' -import { AwsService } from '@island.is/nest/aws' +import { S3 } from 'aws-sdk' +import { S3Service } from './attachments/s3.service' const applicationRecipientName = process.env.ACCIDENT_NOTIFICATION_APPLICATION_RECIPIENT_NAME ?? '' @@ -40,9 +41,10 @@ export class AccidentNotificationModule { AccidentNotificationService, ApplicationAttachmentService, AccidentNotificationAttachmentProvider, + S3Service, { - provide: AwsService, - useValue: new AwsService(), + provide: S3, + useValue: new S3(), }, ], exports: [AccidentNotificationService], diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.spec.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.spec.ts index e47be4f5ee40..55e60124b910 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.spec.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.spec.ts @@ -14,14 +14,14 @@ import { ApplicationAttachmentService } from './attachments/applicationAttachmen import { ACCIDENT_NOTIFICATION_CONFIG } from './config' import { DocumentApi } from '@island.is/clients/icelandic-health-insurance/health-insurance' import { createCurrentUser } from '@island.is/testing/fixtures' +import { S3 } from 'aws-sdk' import type { Locale } from '@island.is/shared/types' import { createApplication } from '@island.is/application/testing' import get from 'lodash/get' import set from 'lodash/set' +import { S3Service } from './attachments/s3.service' import { SmsService } from '@island.is/nova-sms' import { PaymentService } from '@island.is/application/api/payment' -import { AwsService } from '@island.is/nest/aws' - const nationalId = '1234564321' let id = 0 @@ -48,9 +48,16 @@ class MockSmsService { } } +const S3Instance = { + upload: jest.fn().mockReturnThis(), + promise: jest.fn(), + deleteObject: jest.fn().mockReturnThis(), + getObject: jest.fn().mockReturnThis(), +} + describe('AccidentNotificationService', () => { let accidentNotificationService: AccidentNotificationService - let aws: AwsService + let s3Service: S3Service let documentApi: DocumentApi beforeEach(async () => { @@ -73,6 +80,10 @@ describe('AccidentNotificationService', () => { }, })), }, + { + provide: S3, + useFactory: () => S3Instance, + }, { provide: ConfigService, useValue: {}, @@ -90,9 +101,9 @@ describe('AccidentNotificationService', () => { useClass: MockSmsService, }, { - provide: AwsService, + provide: S3Service, useClass: jest.fn(() => ({ - getFileBase64: jest.fn(), + getFilecontentAsBase64: jest.fn(), })), }, { @@ -114,7 +125,7 @@ describe('AccidentNotificationService', () => { }).compile() accidentNotificationService = module.get(AccidentNotificationService) - aws = module.get(AwsService) + s3Service = module.get(S3Service) documentApi = module.get(DocumentApi) }) @@ -202,7 +213,7 @@ describe('AccidentNotificationService', () => { } jest - .spyOn(aws, 'getFileBase64') + .spyOn(s3Service, 'getFilecontentAsBase64') .mockResolvedValueOnce( Buffer.from('some content', 'utf-8') as unknown as string, ) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/applicationAttachment.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/applicationAttachment.service.ts index a4847cf67af1..62516ce162b5 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/applicationAttachment.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/applicationAttachment.service.ts @@ -2,8 +2,8 @@ import { getValueViaPath } from '@island.is/application/core' import { ApplicationWithAttachments as Application } from '@island.is/application/types' import { logger } from '@island.is/logging' -import { AwsService } from '@island.is/nest/aws' import { Injectable } from '@nestjs/common' +import { S3Service } from './s3.service' export interface AttachmentData { key: string @@ -14,7 +14,7 @@ export interface AttachmentData { @Injectable() export class ApplicationAttachmentService { - constructor(private readonly awsService: AwsService) {} + constructor(private readonly s3Service: S3Service) {} public async getFiles( application: Application, @@ -62,7 +62,7 @@ export class ApplicationAttachmentService { return { key: '', fileContent: '', answerKey, fileName: '' } } const fileContent = - (await this.awsService.getFileBase64({ s3Uri: url })) ?? '' + (await this.s3Service.getFilecontentAsBase64(url)) ?? '' return { key, fileContent, answerKey, fileName: name } }), diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/s3.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/s3.service.ts new file mode 100644 index 000000000000..9061b7c152b5 --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/s3.service.ts @@ -0,0 +1,30 @@ +import { logger } from '@island.is/logging' +import { Injectable } from '@nestjs/common' +import AmazonS3URI from 'amazon-s3-uri' +import { S3 } from 'aws-sdk' + +@Injectable() +export class S3Service { + constructor(private readonly s3: S3) {} + + public async getFilecontentAsBase64( + filUri: string, + ): Promise { + const { bucket, key } = AmazonS3URI(filUri) + const uploadBucket = bucket + try { + const file = await this.s3 + .getObject({ + Bucket: uploadBucket, + Key: key, + }) + .promise() + + const fileContent = file.Body as Buffer + return fileContent?.toString('base64') + } catch (error) { + logger.error(error) + return undefined + } + } +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/announcement-of-death/announcement-of-death.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/announcement-of-death/announcement-of-death.service.ts index a802991e3b1d..5d0958d27374 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/announcement-of-death/announcement-of-death.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/announcement-of-death/announcement-of-death.service.ts @@ -12,7 +12,8 @@ import { } from '@island.is/clients/syslumenn' import { Answers as aodAnswers, - OtherPropertiesEnum, + FirearmApplicant, + PropertiesEnum, } from '@island.is/application/templates/announcement-of-death/types' import { NationalRegistry, RoleConfirmationEnum, PickRole } from './types' import { @@ -24,7 +25,7 @@ import { import { isPerson } from 'kennitala' import { BaseTemplateApiService } from '../../base-template-api.service' import { Application, ApplicationTypes } from '@island.is/application/types' -import { coreErrorMessages } from '@island.is/application/core' +import { coreErrorMessages, YES } from '@island.is/application/core' import { TemplateApiError } from '@island.is/nest/problem' import { generateFirearmApplicantEmail } from './emailGenerators/firearmApplicantNotification' import { SharedTemplateApiService } from '../../shared' @@ -75,18 +76,10 @@ export class AnnouncementOfDeathService extends BaseTemplateApiService { } for (const estate in estates) { - estates[estate].assets = estates[estate].assets.map(baseMapper) - estates[estate].vehicles = [ - ...estates[estate].vehicles, - ...estates[estate].ships, - ...estates[estate].flyers, - ].map(baseMapper) estates[estate].estateMembers = estates[estate].estateMembers.map(baseMapper) // TODO: remove once empty array diff problem is resolved // in the application system (property dropped before deepmerge) - estates[estate].assets.unshift(dummyAsset as EstateAsset) - estates[estate].vehicles.unshift(dummyAsset as EstateAsset) estates[estate].estateMembers.unshift(dummyMember as EstateMember) estates[estate].guns.unshift(dummyAsset as EstateAsset) } @@ -230,32 +223,38 @@ export class AnnouncementOfDeathService extends BaseTemplateApiService { estateMembers: JSON.stringify( answers.estateMembers.members.filter((member) => !member?.dummy), ), - assets: JSON.stringify( - answers.assets.assets.filter((asset) => !asset?.dummy), - ), - vehicles: JSON.stringify( - answers.vehicles.vehicles.filter((vehicle) => !vehicle?.dummy), - ), hadFirearms: answers.hadFirearms, - firearm: JSON.stringify(answers.firearmApplicant), + firearm: + answers.hadFirearms === YES + ? JSON.stringify(answers.firearmApplicant) + : JSON.stringify({ + email: '', + phone: '', + name: '', + nationalId: '', + }), bankcodeSecuritiesOrShares: otherProperties.includes( - OtherPropertiesEnum.ACCOUNTS, + PropertiesEnum.ACCOUNTS, ) ? 'true' : 'false', selfOperatedCompany: otherProperties.includes( - OtherPropertiesEnum.OWN_BUSINESS, + PropertiesEnum.OWN_BUSINESS, ) ? 'true' : 'false', occupationRightViaCondominium: otherProperties.includes( - OtherPropertiesEnum.RESIDENCE, + PropertiesEnum.RESIDENCE, ) ? 'true' : 'false', - assetsAbroad: otherProperties.includes( - OtherPropertiesEnum.ASSETS_ABROAD, - ) + assetsAbroad: otherProperties.includes(PropertiesEnum.ASSETS_ABROAD) + ? 'true' + : 'false', + realEstate: otherProperties.includes(PropertiesEnum.REAL_ESTATE) + ? 'true' + : 'false', + vehicles: otherProperties.includes(PropertiesEnum.VEHICLES) ? 'true' : 'false', districtCommissionerHasWill: diff --git a/libs/application/template-api-modules/src/lib/modules/templates/aosh/register-new-machine/register-new-machine.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/aosh/register-new-machine/register-new-machine.service.ts index 57f3c6e42a03..6750c34a3f4a 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/aosh/register-new-machine/register-new-machine.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/aosh/register-new-machine/register-new-machine.service.ts @@ -64,7 +64,7 @@ export class RegisterNewMachineTemplateService extends BaseTemplateApiService { const techInfo = {} as { [key: string]: string | undefined } answers.techInfo.forEach(({ variableName, value }) => { - if (variableName) { + if (variableName && value) { techInfo[variableName] = value } }) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/children-residence-change.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/children-residence-change.module.ts index cf82d718d71c..7034ce395966 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/children-residence-change.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/children-residence-change.module.ts @@ -1,12 +1,13 @@ import { DynamicModule } from '@nestjs/common' import { BaseTemplateAPIModuleConfig } from '../../../types' -import { ChildrenResidenceChangeService } from './children-residence-change.service' -import { PRESIGNED_BUCKET } from './constants' +import { + ChildrenResidenceChangeService, + PRESIGNED_BUCKET, +} from './children-residence-change.service' import { SyslumennClientModule } from '@island.is/clients/syslumenn' import { NationalRegistryClientModule } from '@island.is/clients/national-registry-v2' import { SharedTemplateAPIModule } from '../../shared' import { SmsModule } from '@island.is/nova-sms' -import { AwsService } from '@island.is/nest/aws' export class ChildrenResidenceChangeModule { static register(config: BaseTemplateAPIModuleConfig): DynamicModule { @@ -19,7 +20,6 @@ export class ChildrenResidenceChangeModule { NationalRegistryClientModule, ], providers: [ - AwsService, { provide: PRESIGNED_BUCKET, useFactory: () => config.presignBucket, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/children-residence-change.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/children-residence-change.service.ts index 3a2b08e73465..c531da5ef489 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/children-residence-change.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/children-residence-change.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common' +import { Injectable, Inject } from '@nestjs/common' import { TemplateApiModuleActionProps } from '../../../types' import { SyslumennService, @@ -13,7 +13,7 @@ import { } from '@island.is/application/templates/family-matters-core/utils' import { Override } from '@island.is/application/templates/family-matters-core/types' import { CRCApplication } from '@island.is/application/templates/children-residence-change' -import { PRESIGNED_BUCKET } from './constants' +import { S3 } from 'aws-sdk' import { SharedTemplateApiService } from '../../shared' import { generateApplicationSubmittedEmail, @@ -25,7 +25,9 @@ import { SmsService } from '@island.is/nova-sms' import { syslumennDataFromPostalCode } from './utils' import { applicationRejectedEmail } from './emailGenerators/applicationRejected' import { BaseTemplateApiService } from '../../base-template-api.service' -import { AwsService } from '@island.is/nest/aws' +import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2' + +export const PRESIGNED_BUCKET = 'PRESIGNED_BUCKET' type props = Override< TemplateApiModuleActionProps, @@ -34,14 +36,17 @@ type props = Override< @Injectable() export class ChildrenResidenceChangeService extends BaseTemplateApiService { + s3: S3 + constructor( private readonly syslumennService: SyslumennService, - private readonly sharedTemplateAPIService: SharedTemplateApiService, @Inject(PRESIGNED_BUCKET) private readonly presignedBucket: string, + private readonly sharedTemplateAPIService: SharedTemplateApiService, private readonly smsService: SmsService, - private readonly awsService: AwsService, + private nationalRegistryApi: NationalRegistryClientService, ) { super(ApplicationTypes.CHILDREN_RESIDENCE_CHANGE) + this.s3 = new S3() } async submitApplication({ application }: props) { @@ -49,10 +54,10 @@ export class ChildrenResidenceChangeService extends BaseTemplateApiService { const { nationalRegistry } = externalData const applicant = nationalRegistry.data const s3FileName = `children-residence-change/${application.id}.pdf` - const file = await this.awsService.getFile({ - bucket: this.presignedBucket, - fileName: s3FileName, - }) + const file = await this.s3 + .getObject({ Bucket: this.presignedBucket, Key: s3FileName }) + .promise() + const fileContent = file.Body as Buffer const selectedChildren = getSelectedChildrenFromExternalData( externalData.childrenCustodyInformation.data, @@ -68,16 +73,14 @@ export class ChildrenResidenceChangeService extends BaseTemplateApiService { ) const currentAddress = childResidenceInfo?.current?.address - if (!file.Body) { + if (!fileContent) { throw new Error('File content was undefined') } - const fileB64 = await file.Body.transformToString('base64') - const fileBinary = await file.Body.transformToString('binary') const attachments: Attachment[] = [ { name: `Lögheimilisbreyting-barns-${applicant.nationalId}.pdf`, - content: fileB64, + content: fileContent.toString('base64'), }, ] @@ -149,7 +152,7 @@ export class ChildrenResidenceChangeService extends BaseTemplateApiService { await this.sharedTemplateAPIService.sendEmailWithAttachment( generateSyslumennNotificationEmail, application as unknown as Application, - fileBinary, + fileContent.toString('binary'), syslumennData.email, ) return undefined @@ -159,7 +162,7 @@ export class ChildrenResidenceChangeService extends BaseTemplateApiService { (props) => generateApplicationSubmittedEmail( props, - fileBinary, + fileContent.toString('binary'), answers.parentA.email, syslumennData.name, response?.caseNumber, @@ -171,7 +174,7 @@ export class ChildrenResidenceChangeService extends BaseTemplateApiService { (props) => generateApplicationSubmittedEmail( props, - fileBinary, + fileContent.toString('binary'), answers.parentB.email, syslumennData.name, response?.caseNumber, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/constants.ts b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/constants.ts deleted file mode 100644 index fc4d7a36b13d..000000000000 --- a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const PRESIGNED_BUCKET = 'PRESIGNED_BUCKET' diff --git a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/emailGenerators/applicationSubmitted.ts b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/emailGenerators/applicationSubmitted.ts index 8d2a80da2bdc..32f5a50d5ce9 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/emailGenerators/applicationSubmitted.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change/emailGenerators/applicationSubmitted.ts @@ -8,13 +8,12 @@ import { ulStyles, liStyles, } from './consts' -import { Address, Attachment } from 'nodemailer/lib/mailer' interface ApplicationSubmittedEmail { ( props: EmailTemplateGeneratorProps, - fileContent: Attachment['content'], - recipientEmail: Address['address'], + fileContent: string, + recipientEmail: string, syslumennName: string, caseNumber?: string, ): SendMailOptions diff --git a/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/attachments/attachment-s3.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/attachments/attachment-s3.service.ts new file mode 100644 index 000000000000..1be7582c8a7b --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/attachments/attachment-s3.service.ts @@ -0,0 +1,95 @@ +import { getValueViaPath } from '@island.is/application/core' +import { ApplicationWithAttachments as Application } from '@island.is/application/types' +import { S3 } from 'aws-sdk' +import AmazonS3URI from 'amazon-s3-uri' +import { logger } from '@island.is/logging' +import { Injectable } from '@nestjs/common' + +export interface AttachmentData { + key: string + answerKey: string + fileContent: string + fileName: string +} + +@Injectable() +export class AttachmentS3Service { + private readonly s3: AWS.S3 + constructor() { + this.s3 = new S3() + } + + public async getFiles( + application: Application, + attachmentAnswerKeys: string[], + ): Promise { + const attachments: AttachmentData[] = [] + + for (let i = 0; i < attachmentAnswerKeys.length; i++) { + const answers = getValueViaPath( + application.answers, + attachmentAnswerKeys[i], + ) as Array<{ + key: string + name: string + }> + if (!answers) continue + const list = await this.toDocumentDataList( + answers, + attachmentAnswerKeys[i], + application, + ) + attachments.push(...list) + } + return attachments + } + + private async toDocumentDataList( + answers: Array<{ + key: string + name: string + }>, + answerKey: string, + application: Application, + ): Promise { + return await Promise.all( + answers.map(async ({ key, name }) => { + const url = ( + application.attachments as { + [key: string]: string + } + )[key] + + if (!url) { + logger.info('Failed to get url from application state') + return { key: '', fileContent: '', answerKey, fileName: '' } + } + const fileContent = + (await this.getApplicationFilecontentAsBase64(url)) ?? '' + + return { key, fileContent, answerKey, fileName: name } + }), + ) + } + + private async getApplicationFilecontentAsBase64( + fileName: string, + ): Promise { + const { bucket, key } = AmazonS3URI(fileName) + const uploadBucket = bucket + try { + const file = await this.s3 + .getObject({ + Bucket: uploadBucket, + Key: key, + }) + .promise() + const fileContent = file.Body as Buffer + return fileContent?.toString('base64') + } catch (error) { + logger.log('error ', error) + logger.error(error) + return undefined + } + } +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/attachments/providers/applicationAttachmentProvider.ts b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/attachments/providers/applicationAttachmentProvider.ts index 4716432c8526..9b4fbc6e9958 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/attachments/providers/applicationAttachmentProvider.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/attachments/providers/applicationAttachmentProvider.ts @@ -1,7 +1,7 @@ import { ApplicationWithAttachments as Application } from '@island.is/application/types' import { DocumentInfo } from '@island.is/clients/data-protection-complaint' import { AttachmentType } from '../../models/attachments' -import { AttachmentS3Service } from '../../../../shared/services' +import { AttachmentS3Service } from '../attachment-s3.service' import { Injectable } from '@nestjs/common' export interface DocumentBuildInfo { diff --git a/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/data-protection-complaint.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/data-protection-complaint.module.ts index e8f81aaeac20..631204c5000c 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/data-protection-complaint.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/data-protection-complaint.module.ts @@ -13,8 +13,7 @@ import { ClientsDataProtectionComplaintModule } from '@island.is/clients/data-pr import { FileStorageModule } from '@island.is/file-storage' import { ApplicationAttachmentProvider } from './attachments/providers/applicationAttachmentProvider' import { PdfFileProvider } from './attachments/providers/pdfFileProvider' -import { AttachmentS3Service } from '../../shared/services' -import { AwsService } from '@island.is/nest/aws' +import { AttachmentS3Service } from './attachments/attachment-s3.service' export class DataProtectionComplaintModule { static register(config: BaseTemplateAPIModuleConfig): DynamicModule { @@ -26,7 +25,6 @@ export class DataProtectionComplaintModule { ClientsDataProtectionComplaintModule, ], providers: [ - AwsService, ApplicationAttachmentProvider, PdfFileProvider, AttachmentS3Service, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/directorate-of-immigration/citizenship/citizenship.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/directorate-of-immigration/citizenship/citizenship.service.ts index 8a7996ac0047..089b595ffc9b 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/directorate-of-immigration/citizenship/citizenship.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/directorate-of-immigration/citizenship/citizenship.service.ts @@ -306,8 +306,11 @@ export class CitizenshipService extends BaseTemplateApiService { } }) || [], isFormerIcelandicCitizen: answers.formerIcelander === YES, - givenName: individual?.givenName, - familyName: individual?.familyName, + givenName: + individual?.givenName || + individual?.fullName.split(' ').slice(0, -1).join(' '), //if given name is not available then remove last name and return the rest of the name as the given name + familyName: + individual?.familyName || individual?.fullName.split(' ').pop(), fullName: individual?.fullName, address: individual?.address?.streetAddress, postalCode: individual?.address?.postalCode, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/estate/estate.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/estate/estate.module.ts index 66f9df154677..21e42a341835 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/estate/estate.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/estate/estate.module.ts @@ -2,7 +2,6 @@ import { DynamicModule } from '@nestjs/common' import { SharedTemplateAPIModule } from '../../shared' import { SyslumennClientModule } from '@island.is/clients/syslumenn' -import { AwsService } from '@island.is/nest/aws' import { BaseTemplateAPIModuleConfig } from '../../../types' @@ -16,7 +15,7 @@ export class EstateTemplateModule { SharedTemplateAPIModule.register(config), SyslumennClientModule, ], - providers: [EstateTemplateService, AwsService], + providers: [EstateTemplateService], exports: [EstateTemplateService], } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/estate/estate.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/estate/estate.service.ts index 508da0bc5324..6cb7f6d60cd5 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/estate/estate.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/estate/estate.service.ts @@ -28,22 +28,24 @@ import { AttachmentPaths, ApplicationFile, } from './types/attachments' +import AmazonS3Uri from 'amazon-s3-uri' +import { S3 } from 'aws-sdk' import kennitala from 'kennitala' import { EstateTypes } from './consts' import { LOGGER_PROVIDER } from '@island.is/logging' import type { Logger } from '@island.is/logging' -import { AwsService } from '@island.is/nest/aws' type EstateSchema = zinfer @Injectable() export class EstateTemplateService extends BaseTemplateApiService { + s3: S3 constructor( @Inject(LOGGER_PROVIDER) private logger: Logger, private readonly syslumennService: SyslumennService, - private readonly awsService: AwsService, ) { super(ApplicationTypes.ESTATE) + this.s3 = new S3() } async estateProvider({ @@ -217,6 +219,7 @@ export class EstateTemplateService extends BaseTemplateApiService { const attachments: Attachment[] = [] + // Convert form data to a PDF backup for syslumenn const pdfBuffer = await transformUploadDataToPDFStream( pdfData, application.id, @@ -240,8 +243,7 @@ export class EstateTemplateService extends BaseTemplateApiService { const fileName = (application.attachments as ApplicationAttachments)[ attachmentAnswerData[index]?.key ] - const content = - (await this.awsService.getFileBase64({ s3Uri: fileName })) ?? '' + const content = await this.getFileContentBase64(fileName) attachments.push({ name, content }) } } @@ -266,6 +268,24 @@ export class EstateTemplateService extends BaseTemplateApiService { this.logger.error('[estate]: Failed to upload data - ', result.message) throw new Error('Application submission failed on syslumadur upload data') } - return { success: result.success, id: result.caseNumber } + return { sucess: result.success, id: result.caseNumber } + } + private async getFileContentBase64(fileName: string): Promise { + const { bucket, key } = AmazonS3Uri(fileName) + + const uploadBucket = bucket + try { + const file = await this.s3 + .getObject({ + Bucket: uploadBucket, + Key: key, + }) + .promise() + const fileContent = file.Body as Buffer + return fileContent?.toString('base64') || '' + } catch (e) { + this.logger.warn('[estate]: Failed to get file content - ', e) + return 'err' + } } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/estate/utils/fakeData.ts b/libs/application/template-api-modules/src/lib/modules/templates/estate/utils/fakeData.ts index ad7247877021..ebb19a1a799e 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/estate/utils/fakeData.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/estate/utils/fakeData.ts @@ -79,16 +79,22 @@ export const getFakeEstateInfo = ( name: 'Gervimaður Afríka', relation: 'Sonur', nationalId: '0101303019', + email: 'fake@email.com', + phone: '9999999', }, { name: 'Gervimaður Færeyjar', relation: 'Maki', nationalId: '0101302399', + email: 'fake2@email.com', + phone: '9999998', }, { name: 'Gervimaður Bretland', relation: 'Faðir', nationalId: '0101304929', + email: 'fake3@email.com', + phone: '9999997', }, ], caseNumber: `2020-00012${nationalIdOfDeceased.slice(-4)}`, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/estate/utils/mappers.ts b/libs/application/template-api-modules/src/lib/modules/templates/estate/utils/mappers.ts index 6e6cf78d09ac..af749f55140c 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/estate/utils/mappers.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/estate/utils/mappers.ts @@ -27,8 +27,8 @@ const estateMemberMapper = (element: EstateMember) => { ...element, initial: true, enabled: true, - phone: '', - email: '', + phone: element.phone ?? '', + email: element.email ?? '', relationWithApplicant: '', noContactInfo: ['No'] as ('Yes' | 'No')[], advocate: element.advocate diff --git a/libs/application/template-api-modules/src/lib/modules/templates/financial-statements-inao/financial-statements-inao.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/financial-statements-inao/financial-statements-inao.service.ts index 2a0850681091..1d21f87122b1 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/financial-statements-inao/financial-statements-inao.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/financial-statements-inao/financial-statements-inao.service.ts @@ -18,9 +18,14 @@ import { import * as kennitala from 'kennitala' import { TemplateApiModuleActionProps } from '../../../types' import { BaseTemplateApiService } from '../../base-template-api.service' -import { ApplicationTypes } from '@island.is/application/types' +import { + ApplicationTypes, + ApplicationWithAttachments as Application, +} from '@island.is/application/types' import { getValueViaPath } from '@island.is/application/core' import AmazonS3URI from 'amazon-s3-uri' + +import { S3 } from 'aws-sdk' import { mapValuesToIndividualtype, mapValuesToPartytype, @@ -28,7 +33,6 @@ import { } from './mappers/mapValuesToUsertype' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' -import { SharedTemplateApiService } from '../../shared' export interface AttachmentData { key: string @@ -52,12 +56,48 @@ export interface DataResponse { @Injectable() export class FinancialStatementsInaoTemplateService extends BaseTemplateApiService { + s3: S3 constructor( @Inject(LOGGER_PROVIDER) private logger: Logger, private financialStatementsClientService: FinancialStatementsInaoClientService, - private readonly sharedTemplateAPIService: SharedTemplateApiService, ) { super(ApplicationTypes.FINANCIAL_STATEMENTS_INAO) + this.s3 = new S3() + } + + private async getAttachment(application: Application): Promise { + const attachments: AttachmentData[] | undefined = getValueViaPath( + application.answers, + 'attachments.file', + ) as Array<{ key: string; name: string }> + + const attachmentKey = attachments[0].key + + const fileName = ( + application.attachments as { + [key: string]: string + } + )[attachmentKey] + + if (!fileName) { + return Promise.reject({}) + } + + const { bucket, key } = AmazonS3URI(fileName) + + const uploadBucket = bucket + try { + const file = await this.s3 + .getObject({ + Bucket: uploadBucket, + Key: key, + }) + .promise() + const fileContent = file.Body as Buffer + return fileContent?.toString('base64') || '' + } catch (error) { + throw new Error('Villa kom kom upp við að senda umsókn') + } } async getUserType({ auth }: TemplateApiModuleActionProps) { @@ -77,16 +117,6 @@ export class FinancialStatementsInaoTemplateService extends BaseTemplateApiServi const externalData = application.externalData const currentUserType = getCurrentUserType(answers, externalData) - const getAttachment = async (): Promise => { - const file = await this.sharedTemplateAPIService.getS3File( - application, - 'attachments.file', - 'base64', - ) - if (!file) throw new Error('Error submitting application') - return file - } - if (currentUserType === FSIUSERTYPE.INDIVIDUAL) { const electionIncomeLimit = getValueViaPath( answers, @@ -107,7 +137,9 @@ export class FinancialStatementsInaoTemplateService extends BaseTemplateApiServi ) as string const clientEmail = getValueViaPath(answers, 'about.email') as string - const fileName = noValueStatement ? undefined : await getAttachment() + const fileName = noValueStatement + ? undefined + : await this.getAttachment(application) this.logger.info( `PostFinancialStatementForPersonalElection => clientNationalId: '${nationalId}', actorNationalId: '${ @@ -199,7 +231,7 @@ export class FinancialStatementsInaoTemplateService extends BaseTemplateApiServi ) as string const clientEmail = getValueViaPath(answers, 'about.email') as string - const fileName = await getAttachment() + const fileName = await this.getAttachment(application) const client = { nationalId: nationalId, @@ -274,7 +306,7 @@ export class FinancialStatementsInaoTemplateService extends BaseTemplateApiServi const file = getValueViaPath(answers, 'attachments.file') - const fileName = file ? await getAttachment() : undefined + const fileName = file ? await this.getAttachment(application) : undefined const client = { nationalId: nationalId, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/bucket/bucket.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/bucket/bucket.service.ts new file mode 100644 index 000000000000..0617c09837fe --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/bucket/bucket.service.ts @@ -0,0 +1,45 @@ +import { Inject, Injectable } from '@nestjs/common' +import * as AWS from 'aws-sdk' +import * as S3 from 'aws-sdk/clients/s3' +import type { Logger } from '@island.is/logging' +import { LOGGER_PROVIDER } from '@island.is/logging' +import AmazonS3URI from 'amazon-s3-uri' + +@Injectable() +export class BucketService { + private s3 = new AWS.S3({ apiVersion: '2006-03-01' }) + constructor(@Inject(LOGGER_PROVIDER) private logger: Logger) {} + + async getFileContentAsBase64(filename: string): Promise { + this.logger.info('getFileContent base64...') + + const { region, bucket, key } = AmazonS3URI(filename) + const sm = await this.getFile(key, bucket) + if (sm.Body) { + this.logger.info('found file:' + key) + return sm.Body.toString('base64') + } else { + throw new Error('error getting file:' + key) + } + } + + async getFile( + filename: string, + bucketName: string, + ): Promise { + this.logger.info('get bucket file:' + filename) + return new Promise((resolve, reject) => { + const params = { + Bucket: bucketName, + Key: filename, + } + this.s3.getObject(params, function (err, data) { + if (err) { + reject(err) + } else { + resolve(data) + } + }) + }) + } +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.module.ts index 2549bfcee7fc..a6bdf74fd03a 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.module.ts @@ -10,7 +10,7 @@ import { BaseTemplateAPIModuleConfig } from '../../../types' // Here you import your module service import { HealthInsuranceService } from './health-insurance.service' import { HealthInsuranceV2ClientModule } from '@island.is/clients/icelandic-health-insurance/health-insurance' -import { AwsService } from '@island.is/nest/aws' +import { BucketService } from './bucket/bucket.service' export class HealthInsuranceModule { static register(config: BaseTemplateAPIModuleConfig): DynamicModule { @@ -20,7 +20,7 @@ export class HealthInsuranceModule { HealthInsuranceV2ClientModule, SharedTemplateAPIModule.register(config), ], - providers: [HealthInsuranceService, AwsService], + providers: [HealthInsuranceService, BucketService], exports: [HealthInsuranceService], } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.service.ts index 82eb0f903d46..fe5ff84e7a58 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.service.ts @@ -10,20 +10,20 @@ import { DocumentApi, PersonApi, } from '@island.is/clients/icelandic-health-insurance/health-insurance' +import { BucketService } from './bucket/bucket.service' import { BaseTemplateApiService } from '../../base-template-api.service' import { ApplicationTypes } from '@island.is/application/types' import format from 'date-fns/format' import is from 'date-fns/locale/is' import { TemplateApiError } from '@island.is/nest/problem' import { coreErrorMessages } from '@island.is/application/core/messages' -import { AwsService } from '@island.is/nest/aws' @Injectable() export class HealthInsuranceService extends BaseTemplateApiService { constructor( private documentApi: DocumentApi, + private bucketService: BucketService, private personApi: PersonApi, - private readonly awsService: AwsService, ) { super(ApplicationTypes.HEALTH_INSURANCE) } @@ -64,7 +64,7 @@ export class HealthInsuranceService extends BaseTemplateApiService { const xml = await insuranceToXML( inputs.vistaskjal, inputs.attachmentNames, - this.awsService, + this.bucketService, ) try { diff --git a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.utils.ts b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.utils.ts index 91d8695543ae..b8d26ffcf71f 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.utils.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.utils.ts @@ -5,6 +5,7 @@ import { logger } from '@island.is/logging' import { ApplicationWithAttachments as Application } from '@island.is/application/types' import { objectToXML } from '../../shared/shared.utils' import is from 'date-fns/locale/is' +import { BucketService } from './bucket/bucket.service' import format from 'date-fns/format' import { ApplyHealthInsuranceInputs, @@ -13,7 +14,6 @@ import { GetVistaSkjalBody, VistaSkjalInput, } from './types/health-insurance-types' -import { AwsService } from '@island.is/nest/aws' const formatDate = (date: Date) => { return format(new Date(date), 'yyyy-MM-dd', { @@ -34,7 +34,7 @@ const formatDate = (date: Date) => { export const insuranceToXML = async ( inputObj: VistaSkjalInput, attachmentNames: string[], - aws: AwsService, + bucketService: BucketService, ) => { logger.debug(`--- Starting to convert application to XML ---`) const vistaSkjalBody: GetVistaSkjalBody = { @@ -93,11 +93,11 @@ export const insuranceToXML = async ( } for (let i = 0; i < arrAttachments.length; i++) { const filename = arrAttachments[i] - const file = await aws.getFileBase64({ s3Uri: attachmentNames[i] }) - if (!file) throw new Error('error getting file') const fylgiskjal: Fylgiskjal = { heiti: filename, - innihald: file, + innihald: await bucketService.getFileContentAsBase64( + attachmentNames[i], + ), } fylgiskjol.fylgiskjal.push(fylgiskjal) } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/index.ts b/libs/application/template-api-modules/src/lib/modules/templates/index.ts index 375f2bc3a8ab..01eab1f29940 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/index.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/index.ts @@ -140,6 +140,8 @@ import { NewPrimarySchoolService } from './new-primary-school/new-primary-school import { IdCardModule } from './id-card/id-card.module' import { IdCardService } from './id-card/id-card.service' +import { ParliamentaryListCreationModule } from './signature-collection/parliamentary-list-creation/parliamentary-list-creation.module' +import { ParliamentaryListCreationService } from './signature-collection/parliamentary-list-creation/parliamentary-list-creation.service' export const modules = [ ReferenceTemplateModule, @@ -210,6 +212,7 @@ export const modules = [ IdCardModule, HealthInsuranceDeclarationModule, NewPrimarySchoolModule, + ParliamentaryListCreationModule, ] export const services = [ @@ -281,4 +284,5 @@ export const services = [ IdCardService, HealthInsuranceDeclarationService, NewPrimarySchoolService, + ParliamentaryListCreationService, ] diff --git a/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/fakeData.ts b/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/fakeData.ts index 63e59315e041..705ad7e7dded 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/fakeData.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/fakeData.ts @@ -67,6 +67,7 @@ const generateRandomHeir = ( } : {}), email: name.split(' ').at(-1) + '@gervimadur.com', + phone: '9999999', enabled: true, relationWithApplicant: ['Bróðir', 'Systir', 'Móðir', 'Faðir'].sort( () => 0.5 - Math.random(), diff --git a/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts b/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts index d5eb3dfc9e6d..b248ea6462c9 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts @@ -105,16 +105,16 @@ export const expandAnswers = ( prepaidInheritance: answers.prepaidInheritance, executors: { executor: { - email: '', - phone: '', - name: '', - nationalId: '', + email: answers.executors?.executor.email ?? '', + phone: answers.executors?.executor.phone ?? '', + name: answers.executors?.executor.name ?? '', + nationalId: answers.executors?.executor.nationalId ?? '', }, spouse: { - email: '', - phone: '', - name: '', - nationalId: '', + email: answers.executors?.spouse?.email ?? '', + phone: answers.executors?.spouse?.phone ?? '', + name: answers.executors?.spouse?.name ?? '', + nationalId: answers.executors?.spouse?.nationalId ?? '', }, includeSpouse: undefined, }, @@ -234,6 +234,9 @@ export const expandAnswers = ( }, caseNumber: answers.estateInfoSelection, confirmAction: answers.confirmAction, + assetsConfirmation: answers.assetsConfirmation, + debtsConfirmation: answers.debtsConfirmation, + heirsConfirmation: answers.heirsConfirmation, debts: { debtsTotal: answers?.debts?.debtsTotal ?? 0, domesticAndForeignDebts: { diff --git a/libs/application/template-api-modules/src/lib/modules/templates/operating-license/operatingLicense.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/operating-license/operatingLicense.module.ts index 37b57ee14028..4610e3de6d46 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/operating-license/operatingLicense.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/operating-license/operatingLicense.module.ts @@ -6,7 +6,6 @@ import { SyslumennClientModule } from '@island.is/clients/syslumenn' import { CriminalRecordModule } from '@island.is/api/domains/criminal-record' import { FinanceClientModule } from '@island.is/clients/finance' import { JudicialAdministrationClientModule } from '@island.is/clients/judicial-administration' -import { AwsService } from '@island.is/nest/aws' export class OperatingLicenseModule { static register(config: BaseTemplateAPIModuleConfig): DynamicModule { @@ -19,7 +18,7 @@ export class OperatingLicenseModule { FinanceClientModule, JudicialAdministrationClientModule, ], - providers: [OperatingLicenseService, AwsService], + providers: [OperatingLicenseService], exports: [OperatingLicenseService], } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/operating-license/operatingLicense.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/operating-license/operatingLicense.service.ts index ab8c4725d070..ceb663cbdda2 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/operating-license/operatingLicense.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/operating-license/operatingLicense.service.ts @@ -1,9 +1,10 @@ -import { Inject, Injectable } from '@nestjs/common' +import { Injectable } from '@nestjs/common' import { SharedTemplateApiService } from '../../shared' import { TemplateApiModuleActionProps } from '../../../types' import { coreErrorMessages, getValueViaPath } from '@island.is/application/core' import AmazonS3URI from 'amazon-s3-uri' +import { S3 } from 'aws-sdk' import { SyslumennService, Person, @@ -21,6 +22,7 @@ import { import { ApplicationTypes, ApplicationWithAttachments, + InstitutionNationalIds, YES, } from '@island.is/application/types' import { Info, BankruptcyHistoryResult } from './types/application' @@ -35,23 +37,18 @@ import { BANNED_BANKRUPTCY_STATUSES } from './constants' import { error } from '@island.is/application/templates/operating-license' import { isPerson } from 'kennitala' import { User } from '@island.is/auth-nest-tools' -import { AwsService } from '@island.is/nest/aws' -import { LOGGER_PROVIDER } from '@island.is/logging' -import type { Logger } from '@island.is/logging' - @Injectable() export class OperatingLicenseService extends BaseTemplateApiService { + s3: S3 constructor( private readonly sharedTemplateAPIService: SharedTemplateApiService, private readonly syslumennService: SyslumennService, private readonly criminalRecordService: CriminalRecordService, private readonly financeService: FinanceClientService, private readonly judicialAdministrationService: JudicialAdministrationService, - private readonly awsService: AwsService, - @Inject(LOGGER_PROVIDER) - private readonly logger: Logger, ) { super(ApplicationTypes.OPERATING_LICENSE) + this.s3 = new S3() } async criminalRecord({ @@ -337,12 +334,17 @@ export class OperatingLicenseService extends BaseTemplateApiService { private async getFileContentBase64(fileName: string): Promise { const { bucket, key } = AmazonS3URI(fileName) + const uploadBucket = bucket try { - return ( - (await this.awsService.getFileBase64({ bucket, fileName: key })) ?? '' - ) + const file = await this.s3 + .getObject({ + Bucket: uploadBucket, + Key: key, + }) + .promise() + const fileContent = file.Body as Buffer + return fileContent?.toString('base64') || '' } catch (e) { - this.logger.error('Error getting file', { error: e }) return 'err' } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/p-sign-submission/p-sign-submission.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/p-sign-submission/p-sign-submission.module.ts index bc7551efbf3a..1ec4008ac711 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/p-sign-submission/p-sign-submission.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/p-sign-submission/p-sign-submission.module.ts @@ -3,7 +3,6 @@ import { SyslumennClientModule } from '@island.is/clients/syslumenn' import { PSignSubmissionService } from './p-sign-submission.service' import { SharedTemplateAPIModule } from '../../shared' import { BaseTemplateAPIModuleConfig } from '../../../types' -import { AwsService } from '@island.is/nest/aws' export class PSignSubmissionModule { static register(config: BaseTemplateAPIModuleConfig): DynamicModule { @@ -13,7 +12,7 @@ export class PSignSubmissionModule { SyslumennClientModule, SharedTemplateAPIModule.register(config), ], - providers: [PSignSubmissionService, AwsService], + providers: [PSignSubmissionService], exports: [PSignSubmissionService], } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/p-sign-submission/p-sign-submission.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/p-sign-submission/p-sign-submission.service.ts index 88c286cd84e6..aacee2aea86a 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/p-sign-submission/p-sign-submission.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/p-sign-submission/p-sign-submission.service.ts @@ -16,13 +16,12 @@ import { } from '@island.is/application/types' import AmazonS3URI from 'amazon-s3-uri' +import { S3 } from 'aws-sdk' import { SharedTemplateApiService } from '../../shared' import { BaseTemplateApiService } from '../../base-template-api.service' import { TemplateApiError } from '@island.is/nest/problem' import { LOGGER_PROVIDER } from '@island.is/logging' import type { Logger } from '@island.is/logging' -import { AwsService } from '@island.is/nest/aws' -import { AttachmentS3Service } from '../../shared/services' export const QUALITY_PHOTO = ` query HasQualityPhoto { @@ -60,13 +59,14 @@ type Delivery = { const YES = 'yes' @Injectable() export class PSignSubmissionService extends BaseTemplateApiService { + s3: S3 constructor( @Inject(LOGGER_PROVIDER) private logger: Logger, private readonly syslumennService: SyslumennService, private readonly sharedTemplateAPIService: SharedTemplateApiService, - private readonly awsService: AwsService, ) { super(ApplicationTypes.P_SIGN) + this.s3 = new S3() } async doctorsNote({ @@ -187,13 +187,11 @@ export class PSignSubmissionService extends BaseTemplateApiService { private async getAttachments({ application, }: TemplateApiModuleActionProps): Promise { - const attacher = new AttachmentS3Service(this.awsService) - const attachments = await attacher.getFiles( - application, + const attachments = getValueViaPath( + application.answers, 'photo.attachments', - ) - - const hasAttachments = attachments && attachments.length > 0 + ) as Array<{ key: string; name: string }> + const hasAttachments = attachments && attachments?.length > 0 if (!hasAttachments) { return Promise.reject({}) @@ -206,6 +204,16 @@ export class PSignSubmissionService extends BaseTemplateApiService { } )[attachmentKey] - return (await this.awsService.getFileBase64({ s3Uri: fileName })) ?? '' + const { bucket, key } = AmazonS3URI(fileName) + + const uploadBucket = bucket + const file = await this.s3 + .getObject({ + Bucket: uploadBucket, + Key: key, + }) + .promise() + const fileContent = file.Body as Buffer + return fileContent?.toString('base64') || '' } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.module.ts index 0f532db2fd7b..2221e0f1950a 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.module.ts @@ -11,7 +11,6 @@ import { NationalRegistryClientService, } from '@island.is/clients/national-registry-v2' import { APPLICATION_ATTACHMENT_BUCKET } from './constants' -import { AwsService } from '@island.is/nest/aws' export class ParentalLeaveModule { static register(config: BaseTemplateAPIModuleConfig): DynamicModule { @@ -25,7 +24,6 @@ export class ParentalLeaveModule { NationalRegistryClientModule, ], providers: [ - AwsService, ChildrenService, ParentalLeaveService, NationalRegistryClientService, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.service.spec.ts b/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.service.spec.ts index fe2e7ccae84e..92524c696bbd 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.service.spec.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.service.spec.ts @@ -42,7 +42,6 @@ import { SmsService } from '@island.is/nova-sms' import { ChildrenService } from './children/children.service' import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2' import { PaymentService } from '@island.is/application/api/payment' -import { AwsService } from '@island.is/nest/aws' const nationalId = '1234564321' let id = 0 @@ -240,7 +239,6 @@ describe('ParentalLeaveService', () => { provide: APPLICATION_ATTACHMENT_BUCKET, useValue: 'attachmentBucket', }, - AwsService, ], }).compile() diff --git a/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.service.ts index 4c5fcbb7a2cf..7e84e1d6cff8 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/parental-leave/parental-leave.service.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common' +import { S3 } from 'aws-sdk' import addDays from 'date-fns/addDays' import format from 'date-fns/format' import cloneDeep from 'lodash/cloneDeep' @@ -82,7 +83,6 @@ import { generateEmployerRejectedApplicationSms, generateOtherParentRejectedApplicationSms, } from './smsGenerators' -import { AwsService } from '@island.is/nest/aws' interface VMSTError { type: string @@ -105,6 +105,8 @@ interface AnswerPeriod { @Injectable() export class ParentalLeaveService extends BaseTemplateApiService { + s3 = new S3() + constructor( @Inject(LOGGER_PROVIDER) private logger: Logger, private parentalLeaveApi: ParentalLeaveApi, @@ -115,7 +117,6 @@ export class ParentalLeaveService extends BaseTemplateApiService { private readonly configService: ConfigService, private readonly childrenService: ChildrenService, private readonly nationalRegistryApi: NationalRegistryClientService, - private readonly awsService: AwsService, ) { super(ApplicationTypes.PARENTAL_LEAVE) } @@ -386,16 +387,16 @@ export class ParentalLeaveService extends BaseTemplateApiService { ) const Key = `${application.id}/${filename}` - const file = await this.awsService.getFileBase64({ - bucket: this.attachmentBucket, - fileName: Key, - }) + const file = await this.s3 + .getObject({ Bucket: this.attachmentBucket, Key }) + .promise() + const fileContent = file.Body as Buffer - if (!file) { + if (!fileContent) { throw new Error('File content was undefined') } - return file + return fileContent.toString('base64') } catch (e) { this.logger.error('Cannot get ' + fileUpload + ' attachment', { e }) throw new Error('Failed to get the ' + fileUpload + ' attachment') @@ -433,7 +434,7 @@ export class ParentalLeaveService extends BaseTemplateApiService { state === States.RESIDENCE_GRANT_APPLICATION ) { if (residenceGrantFiles) { - residenceGrantFiles.forEach(async (_item, index) => { + residenceGrantFiles.forEach(async (item, index) => { const pdf = await this.getPdf( application, index, @@ -448,7 +449,7 @@ export class ParentalLeaveService extends BaseTemplateApiService { } if (changeEmployerFile) { - changeEmployerFile.forEach(async (_item, index) => { + changeEmployerFile.forEach(async (item, index) => { const pdf = await this.getPdf( application, index, @@ -464,7 +465,7 @@ export class ParentalLeaveService extends BaseTemplateApiService { // We don't want to send old files to VMST again if (applicationFundId && applicationFundId !== '') { if (additionalDocuments) { - additionalDocuments.forEach(async (_val, i) => { + additionalDocuments.forEach(async (val, i) => { const pdf = await this.getPdf( application, i, @@ -1391,7 +1392,10 @@ export class ParentalLeaveService extends BaseTemplateApiService { return periods } - async sendApplication({ application }: TemplateApiModuleActionProps) { + async sendApplication({ + application, + params = undefined, + }: TemplateApiModuleActionProps) { const { isSelfEmployed, isReceivingUnemploymentBenefits, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/emailGenerators/applicationSubmitted.ts b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/emailGenerators/applicationSubmitted.ts new file mode 100644 index 000000000000..c6d5bd1d7cf6 --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/emailGenerators/applicationSubmitted.ts @@ -0,0 +1,65 @@ +import { EmailTemplateGenerator } from '../../../../../types' +import { CreateListSchema } from '@island.is/application/templates/signature-collection/presidential-list-creation' +import { OwnerInput } from '@island.is/clients/signature-collection' +import { SignatureCollection } from '../types' +import { Message, Body } from '@island.is/email-service' + +export const generateApplicationSubmittedEmail: EmailTemplateGenerator = ( + props, +): Message => { + const { + application, + options: { email }, + } = props + + const answers = application.answers as CreateListSchema + + const owner: OwnerInput = answers.applicant + const currentCollection: SignatureCollection = application.externalData + .currentCollection?.data as SignatureCollection + + const subject = 'Ný meðmælasöfnun hefur verið stofnuð' + const lists: Body[] = currentCollection.areas.map((area) => { + return { + component: 'Copy', + context: { copy: `${owner.name} - ${area.name}` }, + } + }) + + return { + from: { + name: email.sender, + address: email.address, + }, + to: [ + { + name: 'Landskjörstjórn', + address: 'postur@landskjorstjorn.is', + }, + { + name: 'Þjóðskrá', + address: 'kosningar@skra.is', + }, + ], + subject, + template: { + title: subject, + body: [ + { component: 'Heading', context: { copy: subject } }, + { + component: 'Copy', + context: { + copy: `${owner.name} Kt: ${owner.nationalId} hefur stofnað eftirfarandi lista til meðmælasöfnunar:`, + }, + }, + ...lists, + { + component: 'Copy', + context: { + copy: `Samskiptaupplýsingar framboðs. Sími ${owner.phone} Netfang: ${owner.email} `, + }, + }, + ], + }, + } +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/emailGenerators/index.ts b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/emailGenerators/index.ts new file mode 100644 index 000000000000..68d597add205 --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/emailGenerators/index.ts @@ -0,0 +1 @@ +export * from './applicationSubmitted' diff --git a/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/parliamentary-list-creation.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/parliamentary-list-creation.module.ts new file mode 100644 index 000000000000..111c2917eabb --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/parliamentary-list-creation.module.ts @@ -0,0 +1,26 @@ +import { DynamicModule } from '@nestjs/common' + +// This is a shared module that gives you access to common methods +import { SharedTemplateAPIModule } from '../../../shared' + +// The base config that template api modules are registered with by default +// (configurable inside `template-api.module.ts`) +import { BaseTemplateAPIModuleConfig } from '../../../../types' + +// Here you import your module service +import { ParliamentaryListCreationService } from './parliamentary-list-creation.service' +import { SignatureCollectionClientModule } from '@island.is/clients/signature-collection' + +export class ParliamentaryListCreationModule { + static register(config: BaseTemplateAPIModuleConfig): DynamicModule { + return { + module: ParliamentaryListCreationModule, + imports: [ + SharedTemplateAPIModule.register(config), + SignatureCollectionClientModule, + ], + providers: [ParliamentaryListCreationService], + exports: [ParliamentaryListCreationService], + } + } +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/parliamentary-list-creation.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/parliamentary-list-creation.service.ts new file mode 100644 index 000000000000..e528ac393161 --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/parliamentary-list-creation.service.ts @@ -0,0 +1,112 @@ +import { Inject, Injectable } from '@nestjs/common' +import { SharedTemplateApiService } from '../../../shared' +import { TemplateApiModuleActionProps } from '../../../../types' +import { BaseTemplateApiService } from '../../../base-template-api.service' +import { ApplicationTypes } from '@island.is/application/types' +import { + CreateParliamentaryCandidacyInput, + MandateType, + ReasonKey, + SignatureCollectionClientService, +} from '@island.is/clients/signature-collection' +import { errorMessages } from '@island.is/application/templates/signature-collection/parliamentary-list-creation' +import { TemplateApiError } from '@island.is/nest/problem' +import type { Logger } from '@island.is/logging' +import { LOGGER_PROVIDER } from '@island.is/logging' +import { CollectionType } from '@island.is/clients/signature-collection' +import { CreateListSchema } from '@island.is/application/templates/signature-collection/parliamentary-list-creation' +import { FetchError } from '@island.is/clients/middlewares' + +@Injectable() +export class ParliamentaryListCreationService extends BaseTemplateApiService { + constructor( + @Inject(LOGGER_PROVIDER) private logger: Logger, + private readonly _sharedTemplateAPIService: SharedTemplateApiService, + private signatureCollectionClientService: SignatureCollectionClientService, + ) { + super(ApplicationTypes.PARLIAMENTARY_LIST_CREATION) + } + + async candidate({ auth }: TemplateApiModuleActionProps) { + const candidate = await this.signatureCollectionClientService.getSignee( + auth, + ) + + if (!candidate.hasPartyBallotLetter) { + throw new TemplateApiError(errorMessages.partyBallotLetter, 405) + } + + return candidate + } + + async parliamentaryCollection({ auth }: TemplateApiModuleActionProps) { + const currentCollection = + await this.signatureCollectionClientService.currentCollection() + if (currentCollection.collectionType !== CollectionType.Parliamentary) { + throw new TemplateApiError( + errorMessages.currentCollectionNotParliamentary, + 405, + ) + } + + const candidateInCollection = currentCollection.candidates.some( + (candidate) => candidate.nationalId === auth.nationalId, + ) + + if (!candidateInCollection) { + throw new TemplateApiError(errorMessages.partyBallotLetter, 405) + } + + return currentCollection + } + + async submit({ application, auth }: TemplateApiModuleActionProps) { + const answers = application.answers as CreateListSchema + + const input: CreateParliamentaryCandidacyInput = { + owner: answers.applicant, + agents: answers.managers + .map((manager) => ({ + nationalId: manager.manager.nationalId, + phoneNumber: '', + mandateType: MandateType.Guarantor, + email: '', + areas: answers.constituency.map((constituency) => { + const [id, name] = constituency.split('|') + return { + areaId: id, + } + }), + })) + .concat( + answers.supervisors.map((supervisor) => ({ + nationalId: supervisor.supervisor.nationalId, + phoneNumber: '', + mandateType: MandateType.Administrator, + email: '', + areas: supervisor.constituency.map((constituency) => { + const [id, name] = constituency.split('|') + return { + areaId: id, + } + }), + })), + ), + // TODO: determine collectionID + collectionId: '', + } + + const result = await this.signatureCollectionClientService + .createParliamentaryCandidacy(input, auth) + .catch((e: FetchError) => { + return { + success: false, + message: e.message, + } + }) + + return { + success: true, + } + } +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/types.ts b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/types.ts new file mode 100644 index 000000000000..89d1767e0e24 --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/signature-collection/parliamentary-list-creation/types.ts @@ -0,0 +1,12 @@ +export type Area = { + id: string + name: string + min: number +} +export type SignatureCollection = { + areas: Area[] + endTime: Date + id: string + name: string + startTime: Date +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration-utils.ts b/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration-utils.ts index dd15e788eecd..2f2bb8266982 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration-utils.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration-utils.ts @@ -4,6 +4,7 @@ import { ApplicationDTO, TrWebCommonsExternalPortalsApiModelsDocumentsDocument as Attachment, Employer as TrWebEmployer, + IncomeTypes, } from '@island.is/clients/social-insurance-administration' import { ApplicationType, @@ -34,6 +35,12 @@ import { getApplicationExternalData as getPSApplicationExternalData, } from '@island.is/application/templates/social-insurance-administration/pension-supplement' +import { + INCOME, + getApplicationAnswers as getIPApplicationAnswers, + getApplicationExternalData as getIPApplicationExternalData, +} from '@island.is/application/templates/social-insurance-administration/income-plan' + export const transformApplicationToOldAgePensionDTO = ( application: Application, uploads: Attachment[], @@ -298,6 +305,87 @@ export const transformApplicationToPensionSupplementDTO = ( return pensionSupplementDTO } +export const transformApplicationToIncomePlanDTO = ( + application: Application, +): ApplicationDTO => { + const { userProfileEmail, userProfilePhoneNumber, incomePlanConditions } = + getIPApplicationExternalData(application.externalData) + + const incomePlanDTO: ApplicationDTO = { + applicantInfo: { + email: userProfileEmail, + phonenumber: userProfilePhoneNumber, + }, + period: { + year: new Date().getFullYear(), + month: new Date().getMonth(), + }, + applicationId: application.id, + incomePlan: { + incomeYear: incomePlanConditions.incomePlanYear, + incomeTypes: getIncomeTypes(application), + }, + } + + return incomePlanDTO +} + +export const getIncomeTypes = (application: Application): IncomeTypes[] => { + const { incomePlan } = getIPApplicationAnswers(application.answers) + const { categorizedIncomeTypes } = getIPApplicationExternalData( + application.externalData, + ) + + return incomePlan.map((i) => ({ + incomeTypeNumber: + categorizedIncomeTypes.find((c) => c.incomeTypeName === i.incomeType) + ?.incomeTypeNumber ?? 0, + incomeTypeCode: + categorizedIncomeTypes.find((c) => c.incomeTypeName === i.incomeType) + ?.incomeTypeCode ?? '', + incomeTypeName: i.incomeType, + currencyCode: i.currency, + incomeCategoryNumber: + categorizedIncomeTypes.find((c) => c.incomeTypeName === i.incomeType) + ?.categoryNumber ?? 0, + incomeCategoryCode: + categorizedIncomeTypes.find((c) => c.incomeTypeName === i.incomeType) + ?.categoryCode ?? '', + incomeCategoryName: i.incomeCategory, + ...(i.income === RatioType.MONTHLY && + i?.incomeCategory === INCOME && + i?.unevenIncomePerYear?.[0] === YES + ? { + amountJan: Number(i.january), + amountFeb: Number(i.february), + amountMar: Number(i.march), + amountApr: Number(i.april), + amountMay: Number(i.may), + amountJun: Number(i.june), + amountJul: Number(i.july), + amountAug: Number(i.august), + amountSep: Number(i.september), + amountOct: Number(i.october), + amountNov: Number(i.november), + amountDec: Number(i.december), + } + : { + amountJan: Number(i.incomePerYear) / 12, + amountFeb: Number(i.incomePerYear) / 12, + amountMar: Number(i.incomePerYear) / 12, + amountApr: Number(i.incomePerYear) / 12, + amountMay: Number(i.incomePerYear) / 12, + amountJun: Number(i.incomePerYear) / 12, + amountJul: Number(i.incomePerYear) / 12, + amountAug: Number(i.incomePerYear) / 12, + amountSep: Number(i.incomePerYear) / 12, + amountOct: Number(i.incomePerYear) / 12, + amountNov: Number(i.incomePerYear) / 12, + amountDec: Number(i.incomePerYear) / 12, + }), + })) +} + export const getMonthNumber = (monthName: string): number => { // Parse the month name and get the month number (0-based) const monthNumber = parse(monthName, 'MMMM', new Date()) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.module.ts index 246577d402e4..69e9067c328b 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.module.ts @@ -9,7 +9,6 @@ import { import { ApplicationApiCoreModule } from '@island.is/application/api/core' import { NationalRegistryClientModule } from '@island.is/clients/national-registry-v2' import { SocialInsuranceAdministrationClientModule } from '@island.is/clients/social-insurance-administration' -import { AwsService } from '@island.is/nest/aws' export class SocialInsuranceAdministrationModule { static register(config: BaseTemplateAPIModuleConfig): DynamicModule { @@ -22,7 +21,6 @@ export class SocialInsuranceAdministrationModule { NationalRegistryClientModule, ], providers: [ - AwsService, SocialInsuranceAdministrationService, { provide: APPLICATION_ATTACHMENT_BUCKET, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.service.spec.ts b/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.service.spec.ts index 012ad14e3e44..5a33615c9ede 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.service.spec.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.service.spec.ts @@ -8,7 +8,6 @@ import { import { createCurrentUser } from '@island.is/testing/fixtures' import { LOGGER_PROVIDER, logger } from '@island.is/logging' import { ApplicationTypes } from '@island.is/application/types' -import { AwsService } from '@island.is/nest/aws' describe('SocialInsuranceAdministrationService', () => { let socialInsuranceAdministrationService: SocialInsuranceAdministrationService @@ -34,7 +33,6 @@ describe('SocialInsuranceAdministrationService', () => { provide: APPLICATION_ATTACHMENT_BUCKET, useValue: 'attachmentBucket', }, - AwsService, ], }).compile() diff --git a/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.service.ts index ffe34c0e2345..258b8d42d022 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/social-insurance-administration/social-insurance-administration.service.ts @@ -1,51 +1,54 @@ -import { Inject, Injectable } from '@nestjs/common' +import { FileType } from '@island.is/application/templates/social-insurance-administration-core/types' +import { getApplicationAnswers as getASFTEApplicationAnswers } from '@island.is/application/templates/social-insurance-administration/additional-support-for-the-elderly' import { - Application, - ApplicationTypes, - YES, -} from '@island.is/application/types' -import type { Logger } from '@island.is/logging' -import { LOGGER_PROVIDER } from '@island.is/logging' -import { TemplateApiModuleActionProps } from '../../../types' -import { BaseTemplateApiService } from '../../base-template-api.service' + HouseholdSupplementHousing, + getApplicationAnswers as getHSApplicationAnswers, +} from '@island.is/application/templates/social-insurance-administration/household-supplement' import { ApplicationType, Employment, getApplicationAnswers as getOAPApplicationAnswers, isEarlyRetirement, } from '@island.is/application/templates/social-insurance-administration/old-age-pension' -import { - HouseholdSupplementHousing, - getApplicationAnswers as getHSApplicationAnswers, -} from '@island.is/application/templates/social-insurance-administration/household-supplement' import { getApplicationAnswers as getPSApplicationAnswers } from '@island.is/application/templates/social-insurance-administration/pension-supplement' -import { getApplicationAnswers as getASFTEApplicationAnswers } from '@island.is/application/templates/social-insurance-administration/additional-support-for-the-elderly' import { + Application, + ApplicationTypes, + YES, +} from '@island.is/application/types' +import { + ApiProtectedV1IncomePlanWithholdingTaxGetRequest, TrWebCommonsExternalPortalsApiModelsDocumentsDocument as Attachment, DocumentTypeEnum, SocialInsuranceAdministrationClientService, } from '@island.is/clients/social-insurance-administration' +import type { Logger } from '@island.is/logging' +import { LOGGER_PROVIDER } from '@island.is/logging' +import { isRunningOnEnvironment } from '@island.is/shared/utils' +import { Inject, Injectable } from '@nestjs/common' +import { S3 } from 'aws-sdk' +import { TemplateApiModuleActionProps } from '../../../types' +import { BaseTemplateApiService } from '../../base-template-api.service' import { getApplicationType, + transformApplicationToAdditionalSupportForTheElderlyDTO, transformApplicationToHouseholdSupplementDTO, + transformApplicationToIncomePlanDTO, transformApplicationToOldAgePensionDTO, transformApplicationToPensionSupplementDTO, - transformApplicationToAdditionalSupportForTheElderlyDTO, } from './social-insurance-administration-utils' -import { isRunningOnEnvironment } from '@island.is/shared/utils' -import { FileType } from '@island.is/application/templates/social-insurance-administration-core/types' -import { AwsService } from '@island.is/nest/aws' export const APPLICATION_ATTACHMENT_BUCKET = 'APPLICATION_ATTACHMENT_BUCKET' @Injectable() export class SocialInsuranceAdministrationService extends BaseTemplateApiService { + s3 = new S3() + constructor( @Inject(LOGGER_PROVIDER) private logger: Logger, private siaClientService: SocialInsuranceAdministrationClientService, @Inject(APPLICATION_ATTACHMENT_BUCKET) private readonly attachmentBucket: string, - private readonly awsService: AwsService, ) { super('SocialInsuranceAdministration') } @@ -374,15 +377,16 @@ export class SocialInsuranceAdministrationService extends BaseTemplateApiService } async getPdf(key: string) { - this.logger.debug('Getting pdf', { key }) - const file = await this.awsService.getFileBase64({ - bucket: this.attachmentBucket, - fileName: key, - }) - if (!file) { + const file = await this.s3 + .getObject({ Bucket: this.attachmentBucket, Key: key }) + .promise() + const fileContent = file.Body as Buffer + + if (!fileContent) { throw new Error('File content was undefined') } - return file + + return fileContent.toString('base64') } async sendApplication({ application, auth }: TemplateApiModuleActionProps) { @@ -452,6 +456,18 @@ export class SocialInsuranceAdministrationService extends BaseTemplateApiService ) return response } + + if (application.typeId === ApplicationTypes.INCOME_PLAN) { + const incomePlanDTO = transformApplicationToIncomePlanDTO(application) + + const response = await this.siaClientService.sendApplication( + auth, + incomePlanDTO, + application.typeId.toLowerCase(), + ) + + return response + } } async sendDocuments({ application, auth }: TemplateApiModuleActionProps) { @@ -507,4 +523,23 @@ export class SocialInsuranceAdministrationService extends BaseTemplateApiService async getCurrencies({ auth }: TemplateApiModuleActionProps) { return await this.siaClientService.getCurrencies(auth) } + + async getCategorizedIncomeTypes({ auth }: TemplateApiModuleActionProps) { + return await this.siaClientService.getCategorizedIncomeTypes(auth) + } + + async getWithholdingTax( + { auth }: TemplateApiModuleActionProps, + year: ApiProtectedV1IncomePlanWithholdingTaxGetRequest = {}, + ) { + return await this.siaClientService.getWithholdingTax(auth, year) + } + + async getLatestIncomePlan({ auth }: TemplateApiModuleActionProps) { + return await this.siaClientService.getLatestIncomePlan(auth) + } + + async getIncomePlanConditions({ auth }: TemplateApiModuleActionProps) { + return await this.siaClientService.getIncomePlanConditions(auth) + } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts index cd031e996d63..3405f7851e79 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts @@ -136,8 +136,7 @@ export class UniversityService extends BaseTemplateApiService { const exemptionData = educationOptionChosen === UniversityApplicationTypes.EXEMPTION ? { - degreeAttachments: await this.getFilesFromAttachment( - application, + degreeAttachments: await this.getAttachmentUrls( answers.educationDetails.exemptionDetails?.degreeAttachments?.map( (x, i) => { const type = this.mapFileTypes(i) @@ -167,8 +166,7 @@ export class UniversityService extends BaseTemplateApiService { degreeEndDate: answers.educationDetails.thirdLevelDetails?.endDate, moreDetails: answers.educationDetails.thirdLevelDetails?.moreDetails, - degreeAttachments: await this.getFilesFromAttachment( - application, + degreeAttachments: await this.getAttachmentUrls( answers.educationDetails.thirdLevelDetails?.degreeAttachments?.map( (x, i) => { const type = this.mapFileTypes(i) @@ -187,8 +185,7 @@ export class UniversityService extends BaseTemplateApiService { answers.educationDetails.finishedDetails.map(async (item) => { return { ...item, - degreeAttachments: await this.getFilesFromAttachment( - application, + degreeAttachments: await this.getAttachmentUrls( item.degreeAttachments?.map((x, i) => { const type = this.mapFileTypes(i) return { @@ -258,21 +255,20 @@ export class UniversityService extends BaseTemplateApiService { ).universityApplicationControllerCreateApplication(createApplicationDto) } - private async getFilesFromAttachment( - application: ApplicationWithAttachments, + private async getAttachmentUrls( attachments?: { name: string; key: string; type: string }[], - ): Promise<{ fileName: string; fileType: string; blob: Blob }[]> { + ): Promise<{ fileName: string; fileType: string; url: string }[]> { + const expiry = 36000 + return await Promise.all( attachments?.map(async (file) => { - const blob = - await this.sharedTemplateAPIService.getAttachmentContentAsBlob( - application, - file.key, - ) return { fileName: file.name, fileType: file.type, - blob, + url: await this.sharedTemplateAPIService.getAttachmentUrl( + file.key, + expiry, + ), } }) || [], ) diff --git a/libs/application/template-api-modules/src/lib/types/index.ts b/libs/application/template-api-modules/src/lib/types/index.ts index 8969bb680439..9c19b3721967 100644 --- a/libs/application/template-api-modules/src/lib/types/index.ts +++ b/libs/application/template-api-modules/src/lib/types/index.ts @@ -9,7 +9,6 @@ import { IslykillApiModuleConfig } from '@island.is/clients/islykill' import { Message } from '@island.is/email-service' import type { Locale } from '@island.is/shared/types' -import { Attachment } from 'nodemailer/lib/mailer' export interface BaseTemplateAPIModuleConfig { xRoadBasePathWithEnv: string @@ -65,7 +64,7 @@ export type EmailTemplateGenerator = ( export type AttachmentEmailTemplateGenerator = ( props: EmailTemplateGeneratorProps, - fileContent: Attachment['content'], + fileContent: string, email: string, ) => Message @@ -107,8 +106,3 @@ export interface SmsMessage { phoneNumber: string message: string } - -export type ApplicationSubmittedEmail = ( - props: EmailTemplateGeneratorProps, - recipient: T, -) => Message diff --git a/libs/application/template-loader/src/lib/templateLoaders.ts b/libs/application/template-loader/src/lib/templateLoaders.ts index 4d57bc0a1a11..d1ea92123509 100644 --- a/libs/application/template-loader/src/lib/templateLoaders.ts +++ b/libs/application/template-loader/src/lib/templateLoaders.ts @@ -193,6 +193,10 @@ const templates: Record Promise> = { import('@island.is/application/templates/id-card'), [ApplicationTypes.HEALTH_INSURANCE_DECLARATION]: () => import('@island.is/application/templates/health-insurance-declaration'), + [ApplicationTypes.INCOME_PLAN]: () => + import( + '@island.is/application/templates/social-insurance-administration/income-plan' + ), [ApplicationTypes.NEW_PRIMARY_SCHOOL]: () => import('@island.is/application/templates/new-primary-school'), [ApplicationTypes.MACHINE_REGISTRATION]: () => diff --git a/libs/application/templates/announcement-of-death/src/fields/FirearmApplicant/index.tsx b/libs/application/templates/announcement-of-death/src/fields/FirearmApplicant/index.tsx index 64057ebb6524..ef68d88ac7f8 100644 --- a/libs/application/templates/announcement-of-death/src/fields/FirearmApplicant/index.tsx +++ b/libs/application/templates/announcement-of-death/src/fields/FirearmApplicant/index.tsx @@ -107,12 +107,9 @@ export const FirearmApplicant: FC< backgroundColor="blue" icon={name ? 'checkmarkCircle' : undefined} loading={queryLoading} + required error={ - getErrorViaPath( - errors, - 'pickRole.electPerson.lookupError.message', - ) || - getErrorViaPath(errors, 'pickRole.electPerson.nationalId') || + getErrorViaPath(errors, 'firearmApplicant.nationalId') || undefined } /> @@ -126,6 +123,7 @@ export const FirearmApplicant: FC< application, formatMessage, )} + readOnly /> @@ -139,6 +137,10 @@ export const FirearmApplicant: FC< application, formatMessage, )} + error={ + getErrorViaPath(errors, 'firearmApplicant.phone') || undefined + } + required /> @@ -146,6 +148,10 @@ export const FirearmApplicant: FC< id={fieldNames.firearmApplicantEmail} name={fieldNames.firearmApplicantEmail} label={formatText(m.applicantsEmail, application, formatMessage)} + error={ + getErrorViaPath(errors, 'firearmApplicant.email') || undefined + } + required /> diff --git a/libs/application/templates/announcement-of-death/src/forms/done.ts b/libs/application/templates/announcement-of-death/src/forms/done.ts index 4e93344c49a0..6fefe945c4d8 100644 --- a/libs/application/templates/announcement-of-death/src/forms/done.ts +++ b/libs/application/templates/announcement-of-death/src/forms/done.ts @@ -11,7 +11,6 @@ import { Form, FormModes } from '@island.is/application/types' import CoatOfArms from '../assets/CoatOfArms' import { m } from '../lib/messages' import { - extraInfo, files, firearmApplicant, inheritance, @@ -66,7 +65,6 @@ export const done: Form = buildForm({ ...inheritance, ...properties, ...files, - ...extraInfo, ], }), ], diff --git a/libs/application/templates/announcement-of-death/src/forms/draft/draft.ts b/libs/application/templates/announcement-of-death/src/forms/draft/draft.ts index c1dd65829de4..3231e48fa456 100644 --- a/libs/application/templates/announcement-of-death/src/forms/draft/draft.ts +++ b/libs/application/templates/announcement-of-death/src/forms/draft/draft.ts @@ -34,9 +34,9 @@ export const draft: Form = buildForm({ children: [ subSectionInfo, subSectionWillAndTrade, - subSectionFirearms, subSectionInheritance, subSectionProperties, + subSectionFirearms, subSectionFiles, ], }), diff --git a/libs/application/templates/announcement-of-death/src/forms/draft/sectionOverview.ts b/libs/application/templates/announcement-of-death/src/forms/draft/sectionOverview.ts index 5d40dffb698a..65319dd8ed2d 100644 --- a/libs/application/templates/announcement-of-death/src/forms/draft/sectionOverview.ts +++ b/libs/application/templates/announcement-of-death/src/forms/draft/sectionOverview.ts @@ -7,11 +7,10 @@ import { DefaultEvents } from '@island.is/application/types' import { m } from '../../lib/messages' import { additionalInfo, - extraInfo, + properties, files, firearmApplicant, inheritance, - properties, testament, theAnnouncer, theDeceased, @@ -30,11 +29,10 @@ export const sectionOverview = buildSection({ ...theDeceased, ...theAnnouncer, ...testament, - ...firearmApplicant, ...inheritance, ...properties, + ...firearmApplicant, ...files, - ...extraInfo, ...additionalInfo, buildSubmitField({ id: 'submit', diff --git a/libs/application/templates/announcement-of-death/src/forms/draft/subSectionInfo.ts b/libs/application/templates/announcement-of-death/src/forms/draft/subSectionInfo.ts index 54b030af96bf..5b56026530fa 100644 --- a/libs/application/templates/announcement-of-death/src/forms/draft/subSectionInfo.ts +++ b/libs/application/templates/announcement-of-death/src/forms/draft/subSectionInfo.ts @@ -6,6 +6,7 @@ import { } from '@island.is/application/core' import { m } from '../../lib/messages' import { Application } from '../../types/schema' +import { removeCountryCode } from '@island.is/application/ui-components' export const subSectionInfo = buildSubSection({ id: 'infoStep', @@ -33,9 +34,16 @@ export const subSectionInfo = buildSubSection({ format: '###-####', placeholder: '', width: 'half', - defaultValue: (application: Application) => - application.externalData?.userProfile?.data?.mobilePhoneNumber ?? - '', + defaultValue: (application: Application) => { + const phone = + ( + application.externalData.userProfile?.data as { + mobilePhoneNumber?: string + } + )?.mobilePhoneNumber ?? '' + + return removeCountryCode(phone) + }, }), buildTextField({ id: 'applicantEmail', diff --git a/libs/application/templates/announcement-of-death/src/forms/draft/subSectionInheritance.ts b/libs/application/templates/announcement-of-death/src/forms/draft/subSectionInheritance.ts index d6fde6994678..0627cfc43883 100644 --- a/libs/application/templates/announcement-of-death/src/forms/draft/subSectionInheritance.ts +++ b/libs/application/templates/announcement-of-death/src/forms/draft/subSectionInheritance.ts @@ -3,6 +3,8 @@ import { buildMultiField, buildSubSection, buildCustomField, + buildCheckboxField, + YES, } from '@island.is/application/core' import { m } from '../../lib/messages' @@ -28,6 +30,19 @@ export const subSectionInheritance = buildSubSection({ component: 'EstateMemberRepeater', childInputIds: ['estateMembers.encountered', 'estateMembers.members'], }), + buildDescriptionField({ + id: 'inheritanceConfirmationDescription', + title: '', + description: m.inheritanceConfirmationDescription, + space: 'containerGutter', + }), + buildCheckboxField({ + id: 'estateMembers.confirmation', + title: '', + large: false, + backgroundColor: 'white', + options: [{ value: YES, label: m.inheritanceConfirmation }], + }), ], }), ], diff --git a/libs/application/templates/announcement-of-death/src/forms/draft/subSectionProperties.ts b/libs/application/templates/announcement-of-death/src/forms/draft/subSectionProperties.ts index 1cc6036e450e..3f387e465738 100644 --- a/libs/application/templates/announcement-of-death/src/forms/draft/subSectionProperties.ts +++ b/libs/application/templates/announcement-of-death/src/forms/draft/subSectionProperties.ts @@ -6,7 +6,7 @@ import { buildCheckboxField, } from '@island.is/application/core' import { m } from '../../lib/messages' -import { OtherPropertiesEnum } from '../../types' +import { PropertiesEnum } from '../../types' export const subSectionProperties = buildSubSection({ id: 'propertiesStep', @@ -18,37 +18,6 @@ export const subSectionProperties = buildSubSection({ description: m.propertiesDescription, space: 2, children: [ - buildDescriptionField({ - id: 'realEstatesTitle', - title: m.realEstatesTitle, - titleVariant: 'h3', - description: m.realEstatesDescription, - }), - buildCustomField({ - title: 'realEstateRepeater', - id: 'assets', - component: 'RealEstateRepeater', - }), - buildDescriptionField({ - id: 'vehiclesTitle', - title: m.vehiclesTitle, - description: m.vehiclesDescription, - space: 5, - titleVariant: 'h3', - }), - buildCustomField({ - title: 'Vehicles!', - id: 'vehicles', - component: 'VehiclesRepeater', - childInputIds: ['vehicles.vehicles', 'vehicles.encountered'], - }), - buildDescriptionField({ - id: 'otherPropertiesTitle', - title: m.otherPropertiesTitle, - titleVariant: 'h3', - description: m.otherPropertiesDescription, - space: 5, - }), buildCheckboxField({ id: 'otherProperties', title: '', @@ -57,21 +26,29 @@ export const subSectionProperties = buildSubSection({ doesNotRequireAnswer: true, defaultValue: '', options: [ + { + label: m.propertiesRealEstate, + value: PropertiesEnum.REAL_ESTATE, + }, + { + label: m.propertiesVehicles, + value: PropertiesEnum.VEHICLES, + }, { label: m.otherPropertiesAccounts, - value: OtherPropertiesEnum.ACCOUNTS, + value: PropertiesEnum.ACCOUNTS, }, { label: m.otherPropertiesOwnBusiness, - value: OtherPropertiesEnum.OWN_BUSINESS, + value: PropertiesEnum.OWN_BUSINESS, }, { label: m.otherPropertiesResidence, - value: OtherPropertiesEnum.RESIDENCE, + value: PropertiesEnum.RESIDENCE, }, { label: m.otherPropertiesAssetsAbroad, - value: OtherPropertiesEnum.ASSETS_ABROAD, + value: PropertiesEnum.ASSETS_ABROAD, }, ], }), diff --git a/libs/application/templates/announcement-of-death/src/forms/overviewSections.ts b/libs/application/templates/announcement-of-death/src/forms/overviewSections.ts index 4421f3bf30b0..e657384ff918 100644 --- a/libs/application/templates/announcement-of-death/src/forms/overviewSections.ts +++ b/libs/application/templates/announcement-of-death/src/forms/overviewSections.ts @@ -4,14 +4,14 @@ import { buildKeyValueField, buildCustomField, buildTextField, + YES, } from '@island.is/application/core' import { Answer, Application, Field } from '@island.is/application/types' import { format as formatNationalId } from 'kennitala' import { m } from '../lib/messages' import { formatPhoneNumber } from '@island.is/application/ui-components' import format from 'date-fns/format' -import { Asset, Answers as AODAnswers, OtherPropertiesEnum } from '../types' -import { FormatMessage } from '@island.is/localization' +import { Answers as AODAnswers, PropertiesEnum } from '../types' import { getFileRecipientName } from '../lib/utils' import { EstateRegistrant } from '@island.is/clients/syslumenn' @@ -123,7 +123,7 @@ export const testament: Field[] = [ }), buildKeyValueField({ label: m.testamentKnowledgeOfOtherTestament, - width: 'half', + width: 'full', value: ({ answers }) => answers.knowledgeOfOtherWills === 'yes' ? m.testamentKnowledgeOfOtherTestamentYes @@ -132,71 +132,6 @@ export const testament: Field[] = [ }), ] -export const extraInfo: Field[] = [ - buildDividerField({ - condition: (answers) => showInDone(answers.viewOverview), - }), - buildDescriptionField({ - id: 'otherProperties', - title: m.otherPropertiesTitle, - marginBottom: 2, - titleVariant: 'h3', - condition: (answers) => showInDone(answers.viewOverview), - }), - buildKeyValueField({ - label: m.otherPropertiesAccounts, - width: 'half', - value: ({ answers }) => - answers?.otherProperties - ? (answers.otherProperties as string[]).includes( - OtherPropertiesEnum.ACCOUNTS, - ) - ? m.testamentKnowledgeOfOtherTestamentYes - : m.testamentKnowledgeOfOtherTestamentNo - : m.testamentKnowledgeOfOtherTestamentNo, - condition: (answers) => showInDone(answers.viewOverview), - }), - buildKeyValueField({ - label: m.otherPropertiesOwnBusiness, - width: 'half', - value: ({ answers }) => - answers?.otherProperties - ? (answers.otherProperties as string[]).includes( - OtherPropertiesEnum.OWN_BUSINESS, - ) - ? m.testamentKnowledgeOfOtherTestamentYes - : m.testamentKnowledgeOfOtherTestamentNo - : m.testamentKnowledgeOfOtherTestamentNo, - condition: (answers) => showInDone(answers.viewOverview), - }), - buildKeyValueField({ - label: m.otherPropertiesResidence, - width: 'half', - value: ({ answers }) => - answers?.otherProperties - ? (answers.otherProperties as string[]).includes( - OtherPropertiesEnum.RESIDENCE, - ) - ? m.testamentKnowledgeOfOtherTestamentYes - : m.testamentKnowledgeOfOtherTestamentNo - : m.testamentKnowledgeOfOtherTestamentNo, - condition: (answers) => showInDone(answers.viewOverview), - }), - buildKeyValueField({ - label: m.otherPropertiesAssetsAbroad, - width: 'half', - value: ({ answers }) => - answers?.otherProperties - ? (answers.otherProperties as string[]).includes( - OtherPropertiesEnum.ASSETS_ABROAD, - ) - ? m.testamentKnowledgeOfOtherTestamentYes - : m.testamentKnowledgeOfOtherTestamentNo - : m.testamentKnowledgeOfOtherTestamentNo, - condition: (answers) => showInDone(answers.viewOverview), - }), -] - export const inheritance: Field[] = [ buildDividerField({ condition: (answers) => @@ -244,67 +179,90 @@ export const properties: Field[] = [ buildDividerField({ condition: (answers) => showInDone(answers.viewOverview), }), - buildDescriptionField({ - id: 'realEstatesTitle', - title: m.realEstatesTitle, + id: 'propertiesTitle', + title: m.propertiesTitle, titleVariant: 'h3', - description: m.realEstatesDescription, condition: (answers) => showInDone(answers.viewOverview), }), - buildCustomField( - { - title: '', - id: 'assets', - component: 'InfoCard', - width: 'full', - condition: (answers) => - (answers?.assets as { assets: Asset[] })?.assets?.length > 0 && - showInDone(answers.viewOverview), - }, - { - cards: ({ answers }: Application) => - (answers?.assets as { assets: Asset[] }).assets - .filter((asset) => !asset?.dummy) - .map((property) => ({ - title: property.description, - description: (formatMessage: FormatMessage) => [ - `${formatMessage(m.propertyNumber)}: ${property.assetNumber}`, - property.share - ? `${formatMessage(m.propertyShare)}: ${property.share}%` - : '', - ], - })), - }, - ), - buildDescriptionField({ - id: 'vehiclesTitle', - title: m.vehiclesTitle, - description: m.vehiclesDescription, - space: 5, - titleVariant: 'h3', + buildKeyValueField({ + label: m.propertiesRealEstate, + width: 'full', + value: ({ answers }) => + answers?.otherProperties + ? (answers.otherProperties as string[]).includes( + PropertiesEnum.REAL_ESTATE, + ) + ? m.testamentKnowledgeOfOtherTestamentYes + : m.testamentKnowledgeOfOtherTestamentNo + : m.testamentKnowledgeOfOtherTestamentNo, + condition: (answers) => showInDone(answers.viewOverview), + }), + buildKeyValueField({ + label: m.propertiesVehicles, + width: 'full', + value: ({ answers }) => + answers?.otherProperties + ? (answers.otherProperties as string[]).includes( + PropertiesEnum.VEHICLES, + ) + ? m.testamentKnowledgeOfOtherTestamentYes + : m.testamentKnowledgeOfOtherTestamentNo + : m.testamentKnowledgeOfOtherTestamentNo, + condition: (answers) => showInDone(answers.viewOverview), + }), + buildKeyValueField({ + label: m.otherPropertiesAccounts, + width: 'full', + value: ({ answers }) => + answers?.otherProperties + ? (answers.otherProperties as string[]).includes( + PropertiesEnum.ACCOUNTS, + ) + ? m.testamentKnowledgeOfOtherTestamentYes + : m.testamentKnowledgeOfOtherTestamentNo + : m.testamentKnowledgeOfOtherTestamentNo, + condition: (answers) => showInDone(answers.viewOverview), + }), + buildKeyValueField({ + label: m.otherPropertiesOwnBusiness, + width: 'full', + value: ({ answers }) => + answers?.otherProperties + ? (answers.otherProperties as string[]).includes( + PropertiesEnum.OWN_BUSINESS, + ) + ? m.testamentKnowledgeOfOtherTestamentYes + : m.testamentKnowledgeOfOtherTestamentNo + : m.testamentKnowledgeOfOtherTestamentNo, + condition: (answers) => showInDone(answers.viewOverview), + }), + buildKeyValueField({ + label: m.otherPropertiesResidence, + width: 'full', + value: ({ answers }) => + answers?.otherProperties + ? (answers.otherProperties as string[]).includes( + PropertiesEnum.RESIDENCE, + ) + ? m.testamentKnowledgeOfOtherTestamentYes + : m.testamentKnowledgeOfOtherTestamentNo + : m.testamentKnowledgeOfOtherTestamentNo, + condition: (answers) => showInDone(answers.viewOverview), + }), + buildKeyValueField({ + label: m.otherPropertiesAssetsAbroad, + width: 'full', + value: ({ answers }) => + answers?.otherProperties + ? (answers.otherProperties as string[]).includes( + PropertiesEnum.ASSETS_ABROAD, + ) + ? m.testamentKnowledgeOfOtherTestamentYes + : m.testamentKnowledgeOfOtherTestamentNo + : m.testamentKnowledgeOfOtherTestamentNo, condition: (answers) => showInDone(answers.viewOverview), }), - buildCustomField( - { - title: '', - id: 'vehicles', - component: 'InfoCard', - width: 'full', - condition: (answers) => - (answers?.vehicles as { vehicles: Asset[] })?.vehicles?.length > 0 && - showInDone(answers.viewOverview), - }, - { - cards: ({ answers }: Application) => - (answers?.vehicles as { vehicles: Asset[] })?.vehicles - .filter((vehicle) => !vehicle?.dummy) - .map((vehicle) => ({ - title: vehicle.assetNumber, - description: [vehicle.description], - })), - }, - ), ] export const files: Field[] = [ @@ -321,7 +279,6 @@ export const files: Field[] = [ buildCustomField( { title: m.certificateOfDeathAnnouncementTitle, - description: m.certificateOfDeathAnnouncementDescription, id: 'certificateOfDeathAnnouncement', component: 'FilesRecipientCard', condition: (answers) => showInDone(answers.viewOverview), @@ -338,7 +295,6 @@ export const files: Field[] = [ buildCustomField( { title: m.financesDataCollectionPermissionTitle, - description: m.financesDataCollectionPermissionDescription, id: 'financesDataCollectionPermission', component: 'FilesRecipientCard', condition: (answers) => showInDone(answers.viewOverview), @@ -355,7 +311,6 @@ export const files: Field[] = [ buildCustomField( { title: m.authorizationForFuneralExpensesTitle, - description: m.authorizationForFuneralExpensesDescription, id: 'authorizationForFuneralExpenses', component: 'FilesRecipientCard', condition: (answers) => showInDone(answers.viewOverview), @@ -381,31 +336,50 @@ export const firearmApplicant: Field[] = [ titleVariant: 'h3', condition: (answers) => showInDone(answers.viewOverview), }), + buildKeyValueField({ + label: m.firearmsHadFirearms, + width: 'full', + value: ({ answers }) => + answers.hadFirearms === YES ? m.firearmsYes : m.firearmsNo, + condition: (answers) => showInDone(answers.viewOverview), + }), + buildDescriptionField({ + id: 'firearmApplicantInfo', + title: m.firearmsApplicant, + titleVariant: 'h3', + space: 2, + condition: (answers) => + showInDone(answers.viewOverview) && answers.hadFirearms === YES, + }), buildKeyValueField({ label: m.firearmsApplicantName, width: 'half', value: ({ answers }) => (answers.firearmApplicant as any)?.name || '', - condition: (answers) => showInDone(answers.viewOverview), + condition: (answers) => + showInDone(answers.viewOverview) && answers.hadFirearms === YES, }), buildKeyValueField({ label: m.firearmsApplicantNationalId, width: 'half', value: ({ answers }) => formatNationalId((answers.firearmApplicant as any)?.nationalId || ''), - condition: (answers) => showInDone(answers.viewOverview), + condition: (answers) => + showInDone(answers.viewOverview) && answers.hadFirearms === YES, }), buildKeyValueField({ label: m.firearmsApplicantPhone, width: 'half', value: ({ answers }) => formatPhoneNumber((answers.firearmApplicant as any)?.phone || ''), - condition: (answers) => showInDone(answers.viewOverview), + condition: (answers) => + showInDone(answers.viewOverview) && answers.hadFirearms === YES, }), buildKeyValueField({ label: m.firearmsApplicantEmail, width: 'half', value: ({ answers }) => (answers.firearmApplicant as any)?.email || '', - condition: (answers) => showInDone(answers.viewOverview), + condition: (answers) => + showInDone(answers.viewOverview) && answers.hadFirearms === YES, }), ] @@ -414,7 +388,6 @@ export const additionalInfo: Field[] = [ buildDescriptionField({ id: 'additionalInfoTitle', title: m.additionalInfoTitle, - description: m.additionalInfoDescription, titleVariant: 'h3', }), buildTextField({ diff --git a/libs/application/templates/announcement-of-death/src/lib/dataSchema.ts b/libs/application/templates/announcement-of-death/src/lib/dataSchema.ts index 0676b2e15498..e1feabcf8435 100644 --- a/libs/application/templates/announcement-of-death/src/lib/dataSchema.ts +++ b/libs/application/templates/announcement-of-death/src/lib/dataSchema.ts @@ -5,6 +5,7 @@ import { m } from './messages' import { RoleConfirmationEnum } from '../types' import { customZodError } from './utils/customZodError' +import { NO, YES } from '@island.is/application/core' const isValidPhoneNumber = (phoneNumber: string) => { const phone = parsePhoneNumberFromString(phoneNumber, 'IS') @@ -76,9 +77,10 @@ export const dataSchema = z.object({ }), applicantEmail: customZodError(z.string().email(), m.errorEmail), applicantRelation: customZodError(z.string().min(1), m.errorRelation), + hadFirearms: z.enum([YES, NO]), firearmApplicant: z .object({ - nationalId: z.string(), + nationalId: z.string().refine((v) => nationalId.isPerson(v)), name: z.string(), phone: z.string().refine((v) => isValidPhoneNumber(v), { params: m.errorPhoneNumber, @@ -119,6 +121,7 @@ export const dataSchema = z.object({ .array() .optional(), encountered: z.boolean().optional(), + confirmation: z.array(z.enum([YES])).length(1), }), flyers: asset, ships: asset, diff --git a/libs/application/templates/announcement-of-death/src/lib/messages.ts b/libs/application/templates/announcement-of-death/src/lib/messages.ts index cf334a594578..c75ed61bde01 100644 --- a/libs/application/templates/announcement-of-death/src/lib/messages.ts +++ b/libs/application/templates/announcement-of-death/src/lib/messages.ts @@ -232,9 +232,14 @@ export const m = defineMessages({ defaultMessage: 'Skráning vörsluaðila:', description: '', }, + firearmsApplicant: { + id: 'aod.application:firearmsApplicant', + defaultMessage: 'Skráður vörsluaðili', + description: '', + }, firearmsApplicantOverviewHeader: { id: 'aod.application:firearmsApplicantOverviewHeader', - defaultMessage: 'Skotvopn - vörsluaðili', + defaultMessage: 'Skotvopn', description: '', }, firearmsApplicantName: { @@ -323,6 +328,18 @@ Ef ekkert á við sem hér að ofan er talið rennur arfur í ríkissjóð. Nán defaultMessage: 'Fæðingardagur', description: 'Day of birth label', }, + inheritanceConfirmationDescription: { + id: 'aod.application:inheritanceConfirmationDescription#markdown', + defaultMessage: + 'Ath. Það þarf alltaf að fylla út upplýsingar um alla erfingja, til dæmis þótt maki ætli að sitja í óskiptu búi. Ef barn hins látna var fallið frá á undan þarf að gefa upplýsingar um barnabörn. Ef látni sat í óskiptu búi þarf að veita upplýsingar um stjúpbörn.', + description: 'Inheritance confirmation', + }, + inheritanceConfirmation: { + id: 'aod.application:inheritanceConfirmation', + defaultMessage: + 'Ég staðfesti að hafa fært inn upplýsingar um alla erfingja', + description: 'Inheritance confirmation', + }, /* Properties step */ propertiesTitle: { @@ -332,8 +349,7 @@ Ef ekkert á við sem hér að ofan er talið rennur arfur í ríkissjóð. Nán }, propertiesDescription: { id: 'aod.application:propertiesDescription', - defaultMessage: - 'Upplýsingar um eignir og ökutæki hafa verið sóttar rafrænt. Vinsamlega bætið við upplýsingum ef eitthvað vantar. Taktu þér góðan tíma í að fylla þetta út eftir bestu getu. ', + defaultMessage: 'Merktu við það sem á við eftir bestu vitund.', description: 'Properties description', }, realEstatesTitle: { @@ -341,35 +357,25 @@ Ef ekkert á við sem hér að ofan er talið rennur arfur í ríkissjóð. Nán defaultMessage: 'Fasteignir', description: 'Real estates and lands title', }, - realEstatesDescription: { - id: 'aod.application:realEstatesDescription', - defaultMessage: 'Til dæmis íbúðarhús, sumarhús, lóðir og jarðir.', - description: 'Real estates and lands description', - }, vehiclesTitle: { id: 'aod.application:vehiclesTitle', defaultMessage: 'Faratæki', description: 'Vehicles title', }, - vehiclesDescription: { - id: 'aod.application:vehiclesDescription', - defaultMessage: 'Til dæmis bifreiðar, flugvélar og bátar.', - description: 'Vehicles description', - }, vehiclesPlaceholder: { id: 'aod.application:vehiclesPlaceholder', defaultMessage: 't.d. Toyota Yaris', description: 'Placeholder for vehicles type', }, - otherPropertiesTitle: { - id: 'aod.application:otherPropertiesTitle', - defaultMessage: 'Aðrar eignir', - description: 'Other properties title', + propertiesRealEstate: { + id: 'aod.application:propertiesRealEstate', + defaultMessage: 'Fasteignir', + description: 'Properties option: Real estate', }, - otherPropertiesDescription: { - id: 'aod.application:otherPropertiesDescription', - defaultMessage: 'Merktu við það sem á við eftir bestu vitund.', - description: 'Other properties description', + propertiesVehicles: { + id: 'aod.application:propertiesVehicles', + defaultMessage: 'Faratæki', + description: 'Properties option: Vehicles', }, otherPropertiesAccounts: { id: 'aod.application:otherPropertiesAccounts', diff --git a/libs/application/templates/announcement-of-death/src/types.ts b/libs/application/templates/announcement-of-death/src/types.ts index 0079b7f0b2a7..48adf49704b3 100644 --- a/libs/application/templates/announcement-of-death/src/types.ts +++ b/libs/application/templates/announcement-of-death/src/types.ts @@ -12,7 +12,9 @@ export enum RelationEnum { SIBLING = 'sibling', SPOUSE = 'spouse', } -export enum OtherPropertiesEnum { +export enum PropertiesEnum { + REAL_ESTATE = 'realEstate', + VEHICLES = 'vehicles', ACCOUNTS = 'accounts', OWN_BUSINESS = 'ownBusiness', RESIDENCE = 'residence', @@ -50,7 +52,7 @@ export type Answers = { firearmApplicant: FirearmApplicant marriageSettlement: boolean occupationRightViaCondominium: boolean - otherProperties: OtherPropertiesEnum + otherProperties: PropertiesEnum ownBusinessManagement: boolean roleConfirmation: RoleConfirmationEnum vehicles: { diff --git a/libs/application/templates/aosh/register-new-machine/src/fields/AboutMachine/index.tsx b/libs/application/templates/aosh/register-new-machine/src/fields/AboutMachine/index.tsx index 33d7f6e0d49d..701a8b9343fb 100644 --- a/libs/application/templates/aosh/register-new-machine/src/fields/AboutMachine/index.tsx +++ b/libs/application/templates/aosh/register-new-machine/src/fields/AboutMachine/index.tsx @@ -57,7 +57,13 @@ export const AboutMachine: FC> = ( { subCat: string; registrationNumberPrefix: string }[] >([]) const [registrationNumberPrefix, setRegistrationNumberPrefix] = - useState('') + useState( + getValueViaPath( + application.answers, + 'machine.aboutMachine.registrationNumberPrefix', + '', + ) as string, + ) const [subCategoryDisabled, setSubCategoryDisabled] = useState( fromService || (!fromService && !subCategory.length), ) diff --git a/libs/application/templates/aosh/register-new-machine/src/fields/TechnicalInfo/formFieldMapper.tsx b/libs/application/templates/aosh/register-new-machine/src/fields/TechnicalInfo/formFieldMapper.tsx index 833e32fa3c7b..4261a536cc7c 100644 --- a/libs/application/templates/aosh/register-new-machine/src/fields/TechnicalInfo/formFieldMapper.tsx +++ b/libs/application/templates/aosh/register-new-machine/src/fields/TechnicalInfo/formFieldMapper.tsx @@ -23,11 +23,15 @@ export const formFieldMapper = ({ const { variableName, label, type, required, maxLength, values } = item const { application, field } = props const error = - displayError && - required && - variableName && - watchTechInfoFields[variableName].length === 0 - ? formatMessage(coreErrorMessages.defaultError) + displayError && required && variableName + ? (watchTechInfoFields[variableName].length === 0 + ? formatMessage(coreErrorMessages.defaultError) + : undefined) || + ((type === 'int' || type === 'float') && + maxLength && + watchTechInfoFields[variableName].length > maxLength + ? formatMessage(coreErrorMessages.defaultError) + : undefined) : undefined if (values && values.length > 0) { return ( @@ -63,8 +67,9 @@ export const formFieldMapper = ({ children: undefined, backgroundColor: 'blue', required: required, - maxLength: maxLength ? parseInt(maxLength, 10) : undefined, variant: 'number', + max: maxLength ? parseInt(maxLength, 10) : undefined, + min: 0, }} /> ) diff --git a/libs/application/templates/aosh/register-new-machine/src/fields/TechnicalInfo/index.tsx b/libs/application/templates/aosh/register-new-machine/src/fields/TechnicalInfo/index.tsx index 6b621efad1b3..b5b94c0827f4 100644 --- a/libs/application/templates/aosh/register-new-machine/src/fields/TechnicalInfo/index.tsx +++ b/libs/application/templates/aosh/register-new-machine/src/fields/TechnicalInfo/index.tsx @@ -17,6 +17,7 @@ import { UPDATE_APPLICATION } from '@island.is/application/graphql' import { TechInfoItem } from '../../shared/types' import { formFieldMapper } from './formFieldMapper' import { application as applicationMessage } from '../../lib/messages' +import { formatDate } from '../../utils' export const technicalInfoInputs = gql` ${TECHNICAL_INFO_INPUTS} @@ -62,34 +63,46 @@ export const TechnicalInfo: FC> = ( }) useEffect(() => { - // Call subcategory runQuery({ variables: { parentCategory: machineCategory, + subCategory: machineSubCategory, }, }) - }, [machineCategory]) + }, [machineCategory, machineSubCategory]) setBeforeSubmitCallback?.(async () => { - // Renna yfir listan í techInfoItems og finna í listanum watchTechInfoFields - // Ef það er required þá tékka hvort það sé útfyllt - // Síðan setja í answers með nafninu og value setDisplayError(false) const techInfoAnswer = techInfoItems?.map( - ({ variableName, required, label }, index) => { + ({ variableName, required, label, maxLength, type }, index) => { const answer = variableName ? watchTechInfoFields[variableName] : '' - if (required && answer.length === 0) { + if ( + (required && answer.length === 0) || + (required && + (type === 'int' || type === 'float' + ? maxLength + ? answer.length > maxLength + : false + : false)) + ) { setDisplayError(true) return 'error' } setValue(`techInfo[${index}].variableName`, variableName) - setValue(`techInfo[${index}].value`, answer) + setValue( + `techInfo[${index}].value`, + type === 'dateTime' ? formatDate(answer) : answer, + ) setValue(`techInfo[${index}].label`, label) return { variableName, value: answer, label } }, ) - if (techInfoAnswer?.some((val) => val === 'error')) { + if ( + loading || + connectionError || + techInfoAnswer?.some((val) => val === 'error') + ) { return [false, ''] } diff --git a/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/MachineSection/MachineBasicInformation.ts b/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/MachineSection/MachineBasicInformation.ts index f2f7ad6ff90d..11a5ed13c080 100644 --- a/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/MachineSection/MachineBasicInformation.ts +++ b/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/MachineSection/MachineBasicInformation.ts @@ -48,6 +48,8 @@ export const MachineBasicInformation = buildSubSection({ width: 'half', required: true, variant: 'number', + min: 1900, + max: new Date().getFullYear(), }), buildTextField({ id: 'machine.basicInformation.productionNumber', @@ -95,11 +97,11 @@ export const MachineBasicInformation = buildSubSection({ options: [ { value: NEW, - label: 'Ný', + label: machine.labels.basicMachineInformation.new, }, { value: USED, - label: 'Notuð', + label: machine.labels.basicMachineInformation.used, }, ], }), diff --git a/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/OverviewSection.ts b/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/OverviewSection.ts index 0187bccb45b9..20cbc1438e01 100644 --- a/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/OverviewSection.ts +++ b/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/OverviewSection.ts @@ -25,6 +25,7 @@ export const OverviewSection = buildSection({ id: 'submit', placement: 'footer', title: overview.labels.approveButton, + refetchApplicationAfterSubmit: true, actions: [ { event: DefaultEvents.SUBMIT, diff --git a/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/index.ts b/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/index.ts index 2d05584a6a1b..2882b39679bd 100644 --- a/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/index.ts +++ b/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/index.ts @@ -12,8 +12,6 @@ export const RegisterNewMachineForm: Form = buildForm({ title: '', logo: Logo, mode: FormModes.DRAFT, - renderLastScreenButton: true, - renderLastScreenBackButton: true, children: [ buildSection({ id: 'externalData', diff --git a/libs/application/templates/aosh/register-new-machine/src/graphql/queries.ts b/libs/application/templates/aosh/register-new-machine/src/graphql/queries.ts index da35c46331b2..ae1bff3720ea 100644 --- a/libs/application/templates/aosh/register-new-machine/src/graphql/queries.ts +++ b/libs/application/templates/aosh/register-new-machine/src/graphql/queries.ts @@ -27,8 +27,8 @@ export const MACHINE_SUB_CATEGORIES = ` ` export const TECHNICAL_INFO_INPUTS = ` - query GetTechnicalInfoInputs($parentCategory: String!) { - getTechnicalInfoInputs(parentCategory: $parentCategory) { + query GetTechnicalInfoInputs($parentCategory: String!, $subCategory: String!) { + getTechnicalInfoInputs(parentCategory: $parentCategory, subCategory: $subCategory) { variableName label type diff --git a/libs/application/templates/aosh/register-new-machine/src/lib/dataSchema.ts b/libs/application/templates/aosh/register-new-machine/src/lib/dataSchema.ts index e2a76b140c54..1b182d57d631 100644 --- a/libs/application/templates/aosh/register-new-machine/src/lib/dataSchema.ts +++ b/libs/application/templates/aosh/register-new-machine/src/lib/dataSchema.ts @@ -14,6 +14,15 @@ const PersonInformationSchema = z.object({ email: z.string().min(1), }) +const RemovablePersonInformationSchema = z.object({ + name: z.string().optional(), + nationalId: z.string().optional(), + address: z.string().optional(), + postCode: z.string().optional(), + phone: z.string().optional(), + email: z.string().optional(), +}) + const BasicInformationSchema = z.object({ productionCountry: z.string().min(1), productionYear: z.string().min(1), @@ -42,15 +51,135 @@ const TechInfoSchema = z.object({ export const NewMachineAnswersSchema = z.object({ approveExternalData: z.boolean(), - importerInformation: z.object({ - importer: PersonInformationSchema, - isOwnerOtherThanImporter: z.enum([YES, NO]), - owner: PersonInformationSchema.optional(), - }), - operatorInformation: z.object({ - operator: PersonInformationSchema.optional(), - hasOperator: z.enum([YES, NO]), - }), + importerInformation: z + .object({ + importer: PersonInformationSchema, + isOwnerOtherThanImporter: z.enum([YES, NO]), + owner: RemovablePersonInformationSchema.optional(), + }) + .refine( + ({ isOwnerOtherThanImporter, owner }) => { + if (isOwnerOtherThanImporter === NO) return true + return owner && owner.name && owner.name.length > 0 + }, + { + path: ['owner', 'name'], + }, + ) + .refine( + ({ isOwnerOtherThanImporter, owner }) => { + if (isOwnerOtherThanImporter === NO) return true + return ( + owner && + owner.nationalId && + owner.nationalId.length > 0 && + kennitala.isValid(owner.nationalId) + ) + }, + { + path: ['owner', 'nationalId'], + }, + ) + .refine( + ({ isOwnerOtherThanImporter, owner }) => { + if (isOwnerOtherThanImporter === NO) return true + return owner && owner.address && owner.address.length > 0 + }, + { + path: ['owner', 'address'], + }, + ) + .refine( + ({ isOwnerOtherThanImporter, owner }) => { + if (isOwnerOtherThanImporter === NO) return true + return owner && owner.postCode && owner.postCode.length > 0 + }, + { + path: ['owner', 'postCode'], + }, + ) + .refine( + ({ isOwnerOtherThanImporter, owner }) => { + if (isOwnerOtherThanImporter === NO) return true + return owner && owner.phone && owner.phone.length > 0 + }, + { + path: ['owner', 'phone'], + }, + ) + .refine( + ({ isOwnerOtherThanImporter, owner }) => { + if (isOwnerOtherThanImporter === NO) return true + return owner && owner.email && owner.email.length > 0 + }, + { + path: ['owner', 'email'], + }, + ), + operatorInformation: z + .object({ + operator: RemovablePersonInformationSchema.optional(), + hasOperator: z.enum([YES, NO]), + }) + .refine( + ({ hasOperator, operator }) => { + if (hasOperator === NO) return true + return operator && operator.name && operator.name.length > 0 + }, + { + path: ['operator', 'name'], + }, + ) + .refine( + ({ hasOperator, operator }) => { + if (hasOperator === NO) return true + return ( + operator && + operator.nationalId && + operator.nationalId.length > 0 && + kennitala.isValid(operator.nationalId) + ) + }, + { + path: ['operator', 'nationalId'], + }, + ) + .refine( + ({ hasOperator, operator }) => { + if (hasOperator === NO) return true + return operator && operator.address && operator.address.length > 0 + }, + { + path: ['operator', 'address'], + }, + ) + .refine( + ({ hasOperator, operator }) => { + if (hasOperator === NO) return true + return operator && operator.postCode && operator.postCode.length > 0 + }, + { + path: ['operator', 'postCode'], + }, + ) + .refine( + ({ hasOperator, operator }) => { + if (hasOperator === NO) return true + return operator && operator.phone && operator.phone.length > 0 + }, + { + path: ['operator', 'phone'], + }, + ) + .refine( + ({ hasOperator, operator }) => { + if (hasOperator === NO) return true + return operator && operator.email && operator.email.length > 0 + }, + { + path: ['operator', 'email'], + }, + ), machine: z.object({ machineType: z .object({ diff --git a/libs/application/templates/aosh/register-new-machine/src/lib/messages/machine.ts b/libs/application/templates/aosh/register-new-machine/src/lib/messages/machine.ts index 8dc427c54725..1e9c5992949e 100644 --- a/libs/application/templates/aosh/register-new-machine/src/lib/messages/machine.ts +++ b/libs/application/templates/aosh/register-new-machine/src/lib/messages/machine.ts @@ -143,6 +143,16 @@ export const machine = { defaultMessage: 'Er vélin ný eða notuð?', description: `Basic machine information is used or new label`, }, + new: { + id: 'aosh.rnm.application:machine.labels.basicMachineInformation.new', + defaultMessage: 'Ný', + description: `Basic machine information new label`, + }, + used: { + id: 'aosh.rnm.application:machine.labels.basicMachineInformation.used', + defaultMessage: 'Notuð', + description: `Basic machine information used label`, + }, registrationInformationTitle: { id: 'aosh.rnm.application:machine.labels.basicMachineInformation.registrationInformationTitle', defaultMessage: 'Skráningaupplýsingar', diff --git a/libs/application/templates/aosh/register-new-machine/src/utils/formatDate.ts b/libs/application/templates/aosh/register-new-machine/src/utils/formatDate.ts new file mode 100644 index 000000000000..70f6ad10309f --- /dev/null +++ b/libs/application/templates/aosh/register-new-machine/src/utils/formatDate.ts @@ -0,0 +1,9 @@ +import format from 'date-fns/format' +import is from 'date-fns/locale/is' +import parseISO from 'date-fns/parseISO' + +export const formatDate = (date: string) => { + return format(parseISO(date), 'dd.MM.yyyy', { + locale: is, + }) +} diff --git a/libs/application/templates/aosh/register-new-machine/src/utils/formatPhoneNumber.ts b/libs/application/templates/aosh/register-new-machine/src/utils/formatPhoneNumber.ts new file mode 100644 index 000000000000..f4b75cf49acf --- /dev/null +++ b/libs/application/templates/aosh/register-new-machine/src/utils/formatPhoneNumber.ts @@ -0,0 +1,6 @@ +import { parsePhoneNumberFromString } from 'libphonenumber-js' + +export const formatPhoneNumber = (phoneNumber: string): string => { + const phone = parsePhoneNumberFromString(phoneNumber, 'IS') + return phone?.formatNational() || phoneNumber +} diff --git a/libs/application/templates/aosh/register-new-machine/src/utils/getBasicMachineInformation.ts b/libs/application/templates/aosh/register-new-machine/src/utils/getBasicMachineInformation.ts index d8b66c052bb3..d559246a29a8 100644 --- a/libs/application/templates/aosh/register-new-machine/src/utils/getBasicMachineInformation.ts +++ b/libs/application/templates/aosh/register-new-machine/src/utils/getBasicMachineInformation.ts @@ -1,7 +1,8 @@ import { getValueViaPath } from '@island.is/application/core' -import { FormatMessage, FormValue } from '@island.is/application/types' +import { FormatMessage, FormValue, YES } from '@island.is/application/types' import { AboutMachine, BasicInformation } from '../lib/dataSchema' -import { machine } from '../lib/messages' +import { information, machine } from '../lib/messages' +import { NEW } from '../shared/types' export const getBasicMachineInformation = ( answers: FormValue, @@ -39,13 +40,21 @@ export const getBasicMachineInformation = ( machine.labels.basicMachineInformation.productionNumber, )}: ${basicMachineInformation.productionNumber}`, `${formatMessage(machine.labels.basicMachineInformation.markedCE)}: ${ - basicMachineInformation.markedCE + basicMachineInformation.markedCE === YES + ? formatMessage(information.labels.radioButtons.radioOptionYes) + : formatMessage(information.labels.radioButtons.radioOptionNo) }`, `${formatMessage( machine.labels.basicMachineInformation.preRegistration, - )}: ${basicMachineInformation.preRegistration}`, + )}: ${ + basicMachineInformation.preRegistration === YES + ? formatMessage(information.labels.radioButtons.radioOptionYes) + : formatMessage(information.labels.radioButtons.radioOptionNo) + }`, `${formatMessage(machine.labels.basicMachineInformation.isUsed)}: ${ - basicMachineInformation.isUsed + basicMachineInformation.isUsed === NEW + ? formatMessage(machine.labels.basicMachineInformation.new) + : formatMessage(machine.labels.basicMachineInformation.used) }`, `${formatMessage(machine.labels.basicMachineInformation.location)}: ${ basicMachineInformation.location diff --git a/libs/application/templates/aosh/register-new-machine/src/utils/getPersonInformationForOverview.ts b/libs/application/templates/aosh/register-new-machine/src/utils/getPersonInformationForOverview.ts index 12206495b070..a8292a1e6518 100644 --- a/libs/application/templates/aosh/register-new-machine/src/utils/getPersonInformationForOverview.ts +++ b/libs/application/templates/aosh/register-new-machine/src/utils/getPersonInformationForOverview.ts @@ -2,6 +2,8 @@ import { getValueViaPath } from '@island.is/application/core' import { FormatMessage, FormValue } from '@island.is/application/types' import { PersonInformation } from '../lib/dataSchema' import { information } from '../lib/messages' +import { format as formatKennitala } from 'kennitala' +import { formatPhoneNumber } from '../utils' export const getPersonInformationForOverview = ( person: string, @@ -15,10 +17,12 @@ export const getPersonInformationForOverview = ( return [ personInformation.name, - personInformation.nationalId, + personInformation.nationalId + ? formatKennitala(personInformation.nationalId) + : '', `${personInformation.address}, ${personInformation.postCode}`, `${formatMessage(information.labels.owner.phone)}: ${ - personInformation.phone + personInformation.phone ? formatPhoneNumber(personInformation.phone) : '' }`, personInformation.email, ] diff --git a/libs/application/templates/aosh/register-new-machine/src/utils/index.ts b/libs/application/templates/aosh/register-new-machine/src/utils/index.ts index db7d3194c8e8..dbb381d187a8 100644 --- a/libs/application/templates/aosh/register-new-machine/src/utils/index.ts +++ b/libs/application/templates/aosh/register-new-machine/src/utils/index.ts @@ -6,3 +6,5 @@ export * from './getStreetRegistrationInformation' export * from './canRegisterToTraffic' export * from './canMaybeRegisterToTraffic' export * from './getTechnicalInformation' +export * from './formatPhoneNumber' +export * from './formatDate' diff --git a/libs/application/templates/directorate-of-immigration/citizenship/src/forms/CitizenshipForm/PersonalSection/UserInformationSubSection.ts b/libs/application/templates/directorate-of-immigration/citizenship/src/forms/CitizenshipForm/PersonalSection/UserInformationSubSection.ts index e092a4a2525d..5df130ebc371 100644 --- a/libs/application/templates/directorate-of-immigration/citizenship/src/forms/CitizenshipForm/PersonalSection/UserInformationSubSection.ts +++ b/libs/application/templates/directorate-of-immigration/citizenship/src/forms/CitizenshipForm/PersonalSection/UserInformationSubSection.ts @@ -58,7 +58,7 @@ export const UserInformationSubSection = buildSubSection({ undefined, ) as NationalRegistryIndividual | undefined - return `${individual?.givenName} ${individual?.familyName}` + return individual?.fullName }, }), buildTextField({ diff --git a/libs/application/templates/directorate-of-immigration/citizenship/src/lib/CitizenshipTemplate.ts b/libs/application/templates/directorate-of-immigration/citizenship/src/lib/CitizenshipTemplate.ts index 936f02db4cc2..f4473ae7f15d 100644 --- a/libs/application/templates/directorate-of-immigration/citizenship/src/lib/CitizenshipTemplate.ts +++ b/libs/application/templates/directorate-of-immigration/citizenship/src/lib/CitizenshipTemplate.ts @@ -120,7 +120,7 @@ const template: ApplicationTemplate< }, ], }, - lifecycle: pruneAfterDays(1), + lifecycle: pruneAfterDays(30), onExit: defineTemplateApi({ action: ApiActions.validateApplication, }), diff --git a/libs/application/templates/directorate-of-immigration/citizenship/src/lib/messages/confirmation.ts b/libs/application/templates/directorate-of-immigration/citizenship/src/lib/messages/confirmation.ts index c3631ab03312..b48732c1a65e 100644 --- a/libs/application/templates/directorate-of-immigration/citizenship/src/lib/messages/confirmation.ts +++ b/libs/application/templates/directorate-of-immigration/citizenship/src/lib/messages/confirmation.ts @@ -23,7 +23,7 @@ export const confirmation = { description: 'Confirmation accordion title', }, accordionText: { - id: 'doi.cs.application:confirmation.general.accordionText', + id: 'doi.cs.application:confirmation.general.accordionText#markdown', defaultMessage: `* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis eu nulla porta, luctus mi ac, pharetra mauris\n`, description: 'Confirmation accordion text', }, diff --git a/libs/application/templates/driving-license/src/forms/prerequisites/sectionApplicationFor.ts b/libs/application/templates/driving-license/src/forms/prerequisites/sectionApplicationFor.ts index 1c4686790468..323267bc98ad 100644 --- a/libs/application/templates/driving-license/src/forms/prerequisites/sectionApplicationFor.ts +++ b/libs/application/templates/driving-license/src/forms/prerequisites/sectionApplicationFor.ts @@ -84,6 +84,7 @@ export const sectionApplicationFor = (allowBELicense = false) => disabled: !currentLicense || age < 18 || + age >= 65 || categories?.some((c) => c.nr.toUpperCase() === 'BE') || // validToCode === 8 is temporary license and should not be applicable for BE !categories?.some( diff --git a/libs/application/templates/estate/src/fields/EstateMembersRepeater/index.tsx b/libs/application/templates/estate/src/fields/EstateMembersRepeater/index.tsx index afff3c39b2f5..9f1603f1e02b 100644 --- a/libs/application/templates/estate/src/fields/EstateMembersRepeater/index.tsx +++ b/libs/application/templates/estate/src/fields/EstateMembersRepeater/index.tsx @@ -12,7 +12,6 @@ import { } from '@island.is/island-ui/core' import { m } from '../../lib/messages' import * as kennitala from 'kennitala' -import { EstateRegistrant } from '@island.is/clients/syslumenn' import { Answers, EstateMember } from '../../types' import { AdditionalEstateMember } from './AdditionalEstateMember' import { getValueViaPath } from '@island.is/application/core' @@ -175,7 +174,12 @@ export const EstateMembersRepeater: FC< ...acc, 0 ? 7 : 0} key={index}> - {formatMessage(m.estateMember)} + + {formatMessage(m.estateMember)} + + )} + )} diff --git a/libs/application/ui-components/src/components/ApplicationCard/utils/history.tsx b/libs/application/ui-components/src/components/ApplicationCard/utils/history.tsx index 1e36f10e2065..0832446647e7 100644 --- a/libs/application/ui-components/src/components/ApplicationCard/utils/history.tsx +++ b/libs/application/ui-components/src/components/ApplicationCard/utils/history.tsx @@ -42,7 +42,9 @@ export const buildHistoryItems = ( onClick={openApplication} icon="pencil" > - {formatMessage(coreMessages.cardButtonDraft)} + {application.actionCard.pendingAction.button + ? formatMessage(application.actionCard.pendingAction.button) + : formatMessage(coreMessages.cardButtonDraft)} ) : undefined diff --git a/libs/application/ui-components/src/index.ts b/libs/application/ui-components/src/index.ts index a858a0a16536..9f3b2597ec4b 100644 --- a/libs/application/ui-components/src/index.ts +++ b/libs/application/ui-components/src/index.ts @@ -12,6 +12,7 @@ export { formatPhoneNumber, removeCountryCode, formatCurrency, + formatCurrencyWithoutSuffix, } from './utilities/formatters' export { handleServerError } from './utilities/handleServerError' export { InputImageUpload } from './components/InputImageUpload/InputImageUpload' diff --git a/libs/application/ui-components/src/utilities/formatters.ts b/libs/application/ui-components/src/utilities/formatters.ts index 8620d63f6b92..ee0ae4fc4cc3 100644 --- a/libs/application/ui-components/src/utilities/formatters.ts +++ b/libs/application/ui-components/src/utilities/formatters.ts @@ -24,3 +24,6 @@ export const removeCountryCode = (phone: string) => { export const formatCurrency = (answer: string) => answer.replace(/\B(?=(\d{3})+(?!\d))/g, '.') + ' kr.' + +export const formatCurrencyWithoutSuffix = (answer: string) => + answer.replace(/\B(?=(\d{3})+(?!\d))/g, '.') diff --git a/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterFormField.tsx b/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterFormField.tsx index 225e3acdac89..85a934fd5ad8 100644 --- a/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterFormField.tsx +++ b/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterFormField.tsx @@ -4,44 +4,27 @@ import { TableRepeaterField, } from '@island.is/application/types' import { - Stack, + AlertMessage, Box, Button, + GridRow, + Icon, + Stack, Table as T, Text, - Icon, Tooltip, - GridRow, - GridColumn, - AlertMessage, } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' -import { - CheckboxController, - DatePickerController, - FieldDescription, - InputController, - RadioController, - SelectController, -} from '@island.is/shared/form-fields' +import { FieldDescription } from '@island.is/shared/form-fields' import { FC, useState } from 'react' import { useFieldArray, useFormContext, useWatch } from 'react-hook-form' -import { NationalIdWithName } from '@island.is/application/ui-components' import { handleCustomMappedValues } from './utils' +import { Item } from './TableRepeaterItem' interface Props extends FieldBaseProps { field: TableRepeaterField } -const componentMapper = { - input: InputController, - select: SelectController, - checkbox: CheckboxController, - date: DatePickerController, - radio: RadioController, - nationalIdWithName: NationalIdWithName, -} - export const TableRepeaterFormField: FC = ({ application, field: data, @@ -117,16 +100,6 @@ export const TableRepeaterFormField: FC = ({ setActiveIndex(index) } - const getFieldError = (id: string) => { - /** - * Errors that occur in a field-array have incorrect typing - * This hack is needed to get the correct type - */ - const errorList = error as unknown as Record[] | undefined - const errors = errorList?.[activeIndex] - return errors?.[id] - } - const formatTableValue = (key: string, item: Record) => { const formatFn = table?.format?.[key] const formatted = formatFn ? formatFn(item[key]) : item[key] @@ -242,102 +215,17 @@ export const TableRepeaterFormField: FC = ({ )} - {items.map((item) => { - const { - component, - id: itemId, - backgroundColor = 'blue', - label = '', - placeholder = '', - options, - width = 'full', - condition, - readonly = false, - ...props - } = item - const isHalfColumn = component !== 'radio' && width === 'half' - const isThirdColumn = - component !== 'radio' && width === 'third' - const span = isHalfColumn - ? '1/2' - : isThirdColumn - ? '1/3' - : '1/1' - const Component = componentMapper[component] - const id = `${data.id}[${activeIndex}].${itemId}` - const activeValues = - activeIndex >= 0 && values ? values[activeIndex] : undefined - - let translatedOptions: any = [] - if (typeof options === 'function') { - translatedOptions = options(application, activeValues) - } else { - translatedOptions = options?.map((option) => ({ - ...option, - label: formatText( - option.label, - application, - formatMessage, - ), - ...(option.tooltip && { - tooltip: formatText( - option.tooltip, - application, - formatMessage, - ), - }), - })) - } - - let Readonly: boolean | undefined - if (typeof readonly === 'function') { - Readonly = readonly(application, activeValues) - } else { - Readonly = readonly - } - - if (condition && !condition(application, activeValues)) { - return null - } - - return ( - - {component === 'radio' && label && ( - - {formatText(label, application, formatMessage)} - - )} - { - if (error) { - methods.clearErrors(id) - } - }} - application={application} - {...props} - /> - - ) - })} + {items.map((item) => ( + + ))}