From 36a688a0b4d6416cdcfd59e7ad432b88b7f7d283 Mon Sep 17 00:00:00 2001 From: Mathias Oterhals Myklebust Date: Thu, 12 Sep 2024 13:42:56 +0200 Subject: [PATCH 1/3] feat(Compensations): display salary for selected location, degree and examination year --- src/compensations/Compensations.tsx | 41 ++++++++++--- .../salaryCalculator/SalaryCalculator.tsx | 9 ++- src/compensations/utils/calculateSalary.ts | 31 ---------- src/compensations/utils/salary.ts | 57 +++++++++++++++++++ src/compensations/utils/salaryData.ts | 40 ------------- .../salariesInput/utils/parseSalaries.tsx | 10 ++++ studio/lib/payloads/compensations.ts | 8 +++ 7 files changed, 111 insertions(+), 85 deletions(-) delete mode 100644 src/compensations/utils/calculateSalary.ts create mode 100644 src/compensations/utils/salary.ts delete mode 100644 src/compensations/utils/salaryData.ts diff --git a/src/compensations/Compensations.tsx b/src/compensations/Compensations.tsx index 7f2cac51a..a7327d641 100644 --- a/src/compensations/Compensations.tsx +++ b/src/compensations/Compensations.tsx @@ -5,8 +5,14 @@ import { CompensationsPage } from "studio/lib/payloads/compensations"; import SalaryCalculator, { Degree, } from "./components/salaryCalculator/SalaryCalculator"; -import { useState } from "react"; -import { calculatePension, calculateSalary } from "./utils/calculateSalary"; +import { useMemo, useState } from "react"; +import { + calculatePension, + calculateSalary, + maxSalariesExaminationYear, + minSalariesExaminationYear, + salariesFromLocation, +} from "./utils/salary"; import { CompanyLocation } from "studio/lib/payloads/companyDetails"; import { IOption, @@ -36,6 +42,16 @@ const Compensations = ({ compensations, locations }: CompensationsProps) => { selectedDegree: "bachelor", }); + const currentYearSalariesResult = useMemo( + () => + salariesFromLocation( + currentYear, + selectedLocation, + compensations.salaries, + ), + [currentYear, selectedLocation, compensations.salaries], + ); + const updateSelectedDegree = (newDegree: Degree) => { setFormState((prevState) => ({ ...prevState, @@ -52,13 +68,14 @@ const Compensations = ({ compensations, locations }: CompensationsProps) => { const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); - setSalary( - calculateSalary( - currentYear, - formState.examinationYear, - formState.selectedDegree, - ), + if (!currentYearSalariesResult.ok) return; + const salary = calculateSalary( + formState.examinationYear, + formState.selectedDegree, + currentYearSalariesResult.value, ); + if (salary === undefined) return; + setSalary(salary); }; const locationOptions: IOption[] = locations.map((companyLocation) => ({ @@ -85,10 +102,16 @@ const Compensations = ({ compensations, locations }: CompensationsProps) => { selectedId={selectedLocation} onValueChange={(option) => setSelectedLocation(option.id)} /> - {compensations.showSalaryCalculator && ( + {compensations.showSalaryCalculator && currentYearSalariesResult.ok && ( <> void; onExaminationYearChanged: (examinationYear: number) => void; @@ -24,15 +25,13 @@ interface SalaryCalculatorProps { export default function SalaryCalculator({ examinationYearValue: yearValue, + minExaminationYear, + maxExaminationYear, selectedDegree, onDegreeChanged, onExaminationYearChanged, onSubmit, }: SalaryCalculatorProps) { - const currentYear = new Date().getFullYear(); - const minExaminationYear = maxExperience(currentYear); - const maxExaminationYear = currentYear - 1; - return (
{ + const yearlySalaries = salariesByLocation.find( + (s) => s.location._ref === locationId, + )?.yearlySalaries; + if (yearlySalaries === undefined) { + return ResultError(`Could not find salaries for location '${locationId}'`); + } + const salariesData = yearlySalaries.find((s) => s.year === year); + if (salariesData === undefined) { + return ResultError(`Could not find salaries for year ${year}`); + } + const parsedSalaries = JSON.parse(salariesData.salaries); + if (!isSalariesType(parsedSalaries)) { + return ResultError("Parsed salaries data was not valid"); + } + return ResultOk(parsedSalaries); +} diff --git a/src/compensations/utils/salaryData.ts b/src/compensations/utils/salaryData.ts deleted file mode 100644 index 10c359d9a..000000000 --- a/src/compensations/utils/salaryData.ts +++ /dev/null @@ -1,40 +0,0 @@ -export const payscale = { - 2024: { - 2024: 600000, - 2023: 635833, - 2022: 681829, - 2021: 734982, - 2020: 789982, - 2019: 838539, - 2018: 879553, - 2017: 916886, - 2016: 949000, - 2015: 977333, - 2014: 1005324, - 2013: 1031405, - 2012: 1064738, - 2011: 1091489, - 2010: 1113742, - 2009: 1138742, - 2008: 1166667, - 2007: 1192460, - 2006: 1210126, - 2005: 1233560, - 2004: 1264767, - 2003: 1289780, - 2002: 1299680, - 2001: 1295953, - 2000: 1305501, - 1999: 1328501, - 1998: 1349349, - 1997: 1365121, - 1996: 1384832, - 1995: 1399711, - 1994: 1422069, - 1993: 1429358, - 1992: 1452891, - 1991: 1458021, - 1990: 1467321, - 1989: 1484721, - }, -}; diff --git a/studio/components/salariesInput/utils/parseSalaries.tsx b/studio/components/salariesInput/utils/parseSalaries.tsx index f65817bb9..d1ef18c38 100644 --- a/studio/components/salariesInput/utils/parseSalaries.tsx +++ b/studio/components/salariesInput/utils/parseSalaries.tsx @@ -4,6 +4,16 @@ export interface Salaries { [year: string]: number; } +export function isSalariesType(o: unknown): o is Salaries { + return ( + typeof o === "object" && + o !== null && + Object.entries(o).every( + ([k, v]) => !isNaN(Number(k)) && typeof v === "number", + ) + ); +} + const NON_EMPTY_DIGITS_ONLY_REGEX = new RegExp(/^\d+$/); export const VALID_SALARY_REGEX = NON_EMPTY_DIGITS_ONLY_REGEX; diff --git a/studio/lib/payloads/compensations.ts b/studio/lib/payloads/compensations.ts index 4ee5549ef..e2303930d 100644 --- a/studio/lib/payloads/compensations.ts +++ b/studio/lib/payloads/compensations.ts @@ -35,6 +35,13 @@ export interface BonusPage { bonus: number; } +export interface SalariesByLocation { + _key: string; + _type: string; + location: Reference; + yearlySalaries: SalariesPage[]; +} + export interface CompensationsPage { _createdAt: string; _id: string; @@ -47,5 +54,6 @@ export interface CompensationsPage { pensionPercent?: number; benefitsByLocation: BenefitsByLocation[]; bonusesByLocation: BonusesByLocationPage[]; + salaries: SalariesByLocation[]; showSalaryCalculator: boolean; } From 0d11c8e2f8c9c0e176105b3466f644b4c84b49cf Mon Sep 17 00:00:00 2001 From: Mathias Oterhals Myklebust Date: Thu, 12 Sep 2024 13:09:02 +0200 Subject: [PATCH 2/3] refactor(compensations): full arrow function argument names --- src/compensations/utils/salary.ts | 2 +- studio/components/salariesInput/utils/parseSalaries.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compensations/utils/salary.ts b/src/compensations/utils/salary.ts index c8086c97e..b3f957b8e 100644 --- a/src/compensations/utils/salary.ts +++ b/src/compensations/utils/salary.ts @@ -40,7 +40,7 @@ export function salariesFromLocation( salariesByLocation: SalariesByLocation[], ): Result { const yearlySalaries = salariesByLocation.find( - (s) => s.location._ref === locationId, + (salary) => salary.location._ref === locationId, )?.yearlySalaries; if (yearlySalaries === undefined) { return ResultError(`Could not find salaries for location '${locationId}'`); diff --git a/studio/components/salariesInput/utils/parseSalaries.tsx b/studio/components/salariesInput/utils/parseSalaries.tsx index d1ef18c38..244113984 100644 --- a/studio/components/salariesInput/utils/parseSalaries.tsx +++ b/studio/components/salariesInput/utils/parseSalaries.tsx @@ -4,11 +4,11 @@ export interface Salaries { [year: string]: number; } -export function isSalariesType(o: unknown): o is Salaries { +export function isSalariesType(value: unknown): value is Salaries { return ( - typeof o === "object" && - o !== null && - Object.entries(o).every( + typeof value === "object" && + value !== null && + Object.entries(value).every( ([k, v]) => !isNaN(Number(k)) && typeof v === "number", ) ); From 692879e9eb3d9075de443a360c371c40cefbdbf0 Mon Sep 17 00:00:00 2001 From: Mathias Oterhals Myklebust Date: Thu, 12 Sep 2024 13:12:51 +0200 Subject: [PATCH 3/3] docs(salary): document parameters of salariesFromLocation --- src/compensations/utils/salary.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/compensations/utils/salary.ts b/src/compensations/utils/salary.ts index b3f957b8e..92c6d3e9d 100644 --- a/src/compensations/utils/salary.ts +++ b/src/compensations/utils/salary.ts @@ -34,6 +34,20 @@ export function maxSalariesExaminationYear(salaries: Salaries): number { return Math.max(...examinationYearsFromSalaries(salaries)); } +/** + * Retrieves salary data for a specific location and year. + * + * @param year - The year for which to retrieve salary data. + * @param locationId - The string from the `_ref` property of a location reference used in the salaries object. + * @param salariesByLocation - An array of salary data organized by location. + * @returns A Result containing either the Salaries object or an error message. + * + * @remarks + * This function searches for salary data based on the provided location and year. + * The locationId should match the _ref property of a location reference in the salariesByLocation array. + * If the data is found and valid, it returns a ResultOk with the parsed Salaries object. + * If the data is not found or invalid, it returns a ResultError with an appropriate error message. + */ export function salariesFromLocation( year: number, locationId: string,