Files
frontend/src/pages/account-income/ui/AccountIncomePage/components/IncomeComparisons.view.tsx
T
ksv741 b987fd8e78
Frontend security / security (push) Successful in 9s
Frontend verify / verify (push) Successful in 5m10s
Conventional commits / commits (pull_request) Successful in 25s
Frontend security / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Successful in 5m10s
refactor: colocate frontend component files
2026-09-23 08:34:58 +03:00

136 lines
3.5 KiB
TypeScript

import { Paper, Stack, Text, Title } from "@mantine/core";
import type { FC } from "react";
import { useTranslation } from "react-i18next";
import { formatMoney } from "@/shared/lib";
import classes from "../AccountIncomePage.module.css";
import type { IncomeDisplayModel } from "../AccountIncomePage.vm";
type IncomeComparisonsProps = { comparison: IncomeDisplayModel["comparison"] };
type ComparisonProps = NonNullable<IncomeDisplayModel["comparison"]>;
type ComparisonValuesProps = {
kind: "selected" | "compared" | "difference";
label: string;
values: ComparisonProps["values"][number]["selected"];
percentages?: ComparisonProps["values"][number]["percentages"];
};
const IncomeComparisons: FC<IncomeComparisonsProps> = (props) => {
const { t } = useTranslation();
return (
<Stack>
<Title order={2}>{t("income.yearAgo")}</Title>
{props.comparison ? (
<Comparison {...props.comparison} />
) : (
<Paper p="sm" radius="md" className={classes.comparisonNotice}>
<Text size="sm" c="gray.7">
{t("income.yearUnavailable")}
</Text>
</Paper>
)}
</Stack>
);
};
const Comparison: FC<ComparisonProps> = (props) => {
const { t } = useTranslation();
return (
<section aria-label={t("income.yearAgo")} className={classes.comparison}>
<Stack gap="md">
{props.values.map((value) => (
<Stack
gap="xs"
key={value.currency}
role="group"
aria-label={value.currency}
>
<div className={classes.comparisonGrid}>
<ComparisonValues
kind="selected"
label={props.selectedPeriod}
values={value.selected}
/>
<ComparisonValues
kind="compared"
label={props.comparedPeriod}
values={value.compared}
/>
<ComparisonValues
kind="difference"
label={t("income.difference")}
values={value.difference}
percentages={value.percentages}
/>
</div>
</Stack>
))}
{props.values.length === 0 ? (
<Text size="sm" c="gray.7">
{t("income.comparisonEmpty")}
</Text>
) : null}
</Stack>
</section>
);
};
const ComparisonValues: FC<ComparisonValuesProps> = (props) => {
const { t } = useTranslation();
return (
<section
aria-label={props.label}
className={classes.comparisonPanel}
data-kind={props.kind}
data-tone={
props.kind === "difference"
? differenceTone(props.values.total.value)
: undefined
}
>
<Text size="sm" fw={600} className={classes.comparisonLabel}>
{props.label}
</Text>
<dl className={classes.comparisonValues}>
{(["total", "coupon", "dividend"] as const).map((key) => {
const money = props.values[key];
const percentage = props.percentages?.[key];
const tone =
props.kind === "difference"
? differenceTone(money.value)
: undefined;
return (
<div
key={key}
className={
key === "total"
? classes.comparisonTotal
: classes.comparisonDetail
}
>
<dt>{t(`income.${key}`)}</dt>
<dd data-tone={tone}>
<span>
{tone === "positive" ? "+" : ""}
{formatMoney(money)}
</span>
{percentage ? (
<span className={classes.comparisonPercent}>
{percentage}
</span>
) : null}
</dd>
</div>
);
})}
</dl>
</section>
);
};
function differenceTone(value: string): "positive" | "negative" | "neutral" {
if (!/[1-9]/.test(value)) return "neutral";
return value.startsWith("-") ? "negative" : "positive";
}
export { IncomeComparisons };