refactor: enforce frontend view model boundaries
This commit is contained in:
@@ -9,6 +9,7 @@ describe("instrument source and transport presentation", () => {
|
||||
"keeps %s separate from successful transport",
|
||||
(availability) => {
|
||||
const data = availability === "missing" ? undefined : { rows: [] };
|
||||
|
||||
const state = section({
|
||||
data,
|
||||
isError: false,
|
||||
@@ -16,6 +17,7 @@ describe("instrument source and transport presentation", () => {
|
||||
meta: resource(data, { availability }).meta,
|
||||
refetch: vi.fn(),
|
||||
});
|
||||
|
||||
expect(state.loading).toBe(availability === "missing");
|
||||
expect(state.partial).toBe(availability === "partial");
|
||||
expect(state.hasData).toBe(availability !== "missing");
|
||||
@@ -24,13 +26,16 @@ describe("instrument source and transport presentation", () => {
|
||||
it("preserves a full fresh source and timestamp when force fails, while offering scoped retry", () => {
|
||||
const retry = vi.fn();
|
||||
const refetch = vi.fn();
|
||||
|
||||
const meta = resource([], {
|
||||
sync: { errorCode: "TBANK_UNAVAILABLE", id: "work", state: "failed" },
|
||||
}).meta;
|
||||
|
||||
const state = section(
|
||||
{ data: [], isError: false, isFetching: false, meta, refetch },
|
||||
retry,
|
||||
);
|
||||
|
||||
expect(state).toMatchObject({
|
||||
error: true,
|
||||
hasData: true,
|
||||
@@ -43,17 +48,16 @@ describe("instrument source and transport presentation", () => {
|
||||
expect(refetch).not.toHaveBeenCalled();
|
||||
});
|
||||
it("shows source failure without a false loading or empty result", () => {
|
||||
const state = section((
|
||||
{
|
||||
data: undefined,
|
||||
isError: false,
|
||||
isFetching: false,
|
||||
meta: resource(null, {
|
||||
sync: { errorCode: "TBANK_UNAVAILABLE", id: "work", state: "failed" },
|
||||
}).meta,
|
||||
refetch: vi.fn(),
|
||||
}
|
||||
));
|
||||
const state = section({
|
||||
data: undefined,
|
||||
isError: false,
|
||||
isFetching: false,
|
||||
meta: resource(null, {
|
||||
sync: { errorCode: "TBANK_UNAVAILABLE", id: "work", state: "failed" },
|
||||
}).meta,
|
||||
refetch: vi.fn(),
|
||||
});
|
||||
|
||||
expect(state).toMatchObject({
|
||||
error: true,
|
||||
hasData: false,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { paths } from "@/shared/api";
|
||||
|
||||
type Meta
|
||||
= paths["/api/v1/tbank-sync/{id}"]["get"]["responses"][200]["content"]["application/json"]["resources"][number];
|
||||
|
||||
type SectionState = {
|
||||
error: boolean;
|
||||
hasData: boolean;
|
||||
@@ -13,6 +14,7 @@ type SectionState = {
|
||||
sourceError?: boolean;
|
||||
stale?: boolean;
|
||||
};
|
||||
|
||||
function section(
|
||||
query: {
|
||||
data: unknown;
|
||||
@@ -24,6 +26,7 @@ function section(
|
||||
retry?: () => unknown,
|
||||
): SectionState {
|
||||
const isError = query.isError || query.meta?.sync.state === "failed";
|
||||
|
||||
return {
|
||||
error: isError,
|
||||
hasData: Boolean(query.data),
|
||||
|
||||
@@ -6,7 +6,9 @@ import type { SectionState } from "./section";
|
||||
type Candles = NonNullable<
|
||||
paths["/api/v1/stocks/{uid}/candles"]["get"]["responses"][200]["content"]["application/json"]["data"]
|
||||
>;
|
||||
|
||||
type ChartRow = Candles["candles"][number] & { date: string; price: number };
|
||||
|
||||
type InstrumentChartVm = {
|
||||
averageLine: null | number;
|
||||
candles?: Candles;
|
||||
@@ -18,6 +20,7 @@ type InstrumentChartVm = {
|
||||
priceRows: ChartRow[];
|
||||
setPeriod: (value: string) => void;
|
||||
};
|
||||
|
||||
type InstrumentEventsVm = {
|
||||
eventRows: BrokerageOperation[];
|
||||
filter: string;
|
||||
@@ -28,10 +31,12 @@ type InstrumentEventsVm = {
|
||||
operationsState: SectionState;
|
||||
setFilter: (value: string) => void;
|
||||
};
|
||||
|
||||
type InstrumentPositionVm = {
|
||||
position?: Position;
|
||||
positionState: SectionState;
|
||||
};
|
||||
|
||||
type Position = NonNullable<
|
||||
paths["/api/v1/stocks/{uid}/position"]["get"]["responses"][200]["content"]["application/json"]["data"]
|
||||
>;
|
||||
|
||||
@@ -14,19 +14,28 @@ function useInstrumentOperations(
|
||||
categories: OperationCategory[],
|
||||
) {
|
||||
const scope = `${uid}:${filter}`;
|
||||
|
||||
const [pagination, setPagination] = useState<{
|
||||
cursors: string[];
|
||||
scope: string;
|
||||
}>({ cursors: [], scope });
|
||||
|
||||
const cursors = pagination.scope === scope ? pagination.cursors : [];
|
||||
const client = useQueryClient();
|
||||
const firstPage = { categories, instrumentUid: uid, limit: 20 };
|
||||
|
||||
const operations = useBrokerageOperations(uid ? "all" : undefined, {
|
||||
...firstPage,
|
||||
cursor: cursors.at(-1),
|
||||
});
|
||||
|
||||
const retry = (retryScreen: () => unknown) => {
|
||||
if (isApiErrorCode(apiErrorCode(operations.error), ApiErrorCode.InvalidOperationsCursor)) {
|
||||
if (
|
||||
isApiErrorCode(
|
||||
apiErrorCode(operations.error),
|
||||
ApiErrorCode.InvalidOperationsCursor,
|
||||
)
|
||||
) {
|
||||
setPagination({ cursors: [], scope });
|
||||
void client.invalidateQueries({
|
||||
exact: true,
|
||||
@@ -35,6 +44,7 @@ function useInstrumentOperations(
|
||||
}
|
||||
else void retryScreen();
|
||||
};
|
||||
|
||||
return {
|
||||
hasPrevious: cursors.length > 0,
|
||||
onNext: () => {
|
||||
|
||||
Reference in New Issue
Block a user