refactor: clarify frontend module ownership
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Group,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
DeleteIcon,
|
||||
EditIcon,
|
||||
OperationIcon,
|
||||
} from "@wealthledger/icons";
|
||||
import { EntryCard, LoadingSkeleton } from "@wealthledger/ui";
|
||||
import { type FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router";
|
||||
|
||||
import {
|
||||
bankOperationAppearance,
|
||||
} from "@/entities/bank-card";
|
||||
|
||||
import type { CardPageVm } from "./CardPage.vm";
|
||||
|
||||
import classes from "./CardPage.module.css";
|
||||
|
||||
type CardHistoryProps = Pick<CardPageVm,
|
||||
"history"
|
||||
| "period"
|
||||
| "rows"
|
||||
>;
|
||||
|
||||
// eslint-disable-next-line sonarjs/function-return-type -- React renders elements, lists and empty output.
|
||||
const CardHistory: FC<CardHistoryProps> = (props) => {
|
||||
const vm = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (vm.rows) {
|
||||
if (vm.rows.length > 0) {
|
||||
return vm.rows.map((row) => {
|
||||
const details = row.otherCard
|
||||
? (
|
||||
<Anchor
|
||||
component={Link}
|
||||
to={`/cards/${row.otherCard}?from=${vm.period.from}&to=${vm.period.to}`}
|
||||
>
|
||||
{t("bank.card")}
|
||||
</Anchor>
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label={`${row.title} ${row.item.date}`}
|
||||
className={classes.operation}
|
||||
key={row.item.id}
|
||||
>
|
||||
<EntryCard
|
||||
actions={(
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<ActionIcon
|
||||
aria-label={t("bank.edit")}
|
||||
disabled={!row.editable}
|
||||
onClick={row.onEdit}
|
||||
size="sm"
|
||||
title={t("bank.edit")}
|
||||
variant="subtle"
|
||||
>
|
||||
<EditIcon size={18} strokeWidth={1.5} />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
aria-label={t("bank.delete")}
|
||||
color="red"
|
||||
disabled={!row.editable}
|
||||
onClick={row.onDelete}
|
||||
size="sm"
|
||||
title={t("bank.delete")}
|
||||
variant="subtle"
|
||||
>
|
||||
<DeleteIcon size={18} strokeWidth={1.5} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
)}
|
||||
color={row.color}
|
||||
details={details}
|
||||
icon={(
|
||||
<OperationIcon
|
||||
{...bankOperationAppearance(
|
||||
row.item.kind,
|
||||
row.item.categoryName,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
initial={row.title.charAt(0)}
|
||||
metadata={row.metadata}
|
||||
title={row.title}
|
||||
tone={row.tone}
|
||||
value={row.value}
|
||||
valueLabel={t(`bank.kinds.${row.item.kind}`)}
|
||||
wrap
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return <Text>{t("bank.noOperations")}</Text>;
|
||||
}
|
||||
|
||||
if (vm.history.loading) {
|
||||
return (
|
||||
<LoadingSkeleton label={t("bank.loadingHistory")}>
|
||||
<Stack>
|
||||
<Skeleton h={90} />
|
||||
|
||||
<Skeleton h={90} />
|
||||
</Stack>
|
||||
</LoadingSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export { CardHistory };
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Button,
|
||||
Group,
|
||||
Pagination,
|
||||
@@ -14,10 +13,9 @@ import {
|
||||
AddIcon,
|
||||
DeleteIcon,
|
||||
EditIcon,
|
||||
OperationIcon,
|
||||
RestoreIcon,
|
||||
} from "@wealthledger/icons";
|
||||
import { EntryCard, LoadingSkeleton, StateView } from "@wealthledger/ui";
|
||||
import { LoadingSkeleton, StateView } from "@wealthledger/ui";
|
||||
import { type FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router";
|
||||
@@ -25,114 +23,18 @@ import { Link } from "react-router";
|
||||
import {
|
||||
BankCardFace,
|
||||
BankFeedback,
|
||||
bankOperationAppearance,
|
||||
BankPeriodPicker,
|
||||
BankSummaryView,
|
||||
} from "@/entities/bank-card";
|
||||
|
||||
import type { CardPageVm } from "./CardPage.vm";
|
||||
|
||||
import { CardHistory } from "./CardHistory.view";
|
||||
import classes from "./CardPage.module.css";
|
||||
import { useCardPagePresentationVm } from "./CardPagePresentation.vm";
|
||||
|
||||
type CardPageViewProps = { vm: CardPageVm };
|
||||
|
||||
// eslint-disable-next-line sonarjs/function-return-type -- React renders elements, lists and empty output.
|
||||
const CardPageViewHistory: FC<CardPageViewProps> = (props) => {
|
||||
const { vm } = props;
|
||||
const { t } = useTranslation();
|
||||
const card = vm.card;
|
||||
|
||||
if (!card) return;
|
||||
|
||||
if (vm.rows) {
|
||||
if (vm.rows.length > 0) {
|
||||
return vm.rows.map((row) => {
|
||||
const details = row.otherCard
|
||||
? (
|
||||
<Anchor
|
||||
component={Link}
|
||||
to={`/cards/${row.otherCard}?from=${vm.period.from}&to=${vm.period.to}`}
|
||||
>
|
||||
{t("bank.card")}
|
||||
</Anchor>
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label={`${row.title} ${row.item.date}`}
|
||||
className={classes.operation}
|
||||
key={row.item.id}
|
||||
>
|
||||
<EntryCard
|
||||
actions={(
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<ActionIcon
|
||||
aria-label={t("bank.edit")}
|
||||
disabled={!row.editable}
|
||||
onClick={row.onEdit}
|
||||
size="sm"
|
||||
title={t("bank.edit")}
|
||||
variant="subtle"
|
||||
>
|
||||
<EditIcon size={18} strokeWidth={1.5} />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
aria-label={t("bank.delete")}
|
||||
color="red"
|
||||
disabled={!row.editable}
|
||||
onClick={row.onDelete}
|
||||
size="sm"
|
||||
title={t("bank.delete")}
|
||||
variant="subtle"
|
||||
>
|
||||
<DeleteIcon size={18} strokeWidth={1.5} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
)}
|
||||
color={row.color}
|
||||
details={details}
|
||||
icon={(
|
||||
<OperationIcon
|
||||
{...bankOperationAppearance(
|
||||
row.item.kind,
|
||||
row.item.categoryName,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
initial={row.title.charAt(0)}
|
||||
metadata={row.metadata}
|
||||
title={row.title}
|
||||
tone={row.tone}
|
||||
value={row.value}
|
||||
valueLabel={t(`bank.kinds.${row.item.kind}`)}
|
||||
wrap
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return <Text>{t("bank.noOperations")}</Text>;
|
||||
}
|
||||
|
||||
if (vm.history.loading) {
|
||||
return (
|
||||
<LoadingSkeleton label={t("bank.loadingHistory")}>
|
||||
<Stack>
|
||||
<Skeleton h={90} />
|
||||
|
||||
<Skeleton h={90} />
|
||||
</Stack>
|
||||
</LoadingSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const CardPageView: FC<CardPageViewProps> = (props) => {
|
||||
const { vm } = props;
|
||||
|
||||
@@ -291,7 +193,7 @@ const CardPageView: FC<CardPageViewProps> = (props) => {
|
||||
gap="lg"
|
||||
key={`${vm.period.from}-${vm.period.to}-${vm.pageSize}-${vm.history.page ?? "loading"}`}
|
||||
>
|
||||
<CardPageViewHistory {...props} />
|
||||
<CardHistory history={vm.history} period={vm.period} rows={vm.rows} />
|
||||
</Stack>
|
||||
|
||||
{vm.history.error && (
|
||||
|
||||
@@ -6,7 +6,13 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { CardPageVm } from "./CardPage.vm";
|
||||
|
||||
function useCardPagePresentationVm(vm: CardPageVm) {
|
||||
function useCardPagePresentationVm(vm: Pick<CardPageVm,
|
||||
"card"
|
||||
| "history"
|
||||
| "pageSize"
|
||||
| "period"
|
||||
| "setPage"
|
||||
>) {
|
||||
const { t } = useTranslation();
|
||||
const card = vm.card;
|
||||
const paginationRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { DateInput } from "@mantine/dates";
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
import { useController, useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -10,10 +7,9 @@ import type {
|
||||
} from "./OperationForm.types";
|
||||
|
||||
function useOperationFieldsVm(props: Pick<OperationFieldsProps,
|
||||
"dateInputValue"
|
||||
"bindDate"
|
||||
| "kind"
|
||||
| "refundCardId"
|
||||
| "validDate"
|
||||
>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -48,38 +44,13 @@ function useOperationFieldsVm(props: Pick<OperationFieldsProps,
|
||||
props.kind === "transfer" ? "bank.source" : "bank.operationCard",
|
||||
);
|
||||
|
||||
const dateParser: ComponentProps<typeof DateInput>["dateParser"] = value =>
|
||||
props.validDate(props.dateInputValue(value))
|
||||
? props.dateInputValue(value)
|
||||
: null;
|
||||
|
||||
const onDateBlur: ComponentProps<typeof DateInput>["onBlur"] = (event) => {
|
||||
dateField.onChange(props.dateInputValue(event.currentTarget.value));
|
||||
dateField.onBlur();
|
||||
};
|
||||
|
||||
const onDateChange: (value: null | string) => void = value =>
|
||||
dateField.onChange(value ?? "");
|
||||
|
||||
const onDateInput: ComponentProps<typeof DateInput>["onInput"] = event =>
|
||||
dateField.onChange(props.dateInputValue(event.currentTarget.value));
|
||||
|
||||
const dateValue = props.validDate(dateField.value)
|
||||
? dateField.value
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
cardIdField,
|
||||
categoryIdField,
|
||||
dateField,
|
||||
dateParser,
|
||||
dateValue,
|
||||
date: { ...dateField, ...props.bindDate(dateField) },
|
||||
errors,
|
||||
isSubmitting,
|
||||
kindField,
|
||||
onDateBlur,
|
||||
onDateChange,
|
||||
onDateInput,
|
||||
onKindChange,
|
||||
register,
|
||||
sourceCardLabel,
|
||||
|
||||
@@ -43,9 +43,14 @@ const OperationExample: FC<ExampleProps> = (props) => {
|
||||
)
|
||||
}
|
||||
fields={{
|
||||
bindDate: field => ({
|
||||
...field,
|
||||
dateParser: () => "2026-08-01",
|
||||
onChange: value => field.onChange(value ?? ""),
|
||||
onInput: () => {},
|
||||
}),
|
||||
cards: [{ label: "Учебная карта", value: "demo" }],
|
||||
categories: [],
|
||||
dateInputValue: () => "2026-08-01",
|
||||
defaultCategory: "",
|
||||
editing: true,
|
||||
kind,
|
||||
@@ -53,7 +58,6 @@ const OperationExample: FC<ExampleProps> = (props) => {
|
||||
maxDate: () => "2026-08-27",
|
||||
refundCardId: "demo",
|
||||
targets: [],
|
||||
validDate: value => value === "2026-08-01",
|
||||
}}
|
||||
latest={
|
||||
props.conflict
|
||||
@@ -107,7 +111,7 @@ const meta = {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Synthetic presentation без API/VM. Date callbacks фиксируют fixture 01.08.2026; денежная validation, recovery и nested category flow проверяются application tests. Actions только локальные.",
|
||||
"Synthetic presentation без API/VM. денежная validation, recovery и nested category flow проверяются application tests. Actions только локальные.",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import type { BankCard, BankOperation } from "@/entities/bank-card";
|
||||
import type {
|
||||
BankCard,
|
||||
bankDateField,
|
||||
BankOperation,
|
||||
} from "@/entities/bank-card";
|
||||
|
||||
type Choice = { label: string; value: string };
|
||||
|
||||
type OperationFieldsProps = {
|
||||
bindDate: typeof bankDateField;
|
||||
cards: Choice[];
|
||||
categories: Choice[];
|
||||
dateInputValue: (value: string) => string;
|
||||
defaultCategory: string;
|
||||
editing: boolean;
|
||||
kind: OperationValues["kind"];
|
||||
@@ -13,7 +17,6 @@ type OperationFieldsProps = {
|
||||
maxDate: () => string;
|
||||
refundCardId: string;
|
||||
targets: Choice[];
|
||||
validDate: (value: string) => boolean;
|
||||
};
|
||||
|
||||
type OperationFormProps = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { FC, ReactNode, SubmitEventHandler } from "react";
|
||||
|
||||
import { Button,
|
||||
import {
|
||||
Button,
|
||||
Fieldset,
|
||||
Group,
|
||||
Modal,
|
||||
@@ -26,15 +27,10 @@ const OperationFields: FC<OperationFieldsProps> = (props) => {
|
||||
const {
|
||||
cardIdField,
|
||||
categoryIdField,
|
||||
dateField,
|
||||
dateParser,
|
||||
dateValue,
|
||||
date,
|
||||
errors,
|
||||
isSubmitting,
|
||||
kindField,
|
||||
onDateBlur,
|
||||
onDateChange,
|
||||
onDateInput,
|
||||
onKindChange,
|
||||
register,
|
||||
sourceCardLabel,
|
||||
@@ -82,21 +78,16 @@ const OperationFields: FC<OperationFieldsProps> = (props) => {
|
||||
/>
|
||||
|
||||
<DateInput
|
||||
{...dateField}
|
||||
dateParser={dateParser}
|
||||
{...date}
|
||||
error={errors.date?.message}
|
||||
fixOnBlur={false}
|
||||
label={t("bank.date")}
|
||||
maxDate={props.maxDate()}
|
||||
onBlur={onDateBlur}
|
||||
onChange={onDateChange}
|
||||
onInput={onDateInput}
|
||||
popoverProps={{
|
||||
middlewares: { shift: { crossAxis: true } },
|
||||
withinPortal: false,
|
||||
}}
|
||||
required
|
||||
value={dateValue}
|
||||
valueFormat="DD.MM.YYYY"
|
||||
/>
|
||||
|
||||
|
||||
@@ -4,14 +4,12 @@ import { useForm, useWatch } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
bankDateInputValue,
|
||||
bankDateSchema,
|
||||
bankDateField,
|
||||
bankMoneyValue,
|
||||
type BankOperation,
|
||||
bankOperationKinds,
|
||||
bankToday,
|
||||
useBankCategories,
|
||||
useBankExpenses,
|
||||
useBankFormFocus,
|
||||
useBankOperation,
|
||||
useCreateBankOperation,
|
||||
@@ -25,6 +23,7 @@ import type {
|
||||
} from "./OperationForm.types";
|
||||
|
||||
import { createOperationSchema } from "./OperationForm.schema";
|
||||
import { useRefundPurchase } from "./useRefundPurchase";
|
||||
|
||||
const useOperationFormVm = (props: OperationFormProps) => {
|
||||
useBankFormFocus();
|
||||
@@ -33,7 +32,6 @@ const useOperationFormVm = (props: OperationFormProps) => {
|
||||
const [categoriesOpen, setCategoriesOpen] = useState(false);
|
||||
const [version, setVersion] = useState(props.operation?.version ?? 1);
|
||||
const [latest, setLatest] = useState<BankOperation>();
|
||||
const [sourcePage, setSourcePage] = useState(1);
|
||||
|
||||
const currentQuery = useBankOperation(props.operation?.id);
|
||||
|
||||
@@ -72,20 +70,14 @@ const useOperationFormVm = (props: OperationFormProps) => {
|
||||
)(values, context, options),
|
||||
});
|
||||
|
||||
const sourceId = useWatch({
|
||||
control: methods.control,
|
||||
name: "originalExpenseId",
|
||||
});
|
||||
|
||||
const sourceQuery = useBankOperation(sourceId || undefined);
|
||||
|
||||
const kind = useWatch({ control: methods.control, name: "kind" });
|
||||
|
||||
const expenses = useBankExpenses(
|
||||
kind === "refund" ? props.card.id : undefined,
|
||||
{ page: sourcePage },
|
||||
const purchase = useRefundPurchase(
|
||||
methods.control, props.card.id, kind, Boolean(props.operation),
|
||||
);
|
||||
|
||||
const { sourceId, sourceQuery } = purchase;
|
||||
|
||||
const source = useWatch({ control: methods.control, name: "cardId" });
|
||||
const isPending = !opened || methods.formState.isSubmitting;
|
||||
|
||||
@@ -149,23 +141,6 @@ const useOperationFormVm = (props: OperationFormProps) => {
|
||||
else setError(result.error);
|
||||
};
|
||||
|
||||
const expenseChoice = (item: BankOperation) => ({
|
||||
label: t("bank.expenseChoice", {
|
||||
amount: formatMoney(item.amount),
|
||||
available: formatMoney(item.availableRefund),
|
||||
date: formatDate(item.date),
|
||||
name: item.description || item.categoryName,
|
||||
}),
|
||||
value: item.id,
|
||||
});
|
||||
|
||||
const expenseOptions
|
||||
= expenses.data?.items.map(element => expenseChoice(element)) ?? [];
|
||||
|
||||
const selected = sourceQuery.data
|
||||
? expenseChoice(sourceQuery.data)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
categoriesError: categories.isError
|
||||
? categories.error
|
||||
@@ -173,16 +148,14 @@ const useOperationFormVm = (props: OperationFormProps) => {
|
||||
categoriesOpen,
|
||||
closeCategories: () => setCategoriesOpen(false),
|
||||
error,
|
||||
expensesError: expenses.isError
|
||||
? expenses.error
|
||||
: null,
|
||||
expensesError: purchase.error,
|
||||
methods,
|
||||
onCategoryCreated: (id: string) => {
|
||||
methods.setValue("categoryId", id);
|
||||
setCategoriesOpen(false);
|
||||
},
|
||||
retryCategories: () => void categories.refetch(),
|
||||
retryExpenses: () => void expenses.refetch(),
|
||||
retryExpenses: purchase.retry,
|
||||
retrySource: () => void sourceQuery.refetch(),
|
||||
sourceError: sourceQuery.isError
|
||||
? sourceQuery.error
|
||||
@@ -190,14 +163,12 @@ const useOperationFormVm = (props: OperationFormProps) => {
|
||||
view: {
|
||||
categoriesOpen,
|
||||
editing: Boolean(props.operation),
|
||||
emptyExpenses: Boolean(
|
||||
expenses.data && expenses.data.items.length === 0 && !props.operation,
|
||||
),
|
||||
emptyExpenses: purchase.empty,
|
||||
fields: {
|
||||
bindDate: bankDateField,
|
||||
cards: options,
|
||||
categories:
|
||||
categories.data?.map(c => ({ label: c.name, value: c.id })) ?? [],
|
||||
dateInputValue: bankDateInputValue,
|
||||
defaultCategory: categories.data?.find(c => c.isDefault)?.id ?? "",
|
||||
editing: Boolean(props.operation),
|
||||
kind,
|
||||
@@ -208,7 +179,6 @@ const useOperationFormVm = (props: OperationFormProps) => {
|
||||
maxDate: bankToday,
|
||||
refundCardId: props.card.id,
|
||||
targets: options.filter(c => c.value !== source),
|
||||
validDate: (value: string) => bankDateSchema.safeParse(value).success,
|
||||
},
|
||||
latest: latest
|
||||
? {
|
||||
@@ -221,26 +191,22 @@ const useOperationFormVm = (props: OperationFormProps) => {
|
||||
version: latest.version,
|
||||
}
|
||||
: undefined,
|
||||
loadingExpenses: expenses.isLoading,
|
||||
nextDisabled: !expenses.data?.hasNext || expenses.isFetching,
|
||||
loadingExpenses: purchase.loading,
|
||||
nextDisabled: purchase.nextDisabled,
|
||||
onAddCategory: () => setCategoriesOpen(true),
|
||||
onCancel: () => setOpened(false),
|
||||
onClose: () => !isPending && !categoriesOpen && setOpened(false),
|
||||
onExitTransitionEnd: props.onClose,
|
||||
onNext: () => setSourcePage(sourcePage + 1),
|
||||
onPrevious: () => setSourcePage(sourcePage - 1),
|
||||
onNext: purchase.onNext,
|
||||
onPrevious: purchase.onPrevious,
|
||||
onReload: error && (sourceId || props.operation) ? onReload : undefined,
|
||||
onSubmit,
|
||||
opened,
|
||||
pending: isPending,
|
||||
previousDisabled: sourcePage === 1 || expenses.isFetching,
|
||||
previousDisabled: purchase.previousDisabled,
|
||||
refund: {
|
||||
editing: Boolean(props.operation),
|
||||
expenses:
|
||||
selected
|
||||
&& expenseOptions.every(item => item.value !== selected.value)
|
||||
? [selected, ...expenseOptions]
|
||||
: expenseOptions,
|
||||
expenses: purchase.options,
|
||||
limit: props.operation
|
||||
? refundLimit
|
||||
: sourceQuery.data?.availableRefund,
|
||||
@@ -248,7 +214,7 @@ const useOperationFormVm = (props: OperationFormProps) => {
|
||||
saveDisabled:
|
||||
(kind === "expense" && !categories.data)
|
||||
|| (kind === "refund" && !sourceQuery.data),
|
||||
sourcePage,
|
||||
sourcePage: purchase.page,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { Control } from "react-hook-form";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useWatch } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
type BankOperation,
|
||||
useBankExpenses,
|
||||
useBankOperation,
|
||||
} from "@/entities/bank-card";
|
||||
import { formatDate, formatMoney } from "@/shared/lib";
|
||||
|
||||
import type { OperationValues } from "./OperationForm.types";
|
||||
|
||||
function useRefundPurchase(
|
||||
control: Control<OperationValues>,
|
||||
cardId: string,
|
||||
kind: OperationValues["kind"],
|
||||
isEditing: boolean,
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
const [page, setPage] = useState(1);
|
||||
const sourceId = useWatch({ control, name: "originalExpenseId" });
|
||||
const sourceQuery = useBankOperation(sourceId || undefined);
|
||||
const expenses = useBankExpenses(kind === "refund" ? cardId : undefined, { page });
|
||||
|
||||
const choice = (item: BankOperation) => ({
|
||||
label: t("bank.expenseChoice", {
|
||||
amount: formatMoney(item.amount),
|
||||
available: formatMoney(item.availableRefund),
|
||||
date: formatDate(item.date),
|
||||
name: item.description || item.categoryName,
|
||||
}),
|
||||
value: item.id,
|
||||
});
|
||||
|
||||
const options = expenses.data?.items.map(item => choice(item)) ?? [];
|
||||
const selected = sourceQuery.data ? choice(sourceQuery.data) : undefined;
|
||||
|
||||
return {
|
||||
empty: Boolean(
|
||||
!isEditing && expenses.data && expenses.data.items.length === 0,
|
||||
),
|
||||
error: expenses.isError ? expenses.error : null,
|
||||
loading: expenses.isLoading,
|
||||
nextDisabled: !expenses.data?.hasNext || expenses.isFetching,
|
||||
onNext: () => setPage(page + 1),
|
||||
onPrevious: () => setPage(page - 1),
|
||||
options: selected && options.every(item => item.value !== selected.value)
|
||||
? [selected, ...options]
|
||||
: options,
|
||||
page,
|
||||
previousDisabled: page === 1 || expenses.isFetching,
|
||||
retry: () => void expenses.refetch(),
|
||||
sourceId,
|
||||
sourceQuery,
|
||||
};
|
||||
}
|
||||
|
||||
export { useRefundPurchase };
|
||||
Reference in New Issue
Block a user