diff --git a/libs/api/domains/vehicles/project.json b/libs/api/domains/vehicles/project.json index 12892a54a5b4..b08b0b11eb44 100644 --- a/libs/api/domains/vehicles/project.json +++ b/libs/api/domains/vehicles/project.json @@ -14,6 +14,12 @@ "options": { "jestConfig": "libs/api/domains/vehicles/jest.config.ts" } + }, + "extract-strings": { + "executor": "nx:run-commands", + "options": { + "command": "yarn ts-node -P libs/localization/tsconfig.lib.json libs/localization/scripts/extract 'libs/api/domains/vehicles/src/lib/messages.ts'" + } } } } diff --git a/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestOverview.input.ts b/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestOverview.input.ts index 65b238fb9150..3ad3e1bb1e97 100644 --- a/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestOverview.input.ts +++ b/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestOverview.input.ts @@ -1,7 +1,11 @@ import { Field, ID, InputType } from '@nestjs/graphql' +import type { Locale } from '@island.is/shared/types' @InputType() export class BulkVehicleMileageRequestOverviewInput { + @Field(() => String) + locale!: Locale + @Field(() => ID) guid!: string } diff --git a/libs/api/domains/vehicles/src/lib/messages.ts b/libs/api/domains/vehicles/src/lib/messages.ts new file mode 100644 index 000000000000..2d3b82517ee0 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/messages.ts @@ -0,0 +1,83 @@ +import { defineMessages } from 'react-intl' + +export const m = defineMessages({ + tooManyPermno: { + id: 'api.bulk-vehicle-mileage:too-many-permno', + defaultMessage: 'Sama fastanúmer birtist oft í skjali', + }, + missingPermno: { + id: 'api.bulk-vehicle-mileage:missing-permno', + defaultMessage: 'Fastanúmer vantar', + }, + dateMissing: { + id: 'api.bulk-vehicle-mileage:date-missing', + defaultMessage: 'Dagsetning álesturs vantar', + }, + originMissing: { + id: 'api.bulk-vehicle-mileage:missing-origin', + defaultMessage: 'Uppruna álesturs vantar', + }, + mileageMissing: { + id: 'api.bulk-vehicle-mileage:missing-mileage', + defaultMessage: 'Álestur vantar', + }, + mileageTooLow: { + id: 'api.bulk-vehicle-mileage:mileage-too-low', + defaultMessage: 'Km staða getur ekki verið minna en 0', + }, + mileageLowerThanBefore: { + id: 'api.bulk-vehicle-mileage:mileage-lower-than-before', + defaultMessage: + 'Km staða álesturs getur ekki verið minni en síðasta gildi sem skráð hefur verið á ökutækið', + }, + originNotFound: { + id: 'api.bulk-vehicle-mileage:origin-not-found', + defaultMessage: 'Staðartegund í álestri finnst ekki', + }, + carNotFound: { + id: 'api.bulk-vehicle-mileage:car-not-found', + defaultMessage: 'Ökutæki finnst ekki', + }, + dateTooEarly: { + id: 'api.bulk-vehicle-mileage:date-too-early', + defaultMessage: 'Dagsetning færslu minni en nýjasta færsla á ökutæki', + }, + invalidUpdate: { + id: 'api.bulk-vehicle-mileage:invalid-update', + defaultMessage: 'Ekki má breyta færslu sem er ekki innan dagsins í dag', + }, + registerTooEarly: { + id: 'api.bulk-vehicle-mileage:register-too-early', + defaultMessage: + 'Villa við skráningu, ekki má skrá innan 30 daga frá síðustu færslu', + }, + forbiddenUpdate: { + id: 'api.bulk-vehicle-mileage:forbidden-update', + defaultMessage: 'Ekki má breyta færslu sem er ekki nýjasta færsla ökutækis', + }, + unauthorizedUpdater: { + id: 'api.bulk-vehicle-mileage:unauthorized-updater', + defaultMessage: + 'Tilkynnandi eða innsendur tilkynnandi er hvorki umráðamaður né eigandi ökutækis', + }, + invalidMileage: { + id: 'api.bulk-vehicle-mileage:invalid-mileage', + defaultMessage: 'Tegund álesturs ekki til', + }, + invalidDelete: { + id: 'api.bulk-vehicle-mileage:invalid-delete', + defaultMessage: 'Aðeins er leyfilegt að eyða nýjustu km skráningu', + }, + notFoundDelete: { + id: 'api.bulk-vehicle-mileage:not-found-delete', + defaultMessage: 'Færsla til að eyða finnst ekki', + }, + unnecessaryRegistration: { + id: 'api.bulk-vehicle-mileage:unnecessary-registration', + defaultMessage: 'Ökutæki krefst ekki aflesturs', + }, + tooHighMileage: { + id: 'api.bulk-vehicle-mileage:too-high-mileage', + defaultMessage: 'Km staða fer yfir hámark per dag', + }, +}) diff --git a/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestError.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestError.model.ts index a289df9b6cfa..f8f6bf80f1dc 100644 --- a/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestError.model.ts +++ b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestError.model.ts @@ -1,4 +1,4 @@ -import { Field, ObjectType } from '@nestjs/graphql' +import { Field, Int, ObjectType } from '@nestjs/graphql' @ObjectType() export class VehiclesBulkMileageRegistrationRequestError { @@ -7,4 +7,10 @@ export class VehiclesBulkMileageRegistrationRequestError { @Field({ nullable: true }) message?: string + + @Field(() => Int, { nullable: true }) + warningSerialCode?: number + + @Field({ nullable: true }) + warningText?: string } diff --git a/libs/api/domains/vehicles/src/lib/resolvers/bulkMileage.resolver.ts b/libs/api/domains/vehicles/src/lib/resolvers/bulkMileage.resolver.ts index a551c3dc893a..f8c40cfdfa30 100644 --- a/libs/api/domains/vehicles/src/lib/resolvers/bulkMileage.resolver.ts +++ b/libs/api/domains/vehicles/src/lib/resolvers/bulkMileage.resolver.ts @@ -66,6 +66,7 @@ export class VehiclesBulkMileageResolver { ) { return this.bulkService.getBulkMileageRegistrationRequestOverview( user, + input.locale, input.guid, ) } diff --git a/libs/api/domains/vehicles/src/lib/services/bulkMileage.service.ts b/libs/api/domains/vehicles/src/lib/services/bulkMileage.service.ts index d5aec6f44140..f34392b199aa 100644 --- a/libs/api/domains/vehicles/src/lib/services/bulkMileage.service.ts +++ b/libs/api/domains/vehicles/src/lib/services/bulkMileage.service.ts @@ -7,6 +7,7 @@ import { AuthMiddleware } from '@island.is/auth-nest-tools' import type { Auth, User } from '@island.is/auth-nest-tools' import { PostVehicleBulkMileageInput } from '../dto/postBulkVehicleMileage.input' import { isDefined } from '@island.is/shared/utils' +import type { Locale } from '@island.is/shared/types' import { LOG_CATEGORY } from '../constants' import { LOGGER_PROVIDER, type Logger } from '@island.is/logging' import { VehiclesBulkMileageReadingResponse } from '../models/v3/bulkMileage/bulkMileageReadingResponse.model' @@ -14,11 +15,16 @@ import { VehiclesBulkMileageRegistrationJobHistory } from '../models/v3/bulkMile import { VehiclesBulkMileageRegistrationRequestStatus } from '../models/v3/bulkMileage/bulkMileageRegistrationRequestStatus.model' import { VehiclesBulkMileageRegistrationRequestOverview } from '../models/v3/bulkMileage/bulkMileageRegistrationRequestOverview.model' import { FetchError } from '@island.is/clients/middlewares' +import { IntlService } from '@island.is/cms-translations' +import { errorCodeMessageMap } from './errorCodes' + +const namespaces = ['api.bulk-vehicle-mileage'] @Injectable() export class BulkMileageService { constructor( private mileageReadingApi: MileageReadingApi, + private readonly intlService: IntlService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -59,7 +65,6 @@ export class BulkMileageService { return { requestId: res.guid, - errorMessage: res.errorMessage ?? undefined, } } catch (e) { const error: Error = e @@ -127,8 +132,10 @@ export class BulkMileageService { async getBulkMileageRegistrationRequestOverview( auth: User, + locale: Locale, input: GetbulkmileagereadingrequeststatusGuidGetRequest['guid'], ): Promise { + const { formatMessage } = await this.intlService.useIntl(namespaces, locale) const data = await this.getMileageWithAuth( auth, ).getbulkmileagereadingrequestdetailsGuidGet({ guid: input }) @@ -139,15 +146,25 @@ export class BulkMileageService { if (!d.guid || !d.permno) { return null } + return { guid: d.guid, vehicleId: d.permno, mileage: d.mileage ?? undefined, returnCode: d.returnCode ?? undefined, - errors: d.errors?.map((e) => ({ - code: e.errorCode ?? undefined, - message: e.errorText ?? undefined, - })), + errors: d.errors?.map((e) => { + const warningSerial = + e.warningSerial === -1 ? 999 : e.warningSerial + + return { + code: e.errorCode ?? undefined, + message: e.errorText ?? undefined, + warningSerialCode: e.warningSerial, + warningText: warningSerial + ? formatMessage(errorCodeMessageMap[warningSerial]) + : undefined, + } + }), } }) .filter(isDefined), diff --git a/libs/api/domains/vehicles/src/lib/services/errorCodes.ts b/libs/api/domains/vehicles/src/lib/services/errorCodes.ts new file mode 100644 index 000000000000..ba2f1423f82f --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/services/errorCodes.ts @@ -0,0 +1,24 @@ +import { MessageDescriptor } from 'react-intl' +import { m } from '../../lib/messages' + +export const errorCodeMessageMap: Record = { + 999: m.tooManyPermno, //returned error code is -1, which cant be used to index. Transform to 999 + 3: m.missingPermno, + 4: m.dateMissing, + 5: m.originMissing, + 6: m.mileageMissing, + 7: m.mileageTooLow, + 8: m.mileageLowerThanBefore, + 9: m.originNotFound, + 10: m.carNotFound, + 11: m.dateTooEarly, + 12: m.invalidUpdate, + 13: m.registerTooEarly, + 14: m.forbiddenUpdate, + 15: m.unauthorizedUpdater, + 16: m.invalidMileage, + 17: m.invalidDelete, + 18: m.notFoundDelete, + 19: m.unnecessaryRegistration, + 20: m.tooHighMileage, +} diff --git a/libs/api/domains/vehicles/src/lib/services/vehicles.service.ts b/libs/api/domains/vehicles/src/lib/services/vehicles.service.ts index b573cdeec805..018749f96b3c 100644 --- a/libs/api/domains/vehicles/src/lib/services/vehicles.service.ts +++ b/libs/api/domains/vehicles/src/lib/services/vehicles.service.ts @@ -11,7 +11,6 @@ import { VehicleDtoListPagedResponse, PersidnoLookupResultDto, CurrentVehiclesWithMilageAndNextInspDtoListPagedResponse, - ApiResponse, } from '@island.is/clients/vehicles' import { CanregistermileagePermnoGetRequest, @@ -19,7 +18,6 @@ import { MileageReadingApi, MileageReadingDto, PostMileageReadingModel, - PutMileageReadingModel, RequiresmileageregistrationPermnoGetRequest, RootPostRequest, RootPutRequest, @@ -35,10 +33,7 @@ import { GetVehiclesForUserInput, GetVehiclesListV2Input, } from '../dto/getVehiclesForUserInput' -import { - VehicleMileageDetail, - VehicleMileageOverview, -} from '../models/getVehicleMileage.model' +import { VehicleMileageOverview } from '../models/getVehicleMileage.model' import isSameDay from 'date-fns/isSameDay' import { mileageDetailConstructor } from '../utils/helpers' import { FetchError, handle404 } from '@island.is/clients/middlewares' @@ -470,9 +465,11 @@ export class VehiclesService { throw new ForbiddenException(UNAUTHORIZED_OWNERSHIP_LOG) } - return this.getMileageWithAuth(auth).rootPut({ + const dtos = await this.getMileageWithAuth(auth).rootPut({ putMileageReadingModel: input, }) + + return dtos.length > 0 ? dtos[0] : null } async postMileageReadingV2( @@ -537,9 +534,10 @@ export class VehiclesService { } try { - return this.getMileageWithAuth(auth).rootPut({ + const dtos = await this.getMileageWithAuth(auth).rootPut({ putMileageReadingModel: input, }) + return dtos.length > 0 ? dtos[0] : null } catch (e) { if (e instanceof FetchError && (e.status === 400 || e.status === 429)) { const errorBody = e.body as UpdateResponseError diff --git a/libs/api/domains/vehicles/src/lib/vehicles.module.ts b/libs/api/domains/vehicles/src/lib/vehicles.module.ts index 3fcb00edafd3..86dbbc62cce4 100644 --- a/libs/api/domains/vehicles/src/lib/vehicles.module.ts +++ b/libs/api/domains/vehicles/src/lib/vehicles.module.ts @@ -11,6 +11,7 @@ import { FeatureFlagModule } from '@island.is/nest/feature-flags' import { BulkMileageService } from './services/bulkMileage.service' import { VehiclesV3Resolver } from './resolvers/vehicleV3.resolver' import { VehiclesBulkMileageResolver } from './resolvers/bulkMileage.resolver' +import { CmsTranslationsModule } from '@island.is/cms-translations' @Module({ providers: [ @@ -26,6 +27,7 @@ import { VehiclesBulkMileageResolver } from './resolvers/bulkMileage.resolver' VehiclesClientModule, VehiclesMileageClientModule, AuthModule, + CmsTranslationsModule, FeatureFlagModule, ], exports: [VehiclesService], diff --git a/libs/clients/vehicles-mileage/src/clientConfig.json b/libs/clients/vehicles-mileage/src/clientConfig.json index b934c4f3088e..1af507c8b7a0 100644 --- a/libs/clients/vehicles-mileage/src/clientConfig.json +++ b/libs/clients/vehicles-mileage/src/clientConfig.json @@ -2,13 +2,14 @@ "openapi": "3.0.1", "info": { "title": "SGS Rest API", - "description": "Mileage reading API developed in .Net8.0 - Release-6 : 20231122.2", + "description": "Mileage reading API developed in .Net8.0 - Release-21 : 20241105.1", "contact": { "name": "Samgöngustofa", "email": "tolvuhjalp@samgongustofa.is" }, "version": "1.0" }, + "servers": [{ "url": "/vehicle/mileagereading" }], "paths": { "/authenticate": { "post": { @@ -243,11 +244,14 @@ } }, "responses": { - "201": { + "200": { "description": "OK", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/MileageReadingDto" } + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/MileageReadingDto" } + } } } }, @@ -788,6 +792,11 @@ "description": "Error code", "nullable": true }, + "warningSerial": { + "type": "integer", + "description": "Warning serial", + "format": "int32" + }, "errorText": { "type": "string", "description": "Error text", @@ -850,11 +859,6 @@ "type": "string", "description": "Guid to check for status and results", "nullable": true - }, - "errorMessage": { - "type": "string", - "description": "Error message if any", - "nullable": true } }, "additionalProperties": false, diff --git a/libs/service-portal/assets/src/lib/messages.ts b/libs/service-portal/assets/src/lib/messages.ts index caef9991b01f..3e6a455f3bac 100644 --- a/libs/service-portal/assets/src/lib/messages.ts +++ b/libs/service-portal/assets/src/lib/messages.ts @@ -218,6 +218,11 @@ export const vehicleMessage = defineMessages({ id: 'sp.vehicles:not-found', defaultMessage: 'Ökutæki fannst ekki', }, + invalidFileType: { + id: 'sp.vehicles:invalid-file-type', + defaultMessage: + 'Ógild skráargerð. Einungis .xlsx og .csv skrár eru samþykktar', + }, infoNote: { id: 'sp.vehicles:detail-info-note', defaultMessage: @@ -981,6 +986,10 @@ export const vehicleMessage = defineMessages({ id: 'sp.vehicles:upload-failed', defaultMessage: 'Upphleðsla mistókst', }, + noDataInUploadedFile: { + id: 'sp.vehicles:no-data-in-uploaded-file', + defaultMessage: 'Upphleðsla mistókst. Engin gögn í skjali', + }, wrongFileType: { id: 'sp.vehicles:wrong-file-type', defaultMessage: 'Vitlaus skráartýpa. Skrá verður að vera .csv eða .xslx', @@ -989,6 +998,16 @@ export const vehicleMessage = defineMessages({ id: 'sp.vehicles:error-while-processing', defaultMessage: 'Villa við að meðhöndla skjal. Villur: ', }, + invalidPermNoColumn: { + id: 'sp.vehicles:invalid-perm-no-column', + defaultMessage: + 'Fastanúmersdálk vantar eða er skrifaður rangt. Dálkanafn þarf að vera eitt af eftirfarandi; "permno", "vehicleid", "bilnumer","okutaeki","fastanumer"', + }, + invalidMileageColumn: { + id: 'sp.vehicles:invalid-mileage-column', + defaultMessage: + 'Kílómetrastöðudálk vantar eða er skrifaður rangt. Dálkanafn þarf að vera eitt af eftirfarandi; "kilometrastada", "mileage", "odometer"', + }, downloadFailed: { id: 'sp.vehicles:download-failed', defaultMessage: 'Niðurhal mistókst', diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.tsx index 635218334b18..787ab421d3a7 100644 --- a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.tsx +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.tsx @@ -3,8 +3,8 @@ import { useLocale, useNamespaces } from '@island.is/localization' import { m, SAMGONGUSTOFA_SLUG, - IntroHeader, LinkButton, + IntroWrapper, } from '@island.is/service-portal/core' import { vehicleMessage as messages, vehicleMessage } from '../../lib/messages' import * as styles from './VehicleBulkMileage.css' @@ -61,7 +61,7 @@ const VehicleBulkMileage = () => { return ( - @@ -83,44 +83,45 @@ const VehicleBulkMileage = () => { } serviceProviderSlug={SAMGONGUSTOFA_SLUG} serviceProviderTooltip={formatMessage(m.vehiclesTooltip)} - /> - - - - - - {error && !loading && } - {!error && ( - - )} + > + + + + + + {error && !loading && } + {!error && ( + + )} - {totalPages > 1 && ( - ( - - )} - /> - )} - + {totalPages > 1 && ( + ( + + )} + /> + )} + + ) diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageRow.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageRow.tsx index 6536795c32aa..93a18bbae7c5 100644 --- a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageRow.tsx +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageRow.tsx @@ -8,7 +8,7 @@ import { useGetUsersMileageLazyQuery, } from './VehicleBulkMileage.generated' -import { useEffect, useState, useCallback } from 'react' +import { useEffect, useState, useCallback, useMemo } from 'react' import { ExpandRow, NestedFullTable, @@ -22,6 +22,7 @@ import { InputController } from '@island.is/shared/form-fields' import * as styles from './VehicleBulkMileage.css' import { displayWithUnit } from '../../utils/displayWithUnit' import { isReadDateToday } from '../../utils/readDate' +import { isDefined } from '@island.is/shared/utils' const ORIGIN_CODE = 'ISLAND.IS' @@ -210,6 +211,42 @@ export const VehicleBulkMileageRow = ({ vehicle }: Props) => { } }, [mileageData?.vehicleMileageDetails, vehicle.vehicleId]) + const nestedTable = useMemo(() => { + if (!data?.vehiclesMileageRegistrationHistory) { + return [[]] + } + const tableData: Array> = [[]] + if (data?.vehiclesMileageRegistrationHistory?.lastMileageRegistration) { + tableData.push([ + formatDate( + data.vehiclesMileageRegistrationHistory.lastMileageRegistration.date, + ), + data.vehiclesMileageRegistrationHistory.lastMileageRegistration + .originCode, + //'-', + displayWithUnit( + data.vehiclesMileageRegistrationHistory.lastMileageRegistration + .mileage, + 'km', + true, + ), + ]) + } + for (const mileageRegistration of data?.vehiclesMileageRegistrationHistory + ?.mileageRegistrationHistory ?? []) { + if (mileageRegistration) { + tableData.push([ + formatDate(mileageRegistration.date), + mileageRegistration.originCode, + //'-', + displayWithUnit(mileageRegistration.mileage, 'km', true), + ]) + } + } + + return tableData + }, [data?.vehiclesMileageRegistrationHistory]) + return ( { ]} loading={loading} emptyMessage={formatMessage(vehicleMessage.mileageHistoryNotFound)} - data={ - data?.vehiclesMileageRegistrationHistory?.mileageRegistrationHistory?.map( - (r) => [ - formatDate(r.date), - r.originCode, - //'-', - displayWithUnit(r.mileage, 'km', true), - ], - ) ?? [] - } + data={nestedTable} /> )} diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.graphql b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.graphql index 41047a57ba5c..d6c342f0a2b8 100644 --- a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.graphql +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.graphql @@ -18,6 +18,8 @@ query getJobRegistrations($input: BulkVehicleMileageRequestOverviewInput!) { errors { code message + warningSerialCode + warningText } } } diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.tsx index 8b2bc2750962..a82691339e33 100644 --- a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.tsx +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.tsx @@ -8,12 +8,12 @@ import { } from '@island.is/island-ui/core' import { useLocale, useNamespaces } from '@island.is/localization' import { - IntroHeader, SAMGONGUSTOFA_SLUG, m, EmptyTable, TableGrid, downloadFile, + IntroWrapper, } from '@island.is/service-portal/core' import { Problem } from '@island.is/react-spa/shared' import { VehiclesBulkMileageRegistrationRequestStatus } from '@island.is/api/schema' @@ -34,7 +34,7 @@ type UseParams = { const VehicleBulkMileageJobDetail = () => { useNamespaces('sp.vehicles') - const { formatMessage } = useLocale() + const { formatMessage, locale } = useLocale() const { id } = useParams() as UseParams const { data, loading, error, refetch, networkStatus } = @@ -57,6 +57,7 @@ const VehicleBulkMileageJobDetail = () => { notifyOnNetworkStatusChange: true, variables: { input: { + locale: locale, guid: id, }, }, @@ -89,7 +90,7 @@ const VehicleBulkMileageJobDetail = () => { } return [ erroredVehicle.vehicleId, - erroredVehicle.errors.map((j) => j.message).join(', '), + erroredVehicle.errors.map((j) => j.warningText).join(', '), ] }) .filter(isDefined) @@ -103,19 +104,18 @@ const VehicleBulkMileageJobDetail = () => { !(registrationNetworkStatus === NetworkStatus.refetch) return ( - - - {formatMessage(vehicleMessage.dataAboutJob)} -
- {formatMessage(vehicleMessage.refreshDataAboutJob)} - - } - serviceProviderSlug={SAMGONGUSTOFA_SLUG} - serviceProviderTooltip={formatMessage(m.vehiclesTooltip)} - > + + {formatMessage(vehicleMessage.dataAboutJob)} +
+ {formatMessage(vehicleMessage.refreshDataAboutJob)} + + } + serviceProviderSlug={SAMGONGUSTOFA_SLUG} + serviceProviderTooltip={formatMessage(m.vehiclesTooltip)} + buttonGroup={ -
+ } + > {!error && !loading && !jobsStatus && ( { {displayWithUnit(j.mileage, 'km', true)} - {(j.errors ?? []).map((j) => j.message).join(', ')} + {(j.errors ?? []) + .map((j) => j.warningText) + .join(', ')} )) @@ -268,7 +271,7 @@ const VehicleBulkMileageJobDetail = () => {
)} - + ) } diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.tsx index 648ef2116b7c..ae2b5f970c30 100644 --- a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.tsx +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.tsx @@ -7,6 +7,7 @@ import { LinkButton, EmptyTable, formatDateWithTime, + IntroWrapper, } from '@island.is/service-portal/core' import { Problem } from '@island.is/react-spa/shared' import { useGetRequestsStatusQuery } from './VehicleBulkMileageJobOverview.generated' @@ -63,16 +64,18 @@ const VehicleBulkMileageUploadJobOverview = () => { const jobs: Array = data?.vehicleBulkMileageRegistrationJobHistory?.history ?? [] - const sortedJobs = jobs.length > 1 ? [...jobs] : [] - sortedJobs.sort((a, b) => sortJobs(a, b)) + const sortedJobs = [...jobs] + if (sortedJobs.length > 1) { + sortedJobs.sort((a, b) => sortJobs(a, b)) + } + return ( - - + {error && } {!error && ( @@ -141,7 +144,7 @@ const VehicleBulkMileageUploadJobOverview = () => { message={formatMessage(vehicleMessage.noJobsFound)} /> )} - + ) } diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.tsx index 54aa3ad49589..a822c80f359b 100644 --- a/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.tsx +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.tsx @@ -10,18 +10,18 @@ import { fileExtensionWhitelist } from '@island.is/island-ui/core/types' import { useLocale, useNamespaces } from '@island.is/localization' import { useEffect, useState } from 'react' import { FileRejection } from 'react-dropzone' +import { AssetsPaths } from '../../lib/paths' +import { useVehicleBulkMileagePostMutation } from './VehicleBulkMileageUpload.generated' +import { vehicleMessage } from '../../lib/messages' +import { parseFileToMileageRecord } from '../../utils/parseFileToMileage' import { - IntroHeader, + IntroWrapper, LinkButton, SAMGONGUSTOFA_SLUG, m, } from '@island.is/service-portal/core' -import { Problem } from '@island.is/react-spa/shared' -import { AssetsPaths } from '../../lib/paths' -import { useVehicleBulkMileagePostMutation } from './VehicleBulkMileageUpload.generated' import VehicleBulkMileageFileDownloader from '../VehicleBulkMileage/VehicleBulkMileageFileDownloader' -import { vehicleMessage } from '../../lib/messages' -import { parseFileToMileageRecord } from '../../utils/parseFileToMileage' +import { Problem } from '@island.is/react-spa/shared' const extensionToType = { [fileExtensionWhitelist['.csv']]: 'csv', @@ -54,37 +54,45 @@ const VehicleBulkMileageUpload = () => { }, [data?.vehicleBulkMileagePost?.requestId]) const postMileage = async (file: File, type: 'xlsx' | 'csv') => { - try { - const records = await parseFileToMileageRecord(file, type) - if (!records.length) { + const records = await parseFileToMileageRecord(file, type) + + if (!Array.isArray(records)) { + if (records.code === 1) { + setUploadErrorMessage(formatMessage(vehicleMessage.invalidPermNoColumn)) + } else if (records.code === 2) { + setUploadErrorMessage( + formatMessage(vehicleMessage.invalidMileageColumn), + ) + } else { setUploadErrorMessage(formatMessage(vehicleMessage.uploadFailed)) - return } - if (typeof records === 'string') { - setUploadErrorMessage(records) - return - } - vehicleBulkMileagePostMutation({ - variables: { - input: { - mileageData: records.map((r) => ({ - mileageNumber: r.mileage, - vehicleId: r.vehicleId, - })), - originCode: 'ISLAND.IS', - }, - }, - }) - } catch (error) { - setUploadErrorMessage( - `${formatMessage(vehicleMessage.errorWhileProcessing) + error.message} - `, - ) + return + } + + if (!records.length) { + setUploadErrorMessage(formatMessage(vehicleMessage.noDataInUploadedFile)) + return } + vehicleBulkMileagePostMutation({ + variables: { + input: { + mileageData: records.map((r) => ({ + mileageNumber: r.mileage, + vehicleId: r.vehicleId, + })), + originCode: 'ISLAND.IS', + }, + }, + }) } - const handleOnInputFileUploadError = (files: FileRejection[]) => - setUploadErrorMessage(files[0].errors[0].message) + const handleOnInputFileUploadError = (files: FileRejection[]) => { + if (files[0].errors[0].code === 'file-invalid-type') { + setUploadErrorMessage(formatMessage(vehicleMessage.invalidFileType)) + } else { + setUploadErrorMessage(files[0].errors[0].message) + } + } const handleOnInputFileUploadRemove = () => setUploadedFile(null) @@ -113,18 +121,17 @@ const VehicleBulkMileageUpload = () => { } return ( - - + - - + } + > {error && } {data?.vehicleBulkMileagePost?.errorMessage && !loading && !error && ( @@ -187,7 +194,7 @@ const VehicleBulkMileageUpload = () => { errorMessage={uploadErrorMessage ?? undefined} /> - + ) } diff --git a/libs/service-portal/assets/src/utils/parseFileToMileage.ts b/libs/service-portal/assets/src/utils/parseFileToMileage.ts index 7382ab57e315..1c76e4f19a1b 100644 --- a/libs/service-portal/assets/src/utils/parseFileToMileage.ts +++ b/libs/service-portal/assets/src/utils/parseFileToMileage.ts @@ -7,6 +7,11 @@ export interface MileageRecord { mileage: number } +export interface MileageError { + code: 1 | 2 + message: string +} + const vehicleIndexTitle = [ 'permno', 'vehicleid', @@ -16,10 +21,19 @@ const vehicleIndexTitle = [ ] const mileageIndexTitle = ['kilometrastada', 'mileage', 'odometer'] +export const errorMap: Record = { + 1: `Invalid vehicle column header. Must be one of the following: ${vehicleIndexTitle.join( + ', ', + )}`, + 2: `Invalid mileage column header. Must be one of the following: ${mileageIndexTitle.join( + ', ', + )}`, +} + export const parseFileToMileageRecord = async ( file: File, type: 'csv' | 'xlsx', -): Promise> => { +): Promise | MileageError> => { const parsedLines: Array> = await (type === 'csv' ? parseCsv(file) : parseXlsx(file)) @@ -28,28 +42,28 @@ export const parseFileToMileageRecord = async ( const vehicleIndex = header.findIndex((l) => vehicleIndexTitle.includes(l.toLowerCase()), ) + if (vehicleIndex < 0) { - throw new Error( - `Invalid vehicle column header. Must be one of the following: ${vehicleIndexTitle.join( - ', ', - )}`, - ) + return { + code: 1, + message: errorMap[1], + } } + const mileageIndex = header.findIndex((l) => mileageIndexTitle.includes(l.toLowerCase()), ) if (mileageIndex < 0) { - throw new Error( - `Invalid mileage column header. Must be one of the following: ${mileageIndexTitle.join( - ', ', - )}`, - ) + return { + code: 2, + message: errorMap[2], + } } const uploadedOdometerStatuses: Array = values .map((row) => { - const mileage = Number(row[mileageIndex]) + const mileage = Number(sanitizeNumber(row[mileageIndex])) if (Number.isNaN(mileage)) { return undefined } @@ -76,7 +90,6 @@ const parseCsv = async (file: File) => { accumulatedChunk += decoder.decode(res.value) } } - return parseCsvString(accumulatedChunk) } @@ -89,7 +102,6 @@ const parseXlsx = async (file: File) => { const jsonData = XLSX.utils.sheet_to_csv( parsedFile.Sheets[parsedFile.SheetNames[0]], { - strip: true, blankrows: false, }, ) @@ -105,13 +117,13 @@ const parseCsvString = (chunk: string): Promise => { const records: string[][] = [] const parser = parse({ - cast: true, - skipEmptyLines: true, delimiter: [';', ','], + skipLinesWithEmptyValues: true, + trim: true, }) parser.on('readable', () => { - let record + let record: Array while ((record = parser.read()) !== null) { records.push(record) } @@ -129,3 +141,5 @@ const parseCsvString = (chunk: string): Promise => { parser.end() }) } + +const sanitizeNumber = (n: string) => n.replace(new RegExp(/[.,]/g), '')