feat: calculator working
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)];
|
||||
}
|
||||
}
|
||||
|
||||
65
apps/web/src/lib/calculations.ts
Normal file
65
apps/web/src/lib/calculations.ts
Normal 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
77
biome.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
19
bun.lock
19
bun.lock
@@ -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=="],
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user