Files
frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts
T
ksv741 4e5f564fa1 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.
2026-09-26 21:22:49 +03:00

98 lines
2.8 KiB
TypeScript

import { useEffect, useState } from "react";
import { useParams, useSearchParams } from "react-router";
import {
type BrokerageEvent,
useBrokerageEvents,
} from "@/entities/brokerage-event";
import { useScreenSync } from "@/features/tbank-connection";
type AccountEventsPageViewModel =
| { state: "loading" }
| { state: "error"; retry: () => void }
| {
state: "loaded";
events: BrokerageEvent[];
sourceError?: boolean;
lastSuccessfulAt?: string | null;
partial?: boolean;
refreshing: boolean;
refreshError: boolean;
retry: () => void;
draftFrom: string | null;
draftTo: string | null;
canApply: boolean;
onDraftFromChange: (value: string | null) => void;
onDraftToChange: (value: string | null) => void;
onApply: () => void;
onReset: () => void;
};
function useAccountEventsPageViewModel(
scopeId?: string,
): AccountEventsPageViewModel {
const { accountId: routeAccountId } = useParams();
const accountId = scopeId ?? routeAccountId;
const [searchParams, setSearchParams] = useSearchParams();
const appliedFrom = searchParams.get("from");
const appliedTo = searchParams.get("to");
const [draftFrom, setDraftFrom] = useState<string | null>(appliedFrom);
const [draftTo, setDraftTo] = useState<string | null>(appliedTo);
useEffect(() => {
setDraftFrom(appliedFrom);
setDraftTo(appliedTo);
}, [appliedFrom, appliedTo]);
const events = useBrokerageEvents(accountId, {
...(appliedFrom ? { from: appliedFrom } : {}),
...(appliedTo ? { to: appliedTo } : {}),
});
const sync = useScreenSync(
{
screen: "events",
accountId,
...(appliedFrom ? { from: appliedFrom } : {}),
...(appliedTo ? { to: appliedTo } : {}),
},
[events],
);
if (events.isLoading) return { state: "loading" };
if ((events.isError || events.meta?.sync.state === "failed") && !events.data)
return {
state: "error",
retry: () => {
void sync.refresh();
},
};
if (!events.data) return { state: "loading" };
return {
state: "loaded",
events: events.data.events,
sourceError: sync.sourceError,
lastSuccessfulAt: events.meta?.lastSuccessfulAt,
partial: events.meta?.availability !== "complete",
refreshing: events.isFetching || sync.refreshing,
refreshError: events.isError || sync.sourceError || sync.transportError,
retry: () => {
void sync.refresh();
},
draftFrom,
draftTo,
canApply:
(draftFrom !== appliedFrom || draftTo !== appliedTo) &&
(!draftFrom || !draftTo || draftFrom <= draftTo),
onDraftFromChange: setDraftFrom,
onDraftToChange: setDraftTo,
onApply: () =>
setSearchParams({
...(draftFrom ? { from: draftFrom } : {}),
...(draftTo ? { to: draftTo } : {}),
}),
onReset: () => {
setDraftFrom(null);
setDraftTo(null);
setSearchParams({});
},
};
}
export { type AccountEventsPageViewModel, useAccountEventsPageViewModel };