feat: unify income payment cards and remove comparisons (WealthLedger/main#33) (#74)
feat: unify income payment cards and remove comparisons (WealthLedger/main#33)
This commit was merged in pull request #74.
This commit is contained in:
-13
@@ -12,7 +12,6 @@ import { useTranslation } from "react-i18next";
|
||||
import type { AccountIncomePageViewModel } from "../AccountIncomePage.vm";
|
||||
|
||||
import classes from "../AccountIncomePage.module.css";
|
||||
import { IncomeComparisons } from "./IncomeComparisons.view";
|
||||
import { IncomePayments } from "./IncomePayments.view";
|
||||
import { IncomePeriodForm } from "./IncomePeriodForm/IncomePeriodForm.view";
|
||||
import { IncomeSummary } from "./IncomeSummary.view";
|
||||
@@ -54,18 +53,6 @@ const AccountIncomePageLoaded: FC<LoadedProps> = (props) => {
|
||||
|
||||
<Alert color="blue">{t("income.taxBoundary")}</Alert>
|
||||
|
||||
<IncomeComparisons
|
||||
comparison={display.previousComparison}
|
||||
notice={t(`income.${display.previousNotice}`)}
|
||||
title={t("income.previousPeriod")}
|
||||
/>
|
||||
|
||||
<IncomeComparisons
|
||||
comparison={display.comparison}
|
||||
notice={t(`income.${display.yearNotice}`)}
|
||||
title={t("income.yearAgo")}
|
||||
/>
|
||||
|
||||
<IncomePayments entries={display.entries} />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
import type { FC } from "react";
|
||||
|
||||
import { Paper, Stack, Text, Title } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { formatMoney } from "@/shared/lib";
|
||||
|
||||
import type { IncomeDisplayModel } from "../../../model/display";
|
||||
|
||||
import classes from "../AccountIncomePage.module.css";
|
||||
|
||||
type ComparisonProps = NonNullable<IncomeDisplayModel["comparison"]> & {
|
||||
title: string;
|
||||
};
|
||||
|
||||
type ComparisonValuesProps = {
|
||||
kind: "compared"
|
||||
| "difference"
|
||||
| "selected";
|
||||
label: string;
|
||||
percentages?: ComparisonProps["values"][number]["percentages"];
|
||||
values: ComparisonProps["values"][number]["selected"];
|
||||
};
|
||||
|
||||
type IncomeComparisonsProps = {
|
||||
comparison: IncomeDisplayModel["comparison"];
|
||||
notice: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
const IncomeComparisons: FC<IncomeComparisonsProps> = (props) => {
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={2}>{props.title}</Title>
|
||||
|
||||
{props.comparison
|
||||
? <Comparison {...props.comparison} title={props.title} />
|
||||
: (
|
||||
<Paper className={classes.comparisonNotice} p="sm" radius="md">
|
||||
<Text aria-live="polite" c="gray.7" role="status" size="sm">
|
||||
{props.notice}
|
||||
</Text>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const Comparison: FC<ComparisonProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section aria-label={props.title} className={classes.comparison}>
|
||||
<Stack gap="md">
|
||||
{props.values.map(value => (
|
||||
<Stack
|
||||
aria-label={value.currency}
|
||||
gap="xs"
|
||||
key={value.currency}
|
||||
role="group"
|
||||
>
|
||||
<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")}
|
||||
percentages={value.percentages}
|
||||
values={value.difference}
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
{props.values.length === 0 && (
|
||||
<Text c="gray.7" size="sm">
|
||||
{t("income.comparisonEmpty")}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const ComparisonValues: FC<ComparisonValuesProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const data_tone
|
||||
= props.kind === "difference"
|
||||
? differenceTone(props.values.total.value)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label={props.label}
|
||||
className={classes.comparisonPanel}
|
||||
data-kind={props.kind}
|
||||
data-tone={data_tone}
|
||||
>
|
||||
<Text className={classes.comparisonLabel} fw={600} size="sm">
|
||||
{props.label}
|
||||
</Text>
|
||||
|
||||
<dl className={classes.comparisonValues}>
|
||||
{(["total", "coupon", "dividend"] as const).map((key) => {
|
||||
const money
|
||||
// eslint-disable-next-line security/detect-object-injection -- This lookup uses a fixed typed key set, not an arbitrary external property name.
|
||||
= props.values[key];
|
||||
|
||||
const percentage
|
||||
// eslint-disable-next-line security/detect-object-injection -- This lookup uses a fixed typed key set, not an arbitrary external property name.
|
||||
= props.percentages?.[key];
|
||||
|
||||
const tone
|
||||
= props.kind === "difference"
|
||||
? differenceTone(money.value)
|
||||
: undefined;
|
||||
|
||||
const appearanceClass
|
||||
= key === "total"
|
||||
? classes.comparisonTotal
|
||||
: classes.comparisonDetail;
|
||||
|
||||
return (
|
||||
<div className={appearanceClass} key={key}>
|
||||
<dt>{t(`income.${key}`)}</dt>
|
||||
|
||||
<dd data-tone={tone}>
|
||||
<span>
|
||||
{tone === "positive" ? "+" : ""}
|
||||
|
||||
{formatMoney(money)}
|
||||
</span>
|
||||
|
||||
{!!percentage && (
|
||||
<span className={classes.comparisonPercent}>
|
||||
{percentage}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</dl>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
function differenceTone(value: string): "negative"
|
||||
| "neutral"
|
||||
| "positive" {
|
||||
if (!/[1-9]/.test(value)) return "neutral";
|
||||
|
||||
return value.startsWith("-") ? "negative" : "positive";
|
||||
}
|
||||
|
||||
export { IncomeComparisons };
|
||||
+3
-10
@@ -1,6 +1,6 @@
|
||||
import type { FC, SubmitEventHandler } from "react";
|
||||
|
||||
import { Button, Paper, Stack, Text } from "@mantine/core";
|
||||
import { Button, Stack } from "@mantine/core";
|
||||
import { DatePickerInput } from "@mantine/dates";
|
||||
|
||||
import classes from "../../AccountIncomePage.module.css";
|
||||
@@ -27,15 +27,8 @@ const IncomePeriodForm: FC<IncomePeriodFormProps> = (props) => {
|
||||
} = useIncomePeriodFormVm();
|
||||
|
||||
return (
|
||||
<Paper
|
||||
component="form"
|
||||
onSubmit={props.onSubmit}
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
>
|
||||
<form onSubmit={props.onSubmit}>
|
||||
<Stack gap="sm">
|
||||
<Text fw={700}>{t("income.period")}</Text>
|
||||
|
||||
<div className={classes.fields}>
|
||||
<DatePickerInput
|
||||
@@ -71,7 +64,7 @@ const IncomePeriodForm: FC<IncomePeriodFormProps> = (props) => {
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user