Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

.idea
# dependencies
/node_modules
/.pnp
Expand Down
156 changes: 100 additions & 56 deletions app/HomePageContent.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,109 @@
'use client';

import { useSnapshot } from 'valtio';
import { IncomeDetailsCard } from '~/components/IncomeDetailsCard';
import { InputCard } from '~/components/InputCard';
import { SettingsInfoCard } from '~/components/SettingsInfoCard';
import { TaxationDetailsCard } from '~/components/TaxationDetailsCard';
import { state } from '~/lib/state';
import { useTaxesCalculator } from '~/lib/taxes';
import {useSnapshot} from 'valtio';
import {IncomeDetailsCard} from '~/components/IncomeDetailsCard';
import {InputCard} from '~/components/InputCard';
import {SettingsInfoCard} from '~/components/SettingsInfoCard';
import {TaxationDetailsCard} from '~/components/TaxationDetailsCard';
import {state} from '~/lib/state';
import {useTaxesCalculator} from '~/lib/taxes';
import {Card, Text} from "@mantine/core";

export default function HomePageContent() {
const snap = useSnapshot(state);
const snap = useSnapshot(state);

const {
grossIncomeInBaseCurrency,
totalTaxAmountInBaseCurrency,
totalTaxPercentage,
pensionTaxAmountInBaseCurrency,
healthTaxAmountInBaseCurrency,
incomeTaxAmountInBaseCurrency,
netIncome,
totalNetIncomeInBaseCurrency,
exchangeRates,
exchangeRatesLoading,
} = useTaxesCalculator(snap);
const pfa = useTaxesCalculator({...snap, type: 'pfa'});

const accentColor = totalTaxPercentage
? totalTaxPercentage > 100
? 'red'
: totalTaxPercentage > 50
? 'orange'
: 'blue'
: 'blue';
const srlVenit = useTaxesCalculator({...snap, type: 'srl-venit'});
const srlProfit = useTaxesCalculator({...snap, type: 'srl-profit'});

const grossIncomeOverVATThreshold =
grossIncomeInBaseCurrency !== undefined && grossIncomeInBaseCurrency > snap.vatThreshold;
const accentColor = pfa.totalTaxPercentage
? pfa.totalTaxPercentage > 100
? 'red'
: pfa.totalTaxPercentage > 50
? 'orange'
: 'blue'
: 'blue';

return (
<>
<InputCard grossIncomeOverVATThreshold={grossIncomeOverVATThreshold} />
<TaxationDetailsCard
accentColor={accentColor}
totalTaxAmountInBaseCurrency={totalTaxAmountInBaseCurrency}
totalTaxPercentage={totalTaxPercentage}
healthTaxAmountInBaseCurrency={healthTaxAmountInBaseCurrency}
pensionTaxAmountInBaseCurrency={pensionTaxAmountInBaseCurrency}
incomeTaxAmountInBaseCurrency={incomeTaxAmountInBaseCurrency}
exchangeRatesLoading={exchangeRatesLoading}
/>
<IncomeDetailsCard
accentColor={accentColor}
totalNetIncomeInBaseCurrency={totalNetIncomeInBaseCurrency}
netIncome={netIncome}
grossIncomeInBaseCurrency={grossIncomeInBaseCurrency}
totalTaxPercentage={totalTaxPercentage}
exchangeRatesLoading={exchangeRatesLoading}
/>
<SettingsInfoCard
grossIncomeOverVATThreshold={grossIncomeOverVATThreshold}
exchangeRatesLoading={exchangeRatesLoading}
exchangeRates={exchangeRates}
/>
</>
);
const grossIncomeOverVATThreshold =
pfa.grossIncomeInBaseCurrency !== undefined && pfa.grossIncomeInBaseCurrency > snap.vatThreshold;

return (
<>
<InputCard grossIncomeOverVATThreshold={grossIncomeOverVATThreshold}/>
<Card withBorder p="md" radius="md" pos="relative">
<Text fw={700} fz={24} lh={1.25} mt={{base: 'xs', xs: 'md'}} ta={"center"}>
Taxe PFA
</Text>
<TaxationDetailsCard
accentColor={accentColor}
totalTaxAmountInBaseCurrency={pfa.totalTaxAmountInBaseCurrency}
totalTaxPercentage={pfa.totalTaxPercentage}
healthTaxAmountInBaseCurrency={pfa.healthTaxAmountInBaseCurrency}
pensionTaxAmountInBaseCurrency={pfa.pensionTaxAmountInBaseCurrency}
incomeTaxAmountInBaseCurrency={pfa.incomeTaxAmountInBaseCurrency}
exchangeRatesLoading={pfa.exchangeRatesLoading}
/>
<IncomeDetailsCard
accentColor={accentColor}
totalNetIncomeInBaseCurrency={pfa.totalNetIncomeInBaseCurrency}
netIncome={pfa.netIncome}
grossIncomeInBaseCurrency={pfa.grossIncomeInBaseCurrency}
totalTaxPercentage={pfa.totalTaxPercentage}
exchangeRatesLoading={pfa.exchangeRatesLoading}
/>
</Card>

<Card withBorder p="md" radius="md" pos="relative">
<Text fw={700} fz={24} lh={1.25} mt={{base: 'xs', xs: 'md'}} ta={"center"}>
Taxe SRL (venit)
</Text>
<TaxationDetailsCard
accentColor={accentColor}
totalTaxAmountInBaseCurrency={srlVenit.totalTaxAmountInBaseCurrency}
totalTaxPercentage={srlVenit.totalTaxPercentage}
healthTaxAmountInBaseCurrency={srlVenit.healthTaxAmountInBaseCurrency}
pensionTaxAmountInBaseCurrency={srlVenit.pensionTaxAmountInBaseCurrency}
incomeTaxAmountInBaseCurrency={srlVenit.incomeTaxAmountInBaseCurrency}
exchangeRatesLoading={srlVenit.exchangeRatesLoading}
/>
<IncomeDetailsCard
accentColor={accentColor}
totalNetIncomeInBaseCurrency={srlVenit.totalNetIncomeInBaseCurrency}
netIncome={srlVenit.netIncome}
grossIncomeInBaseCurrency={srlVenit.grossIncomeInBaseCurrency}
totalTaxPercentage={srlVenit.totalTaxPercentage}
exchangeRatesLoading={srlVenit.exchangeRatesLoading}
/>
</Card>
<Card withBorder p="md" radius="md" pos="relative">
<Text fw={700} fz={24} lh={1.25} mt={{base: 'xs', xs: 'md'}} ta={"center"}>
Taxe SRL (Profit)
</Text>
<TaxationDetailsCard
accentColor={accentColor}
totalTaxAmountInBaseCurrency={srlProfit.totalTaxAmountInBaseCurrency}
totalTaxPercentage={srlProfit.totalTaxPercentage}
healthTaxAmountInBaseCurrency={srlProfit.healthTaxAmountInBaseCurrency}
pensionTaxAmountInBaseCurrency={srlProfit.pensionTaxAmountInBaseCurrency}
incomeTaxAmountInBaseCurrency={srlProfit.incomeTaxAmountInBaseCurrency}
exchangeRatesLoading={srlProfit.exchangeRatesLoading}
/>
<IncomeDetailsCard
accentColor={accentColor}
totalNetIncomeInBaseCurrency={srlProfit.totalNetIncomeInBaseCurrency}
netIncome={srlProfit.netIncome}
grossIncomeInBaseCurrency={srlProfit.grossIncomeInBaseCurrency}
totalTaxPercentage={srlProfit.totalTaxPercentage}
exchangeRatesLoading={srlProfit.exchangeRatesLoading}
/>
</Card>

<SettingsInfoCard
grossIncomeOverVATThreshold={grossIncomeOverVATThreshold}
exchangeRatesLoading={pfa.exchangeRatesLoading}
exchangeRates={pfa.exchangeRates}
/>
</>
);
}
90 changes: 45 additions & 45 deletions app/setari/SettingsPageContent.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
'use client';

import { Card, NumberInput, Stack, Text } from '@mantine/core';
import { useSnapshot } from 'valtio';
import { BASE_CURRENCY } from '~/lib/config';
import { state } from '~/lib/state';
import {Card, NumberInput, Stack, Text} from '@mantine/core';
import {useSnapshot} from 'valtio';
import {BASE_CURRENCY} from '~/lib/config';
import {state} from '~/lib/state';

export default function SettingsPageContent() {
const snap = useSnapshot(state);
const snap = useSnapshot(state);

return (
<Card p="md" pb="lg" withBorder radius="md">
<Stack gap="md">
<NumberInput
hideControls
required
min={0}
label="Salariul minim pe economie"
description="Reprezintă baza de calcul pentru taxe și impozite, mărită anual din pix de ciolaci"
rightSectionWidth={50}
rightSectionPointerEvents="none"
rightSection={
<Text c="dimmed" fz="sm" w="100%" pr="xs" ta="right">
{BASE_CURRENCY}
</Text>
}
value={snap.minimumWage}
onChange={(val) => (state.minimumWage = val === '' ? 0 : Number(val))}
error={snap.vatThreshold <= 0 ? 'Scrie o valoare pozitivă' : null}
/>
<NumberInput
hideControls
required
min={0}
label="Plafon de TVA"
description="Venitul anual peste care ești obligat să te înregistrezi în scopuri de TVA"
rightSectionWidth={50}
rightSectionPointerEvents="none"
rightSection={
<Text c="dimmed" fz="sm" w="100%" pr="xs" ta="right">
{BASE_CURRENCY}
</Text>
}
value={snap.vatThreshold}
onChange={(val) => (state.vatThreshold = val === '' ? 0 : Number(val))}
error={snap.vatThreshold <= 0 ? 'Scrie o valoare pozitivă' : null}
/>
</Stack>
</Card>
);
return (
<Card p="md" pb="lg" withBorder radius="md">
<Stack gap="md">
<NumberInput
hideControls
required
min={0}
label="Salariul minim pe economie"
description="Reprezintă baza de calcul pentru taxe și impozite, mărită anual din pix de ciolaci"
rightSectionWidth={50}
rightSectionPointerEvents="none"
rightSection={
<Text c="dimmed" fz="sm" w="100%" pr="xs" ta="right">
{BASE_CURRENCY}
</Text>
}
value={snap.minimumWage}
onChange={(val) => (state.minimumWage = val === '' ? 0 : Number(val))}
error={snap.vatThreshold <= 0 ? 'Scrie o valoare pozitivă' : null}
/>
<NumberInput
hideControls
required
min={0}
label="Plafon de TVA"
description="Venitul anual peste care ești obligat să te înregistrezi în scopuri de TVA"
rightSectionWidth={50}
rightSectionPointerEvents="none"
rightSection={
<Text c="dimmed" fz="sm" w="100%" pr="xs" ta="right">
{BASE_CURRENCY}
</Text>
}
value={snap.vatThreshold}
onChange={(val) => (state.vatThreshold = val === '' ? 0 : Number(val))}
error={snap.vatThreshold <= 0 ? 'Scrie o valoare pozitivă' : null}
/>
</Stack>
</Card>
);
}
2 changes: 1 addition & 1 deletion components/IncomeDetailsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function IncomeDetailsCard({
const textAlign: MantineStyleProps['ta'] = { base: 'center', xs: 'left' };

return (
<Card withBorder p="md" radius="md" pos="relative">
<Card p="md" radius="md" pos="relative">
<Flex direction={{ base: 'column', xs: 'row' }} align={{ base: 'center', xs: 'flex-start' }}>
<ExchangeRatesLoadingOverlay exchangeRatesLoading={exchangeRatesLoading} />
<Flex direction="column" gap={{ base: 'sm', xs: 'lg' }} align={{ base: 'center', xs: 'flex-start' }}>
Expand Down
2 changes: 1 addition & 1 deletion components/TaxationDetailsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function TaxationDetailsCard({
}: TaxationDetailsCardProps) {
const textAlign: MantineStyleProps['ta'] = { base: 'center', xs: 'left' };
return (
<Card withBorder p="md" radius="md" pos="relative">
<Card p="md" radius="md" pos="relative">
<Flex direction={{ base: 'column', xs: 'row' }} align={{ base: 'center', xs: 'flex-start' }}>
<ExchangeRatesLoadingOverlay exchangeRatesLoading={exchangeRatesLoading} />
<Flex direction="column" gap={{ base: 'sm', xs: 'lg' }} align={{ base: 'center', xs: 'flex-start' }}>
Expand Down
2 changes: 2 additions & 0 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export const WEEKS_PER_MONTH = 4.34524;

export const PENSION_PERCENTAGE = 0.25;
export const HEALTH_PERCENTAGE = 0.1;
export const PERSONAL_DEDUCTIBLE = 0.2;
export const INCOME_TAX_PERCENTAGE = 0.1;
export const WAGE_COMPANY_TAX_PERCENTAGE = 0.0225;
export const BASE_CURRENCY = 'RON';
export const CURRENCIES = [BASE_CURRENCY, 'EUR', 'USD', 'GBP', 'CHF', 'CAD', 'AUD'];
export const EXCHANGE_RATES_RELOAD_INTERVAL = 3_600_000;
Expand Down
65 changes: 41 additions & 24 deletions lib/state.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
import { proxy } from 'valtio';
import { BASE_CURRENCY, DeductibleExpensesInterval, IncomeInterval } from './config';
import {proxy} from 'valtio';
import {BASE_CURRENCY, DeductibleExpensesInterval, IncomeInterval} from './config';

export type State = {
income: number;
incomeCurrency: string;
incomeInterval: IncomeInterval;
workingHoursPerWeek: number;
workingDaysPerWeek: number;
vacationWeeksPerYear: number;
deductibleExpenses: number;
deductibleExpensesCurrency: string;
deductibleExpensesInterval: DeductibleExpensesInterval;
minimumWage: number;
vatThreshold: number;
income: number;
incomeCurrency: string;
incomeInterval: IncomeInterval;
workingHoursPerWeek: number;
workingDaysPerWeek: number;
vacationWeeksPerYear: number;
deductibleExpenses: number;
deductibleExpensesCurrency: string;
deductibleExpensesInterval: DeductibleExpensesInterval;
minimumWage: number;
minimumWageTaxFreeDeductible: number;
vatThreshold: number;
dividendsTax: number;
companyIncomeTax: number;
companyHighIncomeTax: number;
companyProfitTax: number;
companyIncomeTaxThreshold: number;
companyIncomeTaxToProfitThreshold: number;
};

export const initialState: State = {
income: 12000,
incomeCurrency: BASE_CURRENCY,
incomeInterval: 'monthly',
workingHoursPerWeek: 40,
workingDaysPerWeek: 5,
vacationWeeksPerYear: 4,
deductibleExpenses: 0,
deductibleExpensesCurrency: BASE_CURRENCY,
deductibleExpensesInterval: 'monthly',
minimumWage: 3_300,
vatThreshold: 300_000,
minimumWageTaxFreeDeductible: 300,
income: 12000,
incomeCurrency: BASE_CURRENCY,
incomeInterval: 'monthly',
workingHoursPerWeek: 40,
workingDaysPerWeek: 5,
vacationWeeksPerYear: 4,
deductibleExpenses: 0,
deductibleExpensesCurrency: BASE_CURRENCY,
deductibleExpensesInterval: 'monthly',
minimumWage: 3_300,
vatThreshold: 300_000,
dividendsTax: 0.10,
companyIncomeTax: 0.01,
companyHighIncomeTax: 0.03,
companyProfitTax: 0.16,
// Values are in RON for 2024
companyIncomeTaxThreshold: 298_476,
// Values are in RON for 2024
companyIncomeTaxToProfitThreshold: 2_487_300

};

export const state = proxy<State>(initialState);
Loading