Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/salary and benefits calculator #506

Merged
merged 10 commits into from
Aug 26, 2024
8 changes: 7 additions & 1 deletion src/components/forms/inputField/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ interface InputFieldProps {
autoComplete?: HTMLInputAutoCompleteAttribute;
autoCorrect?: string;
type?: HTMLInputTypeAttribute;
max?: number;
min?: number;
spellCheck?: "true" | "false";
autoCapitalize?: string;
value: string;
value: string | number;
onChange: (name: string, value: string) => void;
required?: boolean;
}
Expand All @@ -24,6 +26,8 @@ const InputField = ({
autoComplete,
autoCorrect = "off",
type = "text",
max,
min,
spellCheck,
autoCapitalize,
value,
Expand Down Expand Up @@ -56,6 +60,8 @@ const InputField = ({
autoComplete={autoComplete}
autoCorrect={autoCorrect}
type={type}
max={max}
min={min}
className={styles.input}
spellCheck={spellCheck}
value={value}
anemne marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
14 changes: 7 additions & 7 deletions src/components/forms/radioButtonGroup/RadioButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import styles from "src/components/forms/radioButtonGroup/radioButtonGroup.modul
import { RadioButton } from "./components/RadioButton";
import textStyles from "src/components/text/text.module.css";

interface IOption {
export interface IOption {
id: string;
label: string;
disabled: boolean;
currentChecked: boolean;
disabled?: boolean;
currentSelected: boolean;
}

interface RenderOptionsProps {
Expand Down Expand Up @@ -41,8 +41,8 @@ interface RadioButtonGroupProps {
*
* ```
* const options = [
* { id: 'radio1', label: 'Option 1', value: '1', currentChecked: false },
* { id: 'radio2', label: 'Option 2', value: '2', currentChecked: true },
* { id: 'radio1', label: 'Option 1', value: '1', currentSelected: false },
* { id: 'radio2', label: 'Option 2', value: '2', currentSelected: true },
* ];
*
* <RadioButtonGroup
Expand Down Expand Up @@ -82,15 +82,15 @@ export const RadioButtonGroup = ({
const RenderOptions = ({ options, onChange }: RenderOptionsProps) => {
return (
<>
{options.map(({ id, label, disabled, currentChecked }) => (
{options.map(({ id, label, disabled, currentSelected }) => (
<RadioButton
key={id}
id={id}
label={label}
name="radio"
disabled={disabled}
value={label}
defaultChecked={currentChecked}
defaultChecked={currentSelected}
onChange={onChange}
/>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.fieldset {
border: 0 none;
padding: 0;
}

.wrapper {
Expand Down
70 changes: 70 additions & 0 deletions src/salaryAndBenefits/SalaryAndBenefits.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,86 @@
"use client";

import styles from "./salaryAndBenefits.module.css";
import Text from "src/components/text/Text";
import { SalaryAndBenefitsPage } from "studio/lib/payloads/salaryAndBenefits";
import { RichText } from "src/components/richText/RichText";
import SalaryCalculator, {
Degree,
} from "./components/salaryCalculator/SalaryCalculator";
import { useState } from "react";
import {
calculatePension,
calculateSalary,
maxExperience,
} from "./utils/calculateSalary";

interface SalaryAndBenefitsProps {
salaryAndBenefits: SalaryAndBenefitsPage;
}

interface SalaryCalculatorFormState {
examinationYear: number;
selectedDegree: Degree;
}

const SalaryAndBenefits = ({ salaryAndBenefits }: SalaryAndBenefitsProps) => {
const currentYear = new Date().getFullYear();
const minExaminationYear = maxExperience(currentYear);
const maxExaminationYear = currentYear - 1;
anemne marked this conversation as resolved.
Show resolved Hide resolved

const [formState, setFormState] = useState<SalaryCalculatorFormState>({
examinationYear: currentYear - 1,
selectedDegree: "bachelor",
});
const [salary, setSalary] = useState<number | null>(null);

const updateSelectedDegree = (newDegree: Degree) => {
setFormState((prevState) => ({
...prevState,
selectedDegree: newDegree,
}));
};

const updateExaminationYear = (newYear: number) => {
setFormState((prevState) => ({
...prevState,
examinationYear: newYear,
}));
};

const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
setSalary(
calculateSalary(
currentYear,
formState.examinationYear,
formState.selectedDegree,
),
);
};

return (
<div className={styles.wrapper}>
<Text type="h1">{salaryAndBenefits.basicTitle}</Text>
{salaryAndBenefits.showSalaryCalculator && (
<SalaryCalculator
// TODO: should also take in degree state (this requires changes to IOption of RadioButtonGroup)
examinationYear={formState.examinationYear}
minExaminationYear={minExaminationYear}
maxExaminationYear={maxExaminationYear}
onDegreeChanged={updateSelectedDegree}
onExaminationYearChanged={updateExaminationYear}
onSubmit={handleSubmit}
/>
)}
{salary !== null ? (
anemne marked this conversation as resolved.
Show resolved Hide resolved
<div aria-live="polite">
<Text> Du vil få en årlig lønn på {salary}</Text>
anemne marked this conversation as resolved.
Show resolved Hide resolved
<Text>
Du vil få en årlig pensjon på omtrent {calculatePension(salary)}
</Text>
</div>
) : null}
<div className={styles.benefits}>
{salaryAndBenefits.benefits.map((benefit) => (
<div key={benefit._key} className={styles.benefitWrapper}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import styles from "./salaryCalculator.module.css";
import InputField from "src/components/forms/inputField/InputField";
import {
IOption,
RadioButtonGroup,
} from "src/components/forms/radioButtonGroup/RadioButtonGroup";
import Button from "src/components/buttons/Button";

export type Degree = "bachelor" | "master";

const degreeOptions: IOption[] = [
{
id: "bachelor",
label: "Bachelor",
currentSelected: true,
disabled: false,
anemne marked this conversation as resolved.
Show resolved Hide resolved
},
{ id: "master", label: "Master", currentSelected: false, disabled: false },
];

interface SalaryCalculatorProps {
examinationYear: number;
minExaminationYear: number;
maxExaminationYear: number;
onDegreeChanged: (degree: Degree) => void;
onExaminationYearChanged: (examinationYear: number) => void;
onSubmit: (event: React.FormEvent) => void;
}

export default function SalaryCalculator({
examinationYear,
minExaminationYear,
maxExaminationYear,
onDegreeChanged,
onExaminationYearChanged,
onSubmit,
}: SalaryCalculatorProps) {
return (
<form
aria-label="salary calculator"
anemne marked this conversation as resolved.
Show resolved Hide resolved
className={styles.calculator}
onSubmit={onSubmit}
>
<RadioButtonGroup
id="degree-group"
label="Choose your degree"
options={degreeOptions}
onValueChange={(value) =>
(value.id === "bachelor" || value.id === "master") &&
onDegreeChanged(value.id)
}
/>
<InputField
label="year"
name="examinationYear"
type="number"
max={maxExaminationYear}
min={minExaminationYear}
value={examinationYear}
onChange={(_name, value) => onExaminationYearChanged(parseInt(value))}
required
/>
<Button type="secondary" size="small">
Submit
</Button>
</form>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.calculator {
display: flex;
flex-direction: column;
gap: 2rem;
}
28 changes: 28 additions & 0 deletions src/salaryAndBenefits/utils/calculateSalary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { payscale } from "./salaryData";

interface PayScale {
[year: number]: {
[examinationYear: number]: number;
};
}

const salaryPayscale: PayScale = payscale;

export function calculateSalary(
currentYear: number,
examinationYear: number,
degree: string,
): number {
const degreeValue = degree === "bachelor" ? 1 : 0;
const adjustedYear = examinationYear + degreeValue;
return salaryPayscale[currentYear][adjustedYear];
}

export function calculatePension(salary: number): number {
return Math.round(salary * 0.07);
}

export function maxExperience(thisYear: number): number {
const years = Object.keys(salaryPayscale[thisYear]).map(Number);
return Math.min(...years);
}
40 changes: 40 additions & 0 deletions src/salaryAndBenefits/utils/salaryData.ts
anemne marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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,
},
};
1 change: 1 addition & 0 deletions studio/lib/payloads/salaryAndBenefits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export interface SalaryAndBenefitsPage {
page: string;
slug: Slug;
benefits: Benefit[];
showSalaryCalculator: boolean;
}