feat: redesign year-over-year income comparison
This commit is contained in:
@@ -11,6 +11,15 @@ type IncomeForm = IncomePeriod;
|
||||
|
||||
type IncomeDisplayModel = {
|
||||
entries: { key: string; operation: IncomeAnalytics["entries"][number] }[];
|
||||
comparison: {
|
||||
selectedPeriod: string;
|
||||
comparedPeriod: string;
|
||||
values: (NonNullable<
|
||||
IncomeAnalytics["yearComparison"]
|
||||
>["totalsByCurrency"][number] & {
|
||||
percentages: Record<"total" | "coupon" | "dividend", string | null>;
|
||||
})[];
|
||||
} | null;
|
||||
};
|
||||
|
||||
type AccountIncomePageViewModel =
|
||||
@@ -209,7 +218,60 @@ function toIncomeDisplayModel(
|
||||
if (instrument.includedCount !== instrument.receiptCount) return null;
|
||||
}
|
||||
|
||||
return { entries };
|
||||
return {
|
||||
entries,
|
||||
comparison: analytics.yearComparison
|
||||
? {
|
||||
selectedPeriod: formatComparisonPeriod(analytics.period),
|
||||
comparedPeriod: formatComparisonPeriod(
|
||||
analytics.yearComparison.period,
|
||||
),
|
||||
values: analytics.yearComparison.totalsByCurrency.map((value) => ({
|
||||
...value,
|
||||
percentages: {
|
||||
total: formatIncomeChange(
|
||||
value.difference.total.value,
|
||||
value.compared.total.value,
|
||||
),
|
||||
coupon: formatIncomeChange(
|
||||
value.difference.coupon.value,
|
||||
value.compared.coupon.value,
|
||||
),
|
||||
dividend: formatIncomeChange(
|
||||
value.difference.dividend.value,
|
||||
value.compared.dividend.value,
|
||||
),
|
||||
},
|
||||
})),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
function formatComparisonPeriod(period: IncomePeriod): string {
|
||||
return new Intl.DateTimeFormat("ru-RU", {
|
||||
dateStyle: "medium",
|
||||
timeZone: "UTC",
|
||||
}).formatRange(
|
||||
new Date(`${period.from}T00:00:00Z`),
|
||||
new Date(`${period.to}T00:00:00Z`),
|
||||
);
|
||||
}
|
||||
|
||||
function formatIncomeChange(
|
||||
difference: string,
|
||||
compared: string,
|
||||
): string | null {
|
||||
// Display-only ratio; server-provided Money values remain exact decimal strings.
|
||||
const base = Number(compared);
|
||||
if (!Number.isFinite(base) || base <= 0) return null;
|
||||
const ratio = Number(difference) / base;
|
||||
if (!Number.isFinite(ratio)) return null;
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "percent",
|
||||
maximumFractionDigits: 1,
|
||||
signDisplay: "exceptZero",
|
||||
}).format(ratio);
|
||||
}
|
||||
|
||||
function moscowDate(value: Date): string {
|
||||
@@ -227,6 +289,7 @@ function moscowDate(value: Date): string {
|
||||
export {
|
||||
type AccountIncomePageViewModel,
|
||||
formatDraftDate,
|
||||
type IncomeDisplayModel,
|
||||
type IncomeForm,
|
||||
isDate,
|
||||
moscowDate,
|
||||
|
||||
Reference in New Issue
Block a user