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
136 lines
3.5 KiB
TypeScript
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 };
|