feat: calculator working

This commit is contained in:
2026-01-01 20:56:32 -06:00
parent a4a4495207
commit bcbee26eaa
6 changed files with 717 additions and 157 deletions

View File

@@ -1,55 +1,445 @@
import { useState } from "react"
import { Button } from "./ui/button"
import { useEffect, useState } from "react";
import { Button } from "./ui/button";
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
} from "./ui/field";
import { Input } from "./ui/input";
import { Checkbox } from "./ui/checkbox";
import { Textarea } from "./ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "./ui/select";
import { Slider } from "./ui/slider";
import { calculateTotalCompensation } from "@/lib/calculations";
import { Plus } from "lucide-react";
interface CalculationData {
base_monthly_salary: number, // Pago mensual
days_of_aguinaldo: number, // Dias de aguinaldo
pto_rate: number, // Prima vacacional
days_of_pto: number, // Dias de vacaiones
savings_fund?: number,
food_vouchers?: number,
additional_bonus? : {
name: string,
payout: number,
}[]
export interface CalculationData {
base_monthly_salary?: number; // Pago mensual
days_of_christmas_bonus?: number; // Dias de aguinaldo
leave_rate?: number; // Prima vacacional
days_of_leave?: number; // Dias de vacaiones
savings_fund?: {
percentage?: number;
is_symmetrical?: boolean;
};
food_vouchers?: number;
days_of_additional_pto?: number;
additional_bonus?: {
name: string;
payout: number;
type: "one_time" | "monthly";
}[];
}
export default function CompensationCalculator(){
const [calcData, setCalcData] = useState<CalculationData>()
export interface AboveLawBenefitsFlagMap {
savings_fund: boolean;
food_vouchers: boolean;
additional_pto: boolean;
}
return <>
export default function CompensationCalculator() {
const [calcData, setCalcData] = useState<CalculationData>();
const [aboveLawBenefitEnabled, setAboveLawBenefitEnabled] =
useState<AboveLawBenefitsFlagMap>({
savings_fund: false,
food_vouchers: false,
additional_pto: false,
});
const [totalCompensation, setTotalCompensation] = useState(0);
const [additionalBonuses, setAdditionalBonuses] = useState<
CalculationData["additional_bonus"]
>([]);
<form onSubmit={(e) => e.preventDefault()}>
<div>
<label htmlFor="base_monthly_salary">Pago mensual</label>
<input type="number" id="base_monthly_salary" />
</div>
<div>
<label htmlFor="days_of_aguinaldo">Dias de aguinaldo</label>
<input type="number" id="days_of_aguinaldo" />
</div>
<div>
<label htmlFor="pto_rate">Prima vacacional</label>
<input type="number" id="pto_rate" />
</div>
<div>
<label htmlFor="days_of_pto">Dias de vacaciones</label>
<input type="number" id="days_of_pto" />
</div>
<div>
<label htmlFor="savings_fund">Fondo de ahorro</label>
<input type="number" id="savings_fund" />
</div>
<div>
<label htmlFor="food_vouchers">Vales de comida</label>
<input type="number" id="food_vouchers" />
</div>
<div>
<label htmlFor="additional_bonus">Bonus adicional</label>
<input type="number" id="additional_bonus" />
</div>
{/* <button type="submit">Calcular</button> */}
<Button type="submit">Calcular</Button>
</form>
</>
}
useEffect(() => {
if (calcData) {
setTotalCompensation(
calculateTotalCompensation(calcData, aboveLawBenefitEnabled),
);
}
}, [calcData, aboveLawBenefitEnabled]);
return (
<main className="px-page-width py-10">
<h1>Compensation Calculator</h1>
<p className="text-4xl font-bold mt-2 mb-10">
Total Yearly Compensation:{" "}
{new Intl.NumberFormat("es-MX", {
style: "decimal",
}).format(totalCompensation)}{" "}
MXN
</p>
<aside className="absolute top-0 left-0 w-full h-full bg-black opacity-50 z-[-1]">
<pre className="text-red-500">{JSON.stringify(calcData, null, 2)}</pre>
<pre className="text-blue-500">
{JSON.stringify(aboveLawBenefitEnabled, null, 2)}
</pre>
</aside>
<form onSubmit={(e) => e.preventDefault()} className="mt-10">
<FieldGroup>
<FieldSet>
<FieldLegend>Income Information</FieldLegend>
<FieldDescription>Salary and Benefits by law</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="checkout-7j9-card-name-43j">
Base Monthly Salary
</FieldLabel>
<Input
id="checkout-7j9-card-name-43j"
placeholder="80000"
required
value={calcData?.base_monthly_salary}
onChange={(e) =>
setCalcData({
...calcData,
base_monthly_salary: Number(e.target.value),
})
}
/>
</Field>
<div className="grid grid-cols-3 gap-4">
<Field>
<FieldLabel htmlFor="checkout-7j9-card-number-uw1">
Days of Christmas Bonus
</FieldLabel>
<Input
id="checkout-7j9-card-number-uw1"
placeholder="12"
value={calcData?.days_of_christmas_bonus}
onChange={(e) =>
setCalcData({
...calcData,
days_of_christmas_bonus: Number(e.target.value),
})
}
/>
</Field>
<Field>
<FieldLabel htmlFor="checkout-7j9-card-number-uw1">
Days of Paid Leave
</FieldLabel>
<Input
id="checkout-7j9-card-number-uw1"
placeholder="12"
value={calcData?.days_of_leave}
onChange={(e) =>
setCalcData({
...calcData,
days_of_leave: Number(e.target.value),
})
}
/>
</Field>
<Field>
<FieldLabel htmlFor="checkout-exp-month-ts6">
Rate of Paid Leave (0% - 100%)
</FieldLabel>
<Slider
max={100}
min={0}
step={10}
className="mt-2 w-full"
aria-label="Price Range"
value={[calcData?.leave_rate ?? 50]}
onValueChange={(values) =>
setCalcData({
...calcData,
leave_rate: Number(values[0]),
})
}
/>
<FieldDescription>
{calcData?.leave_rate ?? 50}%
</FieldDescription>
</Field>
</div>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<FieldSet>
<FieldLegend>Above Law Benefits</FieldLegend>
<FieldDescription>
Common additional benefits given to employees
</FieldDescription>
<FieldGroup>
<Field orientation="horizontal">
<Checkbox
id="checkout-7j9-same-as-shipping-wgm"
checked={aboveLawBenefitEnabled.savings_fund}
onCheckedChange={(checked) =>
setAboveLawBenefitEnabled({
...aboveLawBenefitEnabled,
savings_fund: checked,
})
}
/>
<FieldLabel
htmlFor="checkout-7j9-same-as-shipping-wgm"
className="font-normal"
>
Savings Funds
</FieldLabel>
<FieldDescription>
Do you receive savings funds?
</FieldDescription>
</Field>
{aboveLawBenefitEnabled.savings_fund && (
<>
<FieldGroup className="grid grid-cols-[1fr_350px] gap-4">
<Field>
<FieldLabel htmlFor="checkout-7j9-same-as-shipping-wgm">
Percentage (0 - 50%) of Salary Saved
</FieldLabel>
<Slider
max={50}
min={0}
step={1}
className="mt-2 w-full"
aria-label="Price Range"
value={[calcData?.savings_fund?.percentage ?? 7]}
onValueChange={(values) =>
setCalcData({
...calcData,
savings_fund: {
...calcData?.savings_fund,
percentage: Number(values[0]),
},
})
}
/>
<FieldDescription>
{calcData?.savings_fund?.percentage ?? 7}%
</FieldDescription>
</Field>
<Field orientation="horizontal">
<Checkbox
id="checkout-7j9-same-as-shipping-wgm"
checked={calcData?.savings_fund?.is_symmetrical}
onCheckedChange={(checked) =>
setCalcData({
...calcData,
savings_fund: {
...calcData?.savings_fund,
is_symmetrical: checked,
},
})
}
/>
<FieldLabel
htmlFor="checkout-7j9-same-as-shipping-wgm"
className="font-normal"
>
Is this savings symmetrical?
</FieldLabel>
<FieldDescription>
Does your boss also pay the same percentage to your
savings fund?
</FieldDescription>
</Field>
</FieldGroup>
<FieldSeparator />
</>
)}
<Field orientation="horizontal">
<Checkbox
id="checkout-7j9-same-as-shipping-wgm"
checked={aboveLawBenefitEnabled.food_vouchers}
onCheckedChange={(checked) =>
setAboveLawBenefitEnabled({
...aboveLawBenefitEnabled,
food_vouchers: checked,
})
}
/>
<FieldLabel
htmlFor="checkout-7j9-same-as-shipping-wgm"
className="font-normal"
>
Food Vouchers
</FieldLabel>
<FieldDescription>
Do you receive food vouchers?
</FieldDescription>
</Field>
{aboveLawBenefitEnabled.food_vouchers && (
<FieldGroup>
<Field>
<FieldLabel htmlFor="checkout-7j9-same-as-shipping-wgm">
Monthly Amount of Food Vouchers
</FieldLabel>
<Input
id="checkout-7j9-same-as-shipping-wgm"
placeholder="1000"
required
value={calcData?.food_vouchers}
onChange={(e) =>
setCalcData({
...calcData,
food_vouchers: Number(e.target.value),
})
}
/>
</Field>
</FieldGroup>
)}
<Field orientation="horizontal">
<Checkbox
id="checkout-7j9-same-as-shipping-wgm"
checked={aboveLawBenefitEnabled.additional_pto}
onCheckedChange={(checked) =>
setAboveLawBenefitEnabled({
...aboveLawBenefitEnabled,
additional_pto: checked,
})
}
/>
<FieldLabel
htmlFor="checkout-7j9-same-as-shipping-wgm"
className="font-normal"
>
Additional PTO
</FieldLabel>
<FieldDescription>
Do you receive additional PTO or floating days?
</FieldDescription>
</Field>
</FieldGroup>
{aboveLawBenefitEnabled.additional_pto && (
<FieldGroup>
<Field>
<FieldLabel htmlFor="checkout-7j9-same-as-shipping-wgm">
Days of Additional PTO
</FieldLabel>
<Input
id="checkout-7j9-same-as-shipping-wgm"
placeholder="4"
required
value={calcData?.days_of_additional_pto}
onChange={(e) =>
setCalcData({
...calcData,
days_of_additional_pto: Number(e.target.value),
})
}
/>
</Field>
</FieldGroup>
)}
</FieldSet>
<FieldSeparator />
<FieldSet>
<FieldLegend>Additional Bonuses</FieldLegend>
<FieldDescription>Any additional bonuses</FieldDescription>
{additionalBonuses.map((bonus, index) => (
<FieldGroup key={index} className="grid grid-columns-3 gap-4">
<Field>
<FieldLabel htmlFor="checkout-7j9-same-as-shipping-wgm">
Bonus Name
</FieldLabel>
<Input
id="checkout-7j9-same-as-shipping-wgm"
placeholder="Bonus Name"
required
value={bonus.name}
onChange={(e) =>
setAdditionalBonuses(
additionalBonuses.map((bonus, i) =>
i === index
? { ...bonus, name: e.target.value }
: bonus,
),
)
}
/>
</Field>
<Field>
<FieldLabel htmlFor="checkout-7j9-same-as-shipping-wgm">
Bonus Payout
</FieldLabel>
<Input
id="checkout-7j9-same-as-shipping-wgm"
placeholder="Bonus Payout"
required
value={bonus.payout}
onChange={(e) =>
setAdditionalBonuses(
additionalBonuses.map((bonus, i) =>
i === index
? { ...bonus, payout: Number(e.target.value) }
: bonus,
),
)
}
/>
</Field>
<Field>
<FieldLabel htmlFor="checkout-7j9-same-as-shipping-wgm">
Bonus Type
</FieldLabel>
<Select
id="checkout-7j9-same-as-shipping-wgm"
value={bonus.type}
onValueChange={(value) =>
setAdditionalBonuses(
additionalBonuses.map((bonus, i) =>
i === index
? {
...bonus,
type: value as "one_time" | "monthly",
}
: bonus,
),
)
}
>
<SelectTrigger>
<SelectValue placeholder="Select a bonus type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="one_time">One Time</SelectItem>
<SelectItem value="monthly">Monthly</SelectItem>
</SelectContent>
</Select>
</Field>
</FieldGroup>
))}
<Button
variant="outline"
type="button"
onClick={() =>
setAdditionalBonuses([
...additionalBonuses,
{ name: "", payout: 0, type: "one_time" },
])
}
>
<Plus />
Add Bonus
</Button>
</FieldSet>
</FieldGroup>
</form>
</main>
);
}

View File

@@ -6,124 +6,131 @@
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.42 0.18 266);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.278 0.033 256.848);
--muted-foreground: oklch(0.707 0.022 261.325);
--accent: oklch(0.278 0.033 256.848);
--accent-foreground: oklch(0.985 0.002 247.839);
--destructive: oklch(0.58 0.22 27);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--background: oklch(1 0 0);
--foreground: oklch(0.13 0.028 261.692);
--card: oklch(1 0 0);
--card-foreground: oklch(0.13 0.028 261.692);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.13 0.028 261.692);
--primary: oklch(0.488 0.243 264.376);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.003 264.542);
--muted-foreground: oklch(0.551 0.027 264.364);
--accent: oklch(0.967 0.003 264.542);
--accent-foreground: oklch(0.21 0.034 264.665);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.928 0.006 264.531);
--input: oklch(0.928 0.006 264.531);
--ring: oklch(0.707 0.022 261.325);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--radius: 0.875rem;
--sidebar: oklch(0.985 0.002 247.839);
--sidebar-foreground: oklch(0.13 0.028 261.692);
--sidebar-primary: oklch(0.546 0.245 262.881);
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
--sidebar-accent: oklch(0.967 0.003 264.542);
--sidebar-accent-foreground: oklch(0.21 0.034 264.665);
--sidebar-border: oklch(0.928 0.006 264.531);
--sidebar-ring: oklch(0.707 0.022 261.325);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.87 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.371 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
--background: oklch(0.13 0.028 261.692);
--foreground: oklch(0.985 0.002 247.839);
--card: oklch(0.21 0.034 264.665);
--card-foreground: oklch(0.985 0.002 247.839);
--popover: oklch(0.21 0.034 264.665);
--popover-foreground: oklch(0.985 0.002 247.839);
--primary: oklch(0.42 0.18 266);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.278 0.033 256.848);
--muted-foreground: oklch(0.707 0.022 261.325);
--accent: oklch(0.278 0.033 256.848);
--accent-foreground: oklch(0.985 0.002 247.839);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--sidebar: oklch(0.21 0.034 264.665);
--sidebar-foreground: oklch(0.985 0.002 247.839);
--sidebar-primary: oklch(0.623 0.214 259.815);
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
--sidebar-accent: oklch(0.278 0.033 256.848);
--sidebar-accent-foreground: oklch(0.985 0.002 247.839);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.551 0.027 264.364);
}
@theme inline {
--font-sans: "Poppins", sans-serif;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
--font-sans: 'Noto Sans Variable', sans-serif;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
--page-width: 900px;
}
@layer base {
* {
@apply border-border outline-ring/50;
}
}
body {
@apply font-sans bg-background text-foreground;
}
}
html {
@apply font-sans;
}
}
}
@layer utilities {
.px-page-width {
@apply px-[calc((100vw-900px)/2)];
}
}

View File

@@ -0,0 +1,65 @@
import type {
AboveLawBenefitsFlagMap,
CalculationData,
} from "@/components/compensation-calculator";
function calcISR(amount: number) {
return amount * 0.1;
}
function calcIMSS(amount: number) {
return amount * 0.04;
}
export function calculateTotalCompensation(
{
base_monthly_salary,
additional_bonus,
days_of_christmas_bonus,
days_of_leave,
food_vouchers,
leave_rate,
savings_fund,
days_of_additional_pto,
}: CalculationData,
benefitsEnabled: AboveLawBenefitsFlagMap,
) {
// return calcData.base_monthly_salary * 12;
let total = 0;
if (base_monthly_salary) {
total += base_monthly_salary * 12;
const daily_salary = base_monthly_salary / 30;
if (days_of_christmas_bonus) {
total += days_of_christmas_bonus * daily_salary;
}
if (days_of_leave) {
total +=
days_of_leave * daily_salary * (leave_rate ? leave_rate / 100 : 0.5);
}
if (
benefitsEnabled.savings_fund &&
savings_fund &&
savings_fund.is_symmetrical
) {
total +=
base_monthly_salary *
(savings_fund.percentage ? savings_fund.percentage / 100 : 0.07) *
12;
}
if (benefitsEnabled.additional_pto && days_of_additional_pto) {
total += days_of_additional_pto * daily_salary;
}
}
if (benefitsEnabled.food_vouchers && food_vouchers) {
total += food_vouchers * 12;
}
return total;
}

77
biome.json Normal file
View File

@@ -0,0 +1,77 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"includes": [
"**",
"!**/.next",
"!**/dist",
"!**/.turbo",
"!**/dev-dist",
"!**/.zed",
"!**/.vscode",
"!**/routeTree.gen.ts",
"!**/src-tauri",
"!**/.nuxt",
"!bts.jsonc",
"!**/.expo",
"!**/.wrangler",
"!**/.alchemy",
"!**/.svelte-kit",
"!**/wrangler.jsonc",
"!**/old_project",
"!old_project",
"!**/convex/_generated"
]
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"useExhaustiveDependencies": "info"
},
"nursery": {
"useSortedClasses": {
"level": "warn",
"fix": "safe",
"options": {
"functions": ["clsx", "cva", "cn"]
}
}
},
"style": {
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"css": {
"parser": {
"tailwindDirectives": true
}
}
}

View File

@@ -9,6 +9,7 @@
"zod": "catalog:",
},
"devDependencies": {
"@biomejs/biome": "^2.3.10",
"@mx-comp-calc/config": "workspace:*",
"turbo": "^2.6.3",
"typescript": "catalog:",
@@ -150,6 +151,24 @@
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@biomejs/biome": ["@biomejs/biome@2.3.10", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.10", "@biomejs/cli-darwin-x64": "2.3.10", "@biomejs/cli-linux-arm64": "2.3.10", "@biomejs/cli-linux-arm64-musl": "2.3.10", "@biomejs/cli-linux-x64": "2.3.10", "@biomejs/cli-linux-x64-musl": "2.3.10", "@biomejs/cli-win32-arm64": "2.3.10", "@biomejs/cli-win32-x64": "2.3.10" }, "bin": { "biome": "bin/biome" } }, "sha512-/uWSUd1MHX2fjqNLHNL6zLYWBbrJeG412/8H7ESuK8ewoRoMPUgHDebqKrPTx/5n6f17Xzqc9hdg3MEqA5hXnQ=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M6xUjtCVnNGFfK7HMNKa593nb7fwNm43fq1Mt71kpLpb+4mE7odO8W/oWVDyBVO4ackhresy1ZYO7OJcVo/B7w=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vae7+V6t/Avr8tVbFNjnFSTKZogZHFYl7MMH62P/J1kZtr0tyRQ9Fe0onjqjS2Ek9lmNLmZc/VR5uSekh+p1fg=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-hhPw2V3/EpHKsileVOFynuWiKRgFEV48cLe0eA+G2wO4SzlwEhLEB9LhlSrVeu2mtSn205W283LkX7Fh48CaxA=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-B9DszIHkuKtOH2IFeeVkQmSMVUjss9KtHaNXquYYWCjH8IstNgXgx5B0aSBQNr6mn4RcKKRQZXn9Zu1rM3O0/A=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-wwAkWD1MR95u+J4LkWP74/vGz+tRrIQvr8kfMMJY8KOQ8+HMVleREOcPYsQX82S7uueco60L58Wc6M1I9WA9Dw=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QTfHZQh62SDFdYc2nfmZFuTm5yYb4eO1zwfB+90YxUumRCR171tS1GoTX5OD0wrv4UsziMPmrePMtkTnNyYG3g=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-o7lYc9n+CfRbHvkjPhm8s9FgbKdYZu5HCcGVMItLjz93EhgJ8AM44W+QckDqLA9MKDNFrR8nPbO4b73VC5kGGQ=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.10", "", { "os": "win32", "cpu": "x64" }, "sha512-pHEFgq7dUEsKnqG9mx9bXihxGI49X+ar+UBrEIj3Wqj3UCZp1rNgV+OoyjFgcXsjCWpuEAF4VJdkZr3TrWdCbQ=="],
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.51.4", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.2", "which": "^4.0.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-AoziS8lRQ3ew/lY5J4JSlzYSN9Fo0oiyMBY37L3Bwq4mOQJT5GSrdZYLFPt6pH1LApDI3ZJceNyx+rHRACZSeQ=="],

View File

@@ -14,6 +14,7 @@
},
"type": "module",
"scripts": {
"check": "biome check --write .",
"dev": "turbo dev",
"build": "turbo build",
"check-types": "turbo check-types",
@@ -26,9 +27,10 @@
"@mx-comp-calc/env": "workspace:*"
},
"devDependencies": {
"@biomejs/biome": "^2.3.10",
"@mx-comp-calc/config": "workspace:*",
"turbo": "^2.6.3",
"typescript": "catalog:",
"@mx-comp-calc/config": "workspace:*"
"typescript": "catalog:"
},
"packageManager": "bun@1.2.22"
}