feat: show persisted investment data and sync state

Consume the provided resource-envelope API snapshot. Preserve financial snapshots during
refresh, observe synchronization without importing, and fence late responses across
account and connection changes.
This commit is contained in:
2026-09-26 21:22:49 +03:00
parent b987fd8e78
commit 4e5f564fa1
81 changed files with 4714 additions and 1420 deletions
@@ -4,18 +4,34 @@ import type { FC } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import type { IncomeSummary as IncomeSummaryData } from "@/entities/income";
import { formatMoney } from "@/shared/lib";
import { formatMoney, formatSnapshotTime } from "@/shared/lib";
type IncomeSummaryProps =
| { state: "loading"; to: string }
| { state: "partial"; to: string }
| { state: "error"; to: string; retry: () => void }
| { state: "empty"; to: string }
| { state: "loaded"; to: string; summary: IncomeSummaryData };
| {
state: "loaded";
to: string;
summary: IncomeSummaryData;
lastSuccessfulAt?: string | null;
stale?: boolean;
refreshError?: boolean;
};
const IncomeSummary: FC<IncomeSummaryProps> = (props) => {
const { t } = useTranslation();
if (props.state === "loading")
return <Text c="dimmed">{t("income.summaryLoading")}</Text>;
if (props.state === "loading" || props.state === "partial")
return (
<Text role="status" c="dimmed">
{t(
props.state === "partial"
? "investment.partial"
: "income.summaryLoading",
)}
</Text>
);
if (props.state === "error")
return (
<StateView
@@ -38,6 +54,17 @@ const IncomeSummary: FC<IncomeSummaryProps> = (props) => {
);
return (
<Stack gap="xs">
{props.lastSuccessfulAt ? (
<Text size="xs" c="dimmed">
{t("investment.sourceTime", {
date: formatSnapshotTime(props.lastSuccessfulAt),
})}
</Text>
) : null}
{props.stale ? <Text size="xs">{t("investment.stale")}</Text> : null}
{props.refreshError ? (
<Text role="status">{t("income.refreshError")}</Text>
) : null}
{props.summary.totalsByCurrency.map((total) => (
<Paper key={total.currency} p="sm" withBorder>
<Text fw={700}>{formatMoney(total.total)}</Text>