Reuse account sections and group securities with per-account holdings. Use Mantine Select to preserve filters and reset pagination on scope changes. Document component reuse guardrails and cover routes, grouping and scope switching.
155 lines
3.8 KiB
TypeScript
155 lines
3.8 KiB
TypeScript
import {
|
|
Alert,
|
|
Button,
|
|
Group,
|
|
MultiSelect,
|
|
Progress,
|
|
Stack,
|
|
Title,
|
|
} from "@mantine/core";
|
|
import { DateInput } from "@mantine/dates";
|
|
import type { FC } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import {
|
|
LedgerEntryRow,
|
|
type OperationCategory,
|
|
operationCategories,
|
|
} from "@/entities/broker-operation";
|
|
import { StateView } from "@/shared/ui";
|
|
import type { AccountOperationsPageViewModel } from "../AccountOperationsPage.vm";
|
|
|
|
type AccountOperationsPageLoadedProps = Extract<
|
|
AccountOperationsPageViewModel,
|
|
{ state: "loaded" }
|
|
>;
|
|
|
|
const operationCategorySet = new Set<string>(operationCategories);
|
|
|
|
function toDateValue(value: string | null): Date | null {
|
|
if (!value) return null;
|
|
const [year, month, day] = value.split("-").map(Number);
|
|
return new Date(year, month - 1, day);
|
|
}
|
|
|
|
function fromDateValue(value: Date | string | null): string | null {
|
|
if (!value) return null;
|
|
if (typeof value === "string") return value;
|
|
const year = value.getFullYear();
|
|
const month = String(value.getMonth() + 1).padStart(2, "0");
|
|
const day = String(value.getDate()).padStart(2, "0");
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
|
|
function fromMultiSelectValue(value: string[]) {
|
|
return value.filter((item): item is OperationCategory =>
|
|
operationCategorySet.has(item),
|
|
);
|
|
}
|
|
|
|
const AccountOperationsPageLoaded: FC<AccountOperationsPageLoadedProps> = (
|
|
props,
|
|
) => {
|
|
const {
|
|
canApply,
|
|
draftCategories,
|
|
draftFrom,
|
|
draftTo,
|
|
hasNext,
|
|
hasPrevious,
|
|
items,
|
|
onApply,
|
|
onDraftCategoriesChange,
|
|
onDraftFromChange,
|
|
onDraftToChange,
|
|
onNext,
|
|
onPrevious,
|
|
onReset,
|
|
} = props;
|
|
const { t } = useTranslation();
|
|
|
|
const categoryData = operationCategories.map((category) => ({
|
|
value: category,
|
|
label: t(`ledger.category.${category}`),
|
|
}));
|
|
|
|
return (
|
|
<Stack maw={960} gap="lg">
|
|
<Title order={1}>{t("ledger.title")}</Title>
|
|
{props.refreshing ? (
|
|
<Progress value={100} animated aria-label={t("accounts.refreshing")} />
|
|
) : null}
|
|
{props.refreshError ? (
|
|
<Alert color="yellow">
|
|
{t("accounts.refreshError")}
|
|
<Button variant="subtle" onClick={props.retry}>
|
|
{t("accounts.retry")}
|
|
</Button>
|
|
</Alert>
|
|
) : null}
|
|
<Group align="end">
|
|
<DateInput
|
|
label={t("ledger.from")}
|
|
value={toDateValue(draftFrom)}
|
|
onChange={(value) => onDraftFromChange(fromDateValue(value))}
|
|
maxDate={toDateValue(draftTo) ?? undefined}
|
|
valueFormat="DD.MM.YYYY"
|
|
/>
|
|
<DateInput
|
|
label={t("ledger.to")}
|
|
value={toDateValue(draftTo)}
|
|
onChange={(value) => onDraftToChange(fromDateValue(value))}
|
|
minDate={toDateValue(draftFrom) ?? undefined}
|
|
valueFormat="DD.MM.YYYY"
|
|
/>
|
|
</Group>
|
|
<MultiSelect
|
|
label={t("ledger.categories")}
|
|
placeholder={t("ledger.categoriesPlaceholder")}
|
|
data={categoryData}
|
|
value={draftCategories}
|
|
onChange={(value) =>
|
|
onDraftCategoriesChange(fromMultiSelectValue(value))
|
|
}
|
|
clearable
|
|
/>
|
|
<Group>
|
|
<Button onClick={onApply} disabled={!canApply}>
|
|
{t("ledger.apply")}
|
|
</Button>
|
|
<Button variant="default" onClick={onReset}>
|
|
{t("ledger.reset")}
|
|
</Button>
|
|
</Group>
|
|
{items.length ? (
|
|
items.map((operation) => (
|
|
<LedgerEntryRow
|
|
key={JSON.stringify([
|
|
operation.account?.id,
|
|
operation.id,
|
|
operation.logoName,
|
|
])}
|
|
operation={operation}
|
|
/>
|
|
))
|
|
) : (
|
|
<StateView
|
|
title={t("ledger.emptyTitle")}
|
|
text={t("ledger.emptyText")}
|
|
/>
|
|
)}
|
|
{hasNext || hasPrevious ? (
|
|
<Group justify="space-between">
|
|
<Button disabled={!hasPrevious} onClick={onPrevious}>
|
|
{t("ledger.previous")}
|
|
</Button>
|
|
<Button disabled={!hasNext} onClick={onNext}>
|
|
{t("ledger.next")}
|
|
</Button>
|
|
</Group>
|
|
) : null}
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
export { AccountOperationsPageLoaded };
|