chore: finish frontend ESLint migration #18
Enable every plugin recommended preset and replace Biome with ESLint formatting. Preserve API contracts and JSX whitespace; verify source, tests, builds and tooling.
This commit is contained in:
@@ -1,53 +1,54 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams, useSearchParams } from "react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
type BrokerageEvent,
|
||||
useBrokerageEvents,
|
||||
} from "@/entities/brokerage-event";
|
||||
import { useScreenSync } from "@/features/tbank-connection";
|
||||
|
||||
type AccountEventsPageViewModel =
|
||||
| { state: "loading" }
|
||||
| { state: "error"; retry: () => void; onReset: () => 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;
|
||||
};
|
||||
type AccountEventsPageViewModel
|
||||
= | {
|
||||
canApply: boolean;
|
||||
draftFrom: null | string;
|
||||
draftTo: null | string;
|
||||
events: BrokerageEvent[];
|
||||
lastSuccessfulAt?: null | string;
|
||||
onApply: () => void;
|
||||
onDraftFromChange: (value: null | string) => void;
|
||||
onDraftToChange: (value: null | string) => void;
|
||||
onReset: () => void;
|
||||
partial?: boolean;
|
||||
refreshError: boolean;
|
||||
refreshing: boolean;
|
||||
retry: () => void;
|
||||
sourceError?: boolean;
|
||||
state: "loaded";
|
||||
}
|
||||
| { onReset: () => void; retry: () => void; state: "error" }
|
||||
| { state: "loading" };
|
||||
|
||||
function useAccountEventsPageViewModel(
|
||||
scopeId?: string,
|
||||
): AccountEventsPageViewModel {
|
||||
const { accountId: routeAccountId } = useParams();
|
||||
const accountId = scopeId ?? routeAccountId ?? "";
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const urlFrom = searchParams.get("from");
|
||||
const urlTo = searchParams.get("to");
|
||||
const validUrlPeriod = isValidPeriod(urlFrom, urlTo);
|
||||
const appliedFrom = validUrlPeriod ? urlFrom : null;
|
||||
const appliedTo = validUrlPeriod ? urlTo : null;
|
||||
const [draftFrom, setDraftFrom] = useState<string | null>(appliedFrom);
|
||||
const [draftTo, setDraftTo] = useState<string | null>(appliedTo);
|
||||
const [searchParameters, setSearchParameters] = useSearchParams();
|
||||
const urlFrom = searchParameters.get("from");
|
||||
const urlTo = searchParameters.get("to");
|
||||
const isValidUrlPeriod = isValidPeriod(urlFrom, urlTo);
|
||||
const appliedFrom = isValidUrlPeriod ? urlFrom : /* eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model. */ null;
|
||||
const appliedTo = isValidUrlPeriod ? urlTo : /* eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model. */ null;
|
||||
const [draftFrom, setDraftFrom] = useState<null | string>(appliedFrom);
|
||||
const [draftTo, setDraftTo] = useState<null | string>(appliedTo);
|
||||
useEffect(() => {
|
||||
if (validUrlPeriod) return;
|
||||
const normalized = new URLSearchParams(searchParams);
|
||||
if (isValidUrlPeriod) return;
|
||||
const normalized = new URLSearchParams(searchParameters);
|
||||
normalized.delete("from");
|
||||
normalized.delete("to");
|
||||
setSearchParams(normalized, { replace: true });
|
||||
}, [validUrlPeriod, searchParams, setSearchParams]);
|
||||
setSearchParameters(normalized, { replace: true });
|
||||
}, [isValidUrlPeriod, searchParameters, setSearchParameters]);
|
||||
const [previousPeriod, setPreviousPeriod] = useState([
|
||||
appliedFrom,
|
||||
appliedTo,
|
||||
@@ -58,69 +59,69 @@ function useAccountEventsPageViewModel(
|
||||
setDraftTo(appliedTo);
|
||||
}
|
||||
const events = useBrokerageEvents(accountId, {
|
||||
...(appliedFrom ? { from: appliedFrom } : {}),
|
||||
...(appliedTo ? { to: appliedTo } : {}),
|
||||
...(appliedFrom && { from: appliedFrom }),
|
||||
...(appliedTo && { to: appliedTo }),
|
||||
});
|
||||
const sync = useScreenSync(
|
||||
{
|
||||
screen: "events",
|
||||
accountId,
|
||||
...(appliedFrom ? { from: appliedFrom } : {}),
|
||||
...(appliedTo ? { to: appliedTo } : {}),
|
||||
screen: "events",
|
||||
...(appliedFrom && { from: appliedFrom }),
|
||||
...(appliedTo && { to: appliedTo }),
|
||||
},
|
||||
[events],
|
||||
);
|
||||
if (events.isLoading) return { state: "loading" };
|
||||
const onReset = () => {
|
||||
setDraftFrom(null);
|
||||
setDraftTo(null);
|
||||
setSearchParams({});
|
||||
setSearchParameters({});
|
||||
};
|
||||
if (events.isLoading) return { state: "loading" };
|
||||
if ((events.isError || events.meta?.sync.state === "failed") && !events.data)
|
||||
return {
|
||||
state: "error",
|
||||
onReset,
|
||||
retry: () => {
|
||||
void sync.retry();
|
||||
},
|
||||
state: "error",
|
||||
};
|
||||
if (!events.data) return { state: "loading" };
|
||||
return {
|
||||
state: "loaded",
|
||||
canApply:
|
||||
(draftFrom !== appliedFrom || draftTo !== appliedTo)
|
||||
&& isValidPeriod(draftFrom, draftTo),
|
||||
draftFrom,
|
||||
draftTo,
|
||||
events: events.data.events,
|
||||
sourceError: sync.sourceError,
|
||||
lastSuccessfulAt: events.meta?.lastSuccessfulAt,
|
||||
onApply: () => {
|
||||
if (!isValidPeriod(draftFrom, draftTo)) return;
|
||||
setSearchParameters({
|
||||
...(draftFrom && { from: draftFrom }),
|
||||
...(draftTo && { to: draftTo }),
|
||||
});
|
||||
},
|
||||
onDraftFromChange: setDraftFrom,
|
||||
onDraftToChange: setDraftTo,
|
||||
onReset,
|
||||
partial: events.meta?.availability !== "complete",
|
||||
refreshing: events.isFetching || sync.refreshing,
|
||||
refreshError: events.isError || sync.sourceError || sync.transportError,
|
||||
refreshing: events.isFetching || sync.refreshing,
|
||||
retry: () => {
|
||||
void sync.retry();
|
||||
},
|
||||
draftFrom,
|
||||
draftTo,
|
||||
canApply:
|
||||
(draftFrom !== appliedFrom || draftTo !== appliedTo) &&
|
||||
isValidPeriod(draftFrom, draftTo),
|
||||
onDraftFromChange: setDraftFrom,
|
||||
onDraftToChange: setDraftTo,
|
||||
onApply: () => {
|
||||
if (!isValidPeriod(draftFrom, draftTo)) return;
|
||||
setSearchParams({
|
||||
...(draftFrom ? { from: draftFrom } : {}),
|
||||
...(draftTo ? { to: draftTo } : {}),
|
||||
});
|
||||
},
|
||||
onReset,
|
||||
sourceError: sync.sourceError,
|
||||
state: "loaded",
|
||||
};
|
||||
}
|
||||
|
||||
const calendarDate = z.string().date();
|
||||
function isValidPeriod(from: string | null, to: string | null) {
|
||||
const calendarDate = z.iso.date();
|
||||
function isValidPeriod(from: null | string, to: null | string) {
|
||||
return (
|
||||
[from, to].every(
|
||||
(date) => date === null || calendarDate.safeParse(date).success,
|
||||
) &&
|
||||
(!from || !to || from <= to)
|
||||
date => date === null || calendarDate.safeParse(date).success,
|
||||
)
|
||||
&& (!from || !to || from <= to)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user