diff --git a/apps/dafa-web/src/app/shared/services/api/deltagare.service.ts b/apps/dafa-web/src/app/shared/services/api/deltagare.service.ts index bce80f3..c7a42e8 100644 --- a/apps/dafa-web/src/app/shared/services/api/deltagare.service.ts +++ b/apps/dafa-web/src/app/shared/services/api/deltagare.service.ts @@ -17,6 +17,7 @@ import { DriversLicense, mapResponseToDriversLicense } from '@dafa-models/driver import { Education, mapResponseToEducation } from '@dafa-models/education.model'; import { HighestEducation, mapResponseToHighestEducation } from '@dafa-models/highest-education.model'; import { mapResponseToWorkExperience, WorkExperience } from '@dafa-models/work-experience.model'; +import { sortFromToDates } from '@dafa-utils/sort.util'; import { combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -64,11 +65,15 @@ export class DeltagareService { return this.httpClient .get<{ data: EducationsResponse }>(`${this._apiBaseUrl}/education/${id}`, { ...API_HEADERS }) .pipe( - map(response => - response.data.utbildningar - ? response.data.utbildningar.map(utbildning => mapResponseToEducation(utbildning)) - : [] - ) + map(response => { + if (response.data.utbildningar) { + return response.data.utbildningar.sort((a, b) => + sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom }) + ); + } + return []; + }), + map(educations => educations.map(utbildning => mapResponseToEducation(utbildning))) ); } @@ -112,11 +117,15 @@ export class DeltagareService { return this.httpClient .get<{ data: WorkExperiencesResponse }>(`${this._apiBaseUrl}/work/experience/${id}`, { ...API_HEADERS }) .pipe( - map(response => - response.data.arbetslivserfarenheter - ? response.data.arbetslivserfarenheter.map(erfarenhet => mapResponseToWorkExperience(erfarenhet)) - : [] - ) + map(response => { + if (response.data.arbetslivserfarenheter) { + return response.data.arbetslivserfarenheter.sort((a, b) => + sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom }) + ); + } + return []; + }), + map(workExperiences => workExperiences.map(erfarenhet => mapResponseToWorkExperience(erfarenhet))) ); } diff --git a/apps/dafa-web/src/app/shared/utils/sort.util.spec.ts b/apps/dafa-web/src/app/shared/utils/sort.util.spec.ts new file mode 100644 index 0000000..58d65a0 --- /dev/null +++ b/apps/dafa-web/src/app/shared/utils/sort.util.spec.ts @@ -0,0 +1,54 @@ +import { sortFromToDates } from './sort.util'; + +const A_LATEST = { + a: { from: '20210101', to: '20210801' }, + b: { from: '20200101', to: '20200801' }, +}; +const A_LATEST_DAY_MISSING = { + a: { from: '202101', to: '202108' }, + b: { from: '202001', to: '202008' }, +}; +const A_LATEST_DATE = { + a: { from: new Date('2021-01-01'), to: new Date('2021-08-01') }, + b: { from: new Date('2020-01-01'), to: new Date('2020-08-01') }, +}; +const B_LATEST = { + a: { from: '20200101', to: '20200801' }, + b: { from: '20210101', to: '20210801' }, +}; +const A_B_EQUAL = { + a: { from: '20210101', to: '20210801' }, + b: { from: '20210101', to: '20210801' }, +}; +const MISSING_FROM_DATE = { + a: { from: undefined, to: '20210801' }, + b: { from: '20200101', to: '20200801' }, +}; + +describe('SortUtil', () => { + describe('Sort complete from/to date-strings from latest to oldest', () => { + it('should sort A dates before B dates when A dates are later', () => { + expect(sortFromToDates(A_LATEST.a, A_LATEST.b)).toBe(-1); + }); + + it('should sort A dates before B dates when DAY is missing and A dates are later', () => { + expect(sortFromToDates(A_LATEST_DAY_MISSING.a, A_LATEST_DAY_MISSING.b)).toBe(-1); + }); + + it('should sort A dates before B dates when provided JS Date and A dates are later', () => { + expect(sortFromToDates(A_LATEST_DATE.a, A_LATEST_DATE.b)).toBe(-1); + }); + + it('should sort B from dates before A dates when B dates are later', () => { + expect(sortFromToDates(B_LATEST.a, B_LATEST.b)).toBe(1); + }); + + it('should sort A dates before B dates when dates are equal', () => { + expect(sortFromToDates(A_B_EQUAL.a, A_B_EQUAL.b)).toBe(0); + }); + + it('should sort A dates before B dates when some from-date is missing', () => { + expect(sortFromToDates(MISSING_FROM_DATE.a, MISSING_FROM_DATE.b)).toBe(-1); + }); + }); +}); diff --git a/apps/dafa-web/src/app/shared/utils/sort.util.ts b/apps/dafa-web/src/app/shared/utils/sort.util.ts index fe5d223..43a090e 100644 --- a/apps/dafa-web/src/app/shared/utils/sort.util.ts +++ b/apps/dafa-web/src/app/shared/utils/sort.util.ts @@ -1,4 +1,10 @@ import { Sort } from '@dafa-models/sort.model'; +const CURRENT_YEAR = new Date().getFullYear(); + +interface FromToDates { + from: string | Date; + to: string | Date; +} export function sort(data: T[], sort: Sort): T[] { const reverse = sort.order === 'desc' ? -1 : 1; @@ -9,3 +15,55 @@ export function sort(data: T[], sort: Sort): T[] { return reverse * (+(first > second) - +(second > first)); }); } + +export function sortFromToDates(a: FromToDates, b: FromToDates): number { + if (!a.from || !b.from) { + console.error('Some date is not set: ', { a, b }); + return -1; + } + + if (a.from instanceof Date) { + a.from = a.from.toISOString(); + } + if (a.to instanceof Date) { + a.to = a.to.toISOString(); + } + if (b.from instanceof Date) { + b.from = b.from.toISOString(); + } + if (b.to instanceof Date) { + b.to = b.to.toISOString(); + } + + // Remove optional time and '-' characters whenever the ISO date string is given. + a.from = a.from.substring(0, 10).replaceAll('-', ''); + b.from = b.from.substring(0, 10).replaceAll('-', ''); + a.to = a.to.substring(0, 10).replaceAll('-', ''); + b.to = b.to.substring(0, 10).replaceAll('-', ''); + + // If no complete dates are available we will add as high numbers as possible to make them sort on top. + // If no tillValue is available it means it is still ongoing and should sort on top + const aFrom = +a.from.padEnd(8, '9'); + const bFrom = +b.from.padEnd(8, '9'); + const aTo = !a.to ? +`${CURRENT_YEAR + 1}9999` : +a.to.padEnd(8, '9'); + const bTo = !b.to ? +`${CURRENT_YEAR + 1}9999` : +b.to.padEnd(8, '9'); + + if (isNaN(aTo)) { + return 1; + } + if (isNaN(bTo)) { + return -1; + } + + if (aTo === bTo) { + if (isNaN(aFrom)) { + return 1; + } + if (isNaN(bFrom)) { + return -1; + } + return aFrom === bFrom ? 0 : bFrom < aFrom ? -1 : 1; + } + + return bTo < aTo ? -1 : 1; +} diff --git a/tsconfig.base.json b/tsconfig.base.json index b68976a..ee338d2 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -10,7 +10,7 @@ "importHelpers": true, "target": "es2015", "module": "esnext", - "lib": ["es2017", "dom"], + "lib": ["esnext", "dom"], "skipLibCheck": true, "skipDefaultLibCheck": true, "baseUrl": ".",