refactor: enforce frontend view model boundaries
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
import { DateInput } from "@mantine/dates";
|
||||
import { useController, useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { bankDateInputValue } from "../../model/form-validation";
|
||||
import { type BankCardValues, bankDateSchema } from "../../model/form.schema";
|
||||
|
||||
function useBankCardFieldsVm() {
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
register,
|
||||
} = useFormContext<BankCardValues>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { field: paymentSystemField } = useController({
|
||||
control,
|
||||
name: "paymentSystem",
|
||||
});
|
||||
|
||||
const { field: openingDateField } = useController({
|
||||
control,
|
||||
name: "openingDate",
|
||||
});
|
||||
|
||||
const paymentSystems = ["mir", "visa", "mastercard"].map(value => ({
|
||||
label: t(`bank.paymentSystems.${value}`),
|
||||
value,
|
||||
}));
|
||||
|
||||
const onPaymentSystemChange: (value: null | string) => void = value =>
|
||||
paymentSystemField.onChange(value ?? "");
|
||||
|
||||
const dateParser: ComponentProps<typeof DateInput>["dateParser"] = value =>
|
||||
bankDateSchema.safeParse(bankDateInputValue(value)).success
|
||||
? bankDateInputValue(value)
|
||||
: null; // eslint-disable-line unicorn/no-null -- Mantine represents an empty controlled openingDateField as null.
|
||||
|
||||
const onOpeningDateBlur: ComponentProps<typeof DateInput>["onBlur"] = (
|
||||
event,
|
||||
) => {
|
||||
openingDateField.onChange(bankDateInputValue(event.currentTarget.value));
|
||||
openingDateField.onBlur();
|
||||
};
|
||||
|
||||
const onOpeningDateChange: (value: null | string) => void = value =>
|
||||
openingDateField.onChange(value ?? "");
|
||||
|
||||
const onOpeningDateInput: ComponentProps<typeof DateInput>["onInput"] = event =>
|
||||
openingDateField.onChange(bankDateInputValue(event.currentTarget.value));
|
||||
|
||||
const openingDateValue = bankDateSchema.safeParse(openingDateField.value)
|
||||
.success
|
||||
? openingDateField.value
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
dateParser,
|
||||
errors,
|
||||
onOpeningDateBlur,
|
||||
onOpeningDateChange,
|
||||
onOpeningDateInput,
|
||||
onPaymentSystemChange,
|
||||
openingDateField,
|
||||
openingDateValue,
|
||||
paymentSystemField,
|
||||
paymentSystems,
|
||||
register,
|
||||
t,
|
||||
};
|
||||
}
|
||||
|
||||
export { useBankCardFieldsVm };
|
||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
|
||||
import type { BankCardValues } from "../../model/form-validation";
|
||||
import type { BankCardValues } from "../../model/form.schema";
|
||||
|
||||
import { BankCardFields } from "./BankCardForm.view";
|
||||
|
||||
@@ -20,6 +20,7 @@ const meta = {
|
||||
openingDate: "2026-08-01",
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<Story />
|
||||
@@ -38,8 +39,11 @@ const meta = {
|
||||
tags: ["autodocs"],
|
||||
title: "Product/BankCards/CardFields",
|
||||
} satisfies Meta<typeof BankCardFields>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Active: Story = { name: "Независимая карта в рублях" };
|
||||
export const Archived: Story = {
|
||||
args: { archived: true },
|
||||
|
||||
@@ -12,8 +12,10 @@ import type { BankCard } from "../../api/queries";
|
||||
|
||||
import { BankCardForm } from "./BankCardForm";
|
||||
|
||||
const card: BankCard = (/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
|
||||
{
|
||||
/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
|
||||
|
||||
const card: BankCard
|
||||
= {
|
||||
archivedAt: null,
|
||||
balance: { currency: "RUB", value: "50" },
|
||||
bankName: "Банк",
|
||||
@@ -24,8 +26,9 @@ const card: BankCard = (/* eslint-disable unicorn/no-null -- Fixture preserves t
|
||||
openingDate: "2026-01-01",
|
||||
paymentSystem: null,
|
||||
version: 1,
|
||||
}
|
||||
/* eslint-enable unicorn/no-null */);
|
||||
};
|
||||
|
||||
/* eslint-enable unicorn/no-null */
|
||||
function setup(value: BankCard) {
|
||||
configureAuthClient({
|
||||
accessToken: () => "test",
|
||||
@@ -41,8 +44,10 @@ function setup(value: BankCard) {
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
return close;
|
||||
}
|
||||
|
||||
describe("BankCardForm", () => {
|
||||
it("waits for the exit transition before notifying the parent", async () => {
|
||||
const close = setup(card);
|
||||
@@ -64,20 +69,23 @@ describe("BankCardForm", () => {
|
||||
server.use(
|
||||
http.patch("*/api/v1/bank-cards/a", async ({ request }) => {
|
||||
body = await request.json();
|
||||
|
||||
return HttpResponse.json(card);
|
||||
}),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
|
||||
await waitFor(() => expect(close).toHaveBeenCalled());
|
||||
expect(body).toEqual((/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
|
||||
/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
|
||||
expect(body).toEqual(
|
||||
{
|
||||
bankName: null,
|
||||
lastFour: null,
|
||||
name: "Основная",
|
||||
paymentSystem: null,
|
||||
version: 1,
|
||||
}
|
||||
/* eslint-enable unicorn/no-null */));
|
||||
},
|
||||
/* eslint-enable unicorn/no-null */
|
||||
);
|
||||
});
|
||||
it("loads and saves the selected payment system", async () => {
|
||||
const close = setup({ ...card, paymentSystem: "mir" });
|
||||
@@ -92,6 +100,7 @@ describe("BankCardForm", () => {
|
||||
server.use(
|
||||
http.patch("*/api/v1/bank-cards/a", async ({ request }) => {
|
||||
body = await request.json();
|
||||
|
||||
return HttpResponse.json({ ...card, paymentSystem: "visa" });
|
||||
}),
|
||||
);
|
||||
@@ -132,6 +141,7 @@ describe("BankCardForm", () => {
|
||||
const body = (await request.json()) as Record<string, unknown>;
|
||||
bodies.push(body);
|
||||
version = 2;
|
||||
|
||||
return body.version === 1
|
||||
? HttpResponse.json(
|
||||
{ code: "BANK_VERSION_CONFLICT" },
|
||||
@@ -146,9 +156,11 @@ describe("BankCardForm", () => {
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
|
||||
await screen.findByText(/Запись уже изменена/);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Перечитать запись" }));
|
||||
|
||||
const adopt = await screen.findByRole("button", {
|
||||
name: "Использовать актуальную версию 2",
|
||||
});
|
||||
|
||||
expect(screen.getByLabelText(/Название карты/)).toHaveValue("Мой черновик");
|
||||
fireEvent.click(adopt);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
|
||||
|
||||
@@ -1,121 +1,61 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Button, Group, Modal, Stack, Text } from "@mantine/core";
|
||||
import { type FC, useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type FC } from "react";
|
||||
import { FormProvider } from "react-hook-form";
|
||||
|
||||
import { formatDate } from "@/shared/lib";
|
||||
|
||||
import { useBankMutations } from "../../api/mutations";
|
||||
import { type BankCard, useBankCard } from "../../api/queries";
|
||||
import {
|
||||
bankCardSchema,
|
||||
type BankCardValues,
|
||||
bankMoneyValue,
|
||||
bankToday,
|
||||
} from "../../model/form-validation";
|
||||
import { type BankCard } from "../../api/queries";
|
||||
import { BankFeedback } from "../BankFeedback";
|
||||
import classes from "../BankForms.module.css";
|
||||
import { useBankFormFocus } from "../useBankFormFocus";
|
||||
import { BankCardFields } from "./BankCardForm.view";
|
||||
import { useBankCardFormVm } from "./BankCardForm.vm";
|
||||
|
||||
type BankCardFormProps = { card?: BankCard; onClose: () => void };
|
||||
|
||||
const BankCardForm: FC<BankCardFormProps> = (props) => {
|
||||
useBankFormFocus();
|
||||
const [opened, setOpened] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
const [intent] = useState(() => crypto.randomUUID());
|
||||
const [version, setVersion] = useState(props.card?.version ?? 1);
|
||||
const [error, setError] = useState<unknown>();
|
||||
const [latest, setLatest] = useState<BankCard>();
|
||||
const fresh = useBankCard(props.card?.id);
|
||||
const mutation = useBankMutations();
|
||||
const methods = useForm<BankCardValues>({
|
||||
defaultValues: {
|
||||
bankName: props.card?.bankName ?? "",
|
||||
lastFour: props.card?.lastFour ?? "",
|
||||
name: props.card?.name ?? "",
|
||||
openingBalance: props.card?.openingBalance.value ?? "0",
|
||||
openingDate: props.card?.openingDate ?? bankToday(),
|
||||
paymentSystem: props.card?.paymentSystem ?? "",
|
||||
},
|
||||
resolver: zodResolver(bankCardSchema),
|
||||
});
|
||||
const isPending
|
||||
= !opened
|
||||
|| methods.formState.isSubmitting
|
||||
|| (fresh.isFetching && Boolean(error));
|
||||
const {
|
||||
dialogTitle,
|
||||
error,
|
||||
isPending,
|
||||
latest,
|
||||
methods,
|
||||
onAdoptVersion,
|
||||
onCancel,
|
||||
onReloadRecord,
|
||||
onRequestClose,
|
||||
onSubmit,
|
||||
opened,
|
||||
t,
|
||||
} = useBankCardFormVm(props);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
closeButtonProps={{ "aria-label": t("bank.close"), "disabled": isPending }}
|
||||
closeOnClickOutside={!isPending}
|
||||
closeOnEscape={!isPending}
|
||||
onClose={() => !isPending && setOpened(false)}
|
||||
onClose={onRequestClose}
|
||||
onExitTransitionEnd={props.onClose}
|
||||
opened={opened}
|
||||
title={t(props.card ? "bank.editCard" : "bank.addCard")}
|
||||
title={dialogTitle}
|
||||
>
|
||||
<FormProvider {...methods}>
|
||||
<form
|
||||
onSubmit={methods.handleSubmit(async (values) => {
|
||||
setError(undefined);
|
||||
try {
|
||||
const metadata = {
|
||||
bankName: values.bankName || /* eslint-disable-line unicorn/no-null -- PATCH uses null to clear these optional card fields. */ null,
|
||||
lastFour: values.lastFour || /* eslint-disable-line unicorn/no-null -- PATCH uses null to clear these optional card fields. */ null,
|
||||
name: values.name,
|
||||
paymentSystem: values.paymentSystem || /* eslint-disable-line unicorn/no-null -- PATCH uses null to clear these optional card fields. */ null,
|
||||
};
|
||||
const financial = {
|
||||
openingBalance: {
|
||||
currency: "RUB" as const,
|
||||
value: bankMoneyValue(values.openingBalance),
|
||||
},
|
||||
openingDate: values.openingDate,
|
||||
};
|
||||
if (props.card)
|
||||
await mutation.updateCard.mutateAsync({
|
||||
body: {
|
||||
...metadata,
|
||||
...(!props.card.archivedAt && financial),
|
||||
version,
|
||||
},
|
||||
id: props.card.id,
|
||||
});
|
||||
else
|
||||
await mutation.createCard.mutateAsync({
|
||||
id: intent,
|
||||
...metadata,
|
||||
...financial,
|
||||
});
|
||||
setOpened(false);
|
||||
}
|
||||
catch (error_) {
|
||||
setError(error_);
|
||||
}
|
||||
})}
|
||||
>
|
||||
<form onSubmit={onSubmit}>
|
||||
<fieldset className={classes.fieldset} disabled={isPending}>
|
||||
<Stack>
|
||||
<BankCardFields archived={Boolean(props.card?.archivedAt)} />
|
||||
{!!(error) && ((
|
||||
|
||||
{!!error && (
|
||||
<>
|
||||
<BankFeedback error={error} />
|
||||
|
||||
{props.card && (
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const response = await fresh.refetch();
|
||||
if (response.data && !response.error)
|
||||
setLatest(response.data);
|
||||
else setError(response.error);
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<Button onClick={onReloadRecord} variant="default">
|
||||
{t("bank.reloadRecord")}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
)}
|
||||
|
||||
{latest && (
|
||||
<>
|
||||
<Text>
|
||||
@@ -123,23 +63,19 @@ const BankCardForm: FC<BankCardFormProps> = (props) => {
|
||||
details: `${latest.name} · ${latest.openingBalance.value} RUB · ${formatDate(latest.openingDate)}`,
|
||||
})}
|
||||
</Text>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setVersion(latest.version);
|
||||
setLatest(undefined);
|
||||
setError(undefined);
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
|
||||
<Button onClick={onAdoptVersion} variant="default">
|
||||
{t("bank.adoptVersion", { version: latest.version })}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Group>
|
||||
<Button loading={isPending} type="submit">
|
||||
{t("bank.save")}
|
||||
</Button>
|
||||
<Button onClick={() => setOpened(false)} variant="default">
|
||||
|
||||
<Button onClick={onCancel} variant="default">
|
||||
{t("bank.cancel")}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -2,24 +2,28 @@ import type { FC } from "react";
|
||||
|
||||
import { Select, Stack, Text, TextInput } from "@mantine/core";
|
||||
import { DateInput } from "@mantine/dates";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
type BankCardValues,
|
||||
bankDateInputValue,
|
||||
bankDateSchema,
|
||||
bankToday,
|
||||
} from "../../model/form-validation";
|
||||
import { bankToday } from "../../model/form-validation";
|
||||
import { useBankCardFieldsVm } from "./BankCardFields.vm";
|
||||
|
||||
type BankCardFieldsProps = { archived: boolean };
|
||||
|
||||
const BankCardFields: FC<BankCardFieldsProps> = (props) => {
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
dateParser,
|
||||
errors,
|
||||
onOpeningDateBlur,
|
||||
onOpeningDateChange,
|
||||
onOpeningDateInput,
|
||||
onPaymentSystemChange,
|
||||
openingDateField,
|
||||
openingDateValue,
|
||||
paymentSystemField,
|
||||
paymentSystems,
|
||||
register,
|
||||
} = useFormContext<BankCardValues>();
|
||||
const { t } = useTranslation();
|
||||
t,
|
||||
} = useBankCardFieldsVm();
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<TextInput
|
||||
@@ -28,37 +32,36 @@ const BankCardFields: FC<BankCardFieldsProps> = (props) => {
|
||||
{...register("name")}
|
||||
error={errors.name?.message}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label={t("bank.bankName")}
|
||||
{...register("bankName")}
|
||||
error={errors.bankName?.message}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
inputMode="numeric"
|
||||
label={t("bank.lastFour")}
|
||||
{...register("lastFour")}
|
||||
error={errors.lastFour?.message}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="paymentSystem"
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
clearable
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
data={["mir", "visa", "mastercard"].map(value => ({
|
||||
label: t(`bank.paymentSystems.${value}`),
|
||||
value,
|
||||
}))}
|
||||
error={errors.paymentSystem?.message}
|
||||
label={t("bank.paymentSystem")}
|
||||
onChange={value => field.onChange(value ?? "")}
|
||||
placeholder={t("bank.paymentSystemUnknown")}
|
||||
value={field.value || /* eslint-disable-line unicorn/no-null -- Mantine represents an empty controlled field as null. */ null}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Select
|
||||
{...paymentSystemField}
|
||||
clearable
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
data={paymentSystems}
|
||||
error={errors.paymentSystem?.message}
|
||||
label={t("bank.paymentSystem")}
|
||||
onChange={onPaymentSystemChange}
|
||||
placeholder={t("bank.paymentSystemUnknown")}
|
||||
value={
|
||||
paymentSystemField.value
|
||||
// eslint-disable-next-line unicorn/no-null -- Mantine represents an empty controlled paymentSystemField as null.
|
||||
|| null
|
||||
}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
inputMode="decimal"
|
||||
label={t("bank.openingBalance")}
|
||||
@@ -67,43 +70,31 @@ const BankCardFields: FC<BankCardFieldsProps> = (props) => {
|
||||
{...register("openingBalance")}
|
||||
error={errors.openingBalance?.message}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="openingDate"
|
||||
render={({ field }) => (
|
||||
<DateInput
|
||||
{...field}
|
||||
dateParser={value =>
|
||||
bankDateSchema.safeParse(bankDateInputValue(value)).success
|
||||
? bankDateInputValue(value)
|
||||
: /* eslint-disable-line unicorn/no-null -- Mantine represents an empty controlled field as null. */ null}
|
||||
error={errors.openingDate?.message}
|
||||
fixOnBlur={false}
|
||||
label={t("bank.openingDate")}
|
||||
maxDate={bankToday()}
|
||||
onBlur={(event) => {
|
||||
field.onChange(bankDateInputValue(event.currentTarget.value));
|
||||
field.onBlur();
|
||||
}}
|
||||
onChange={value => field.onChange(value ?? "")}
|
||||
onInput={event =>
|
||||
field.onChange(bankDateInputValue(event.currentTarget.value))}
|
||||
popoverProps={{
|
||||
middlewares: { shift: { crossAxis: true } },
|
||||
withinPortal: false,
|
||||
}}
|
||||
readOnly={props.archived}
|
||||
required
|
||||
value={
|
||||
(bankDateSchema.safeParse(field.value).success) ? (field.value) : undefined
|
||||
}
|
||||
valueFormat="DD.MM.YYYY"
|
||||
/>
|
||||
)}
|
||||
|
||||
<DateInput
|
||||
{...openingDateField}
|
||||
dateParser={dateParser}
|
||||
error={errors.openingDate?.message}
|
||||
fixOnBlur={false}
|
||||
label={t("bank.openingDate")}
|
||||
maxDate={bankToday()}
|
||||
onBlur={onOpeningDateBlur}
|
||||
onChange={onOpeningDateChange}
|
||||
onInput={onOpeningDateInput}
|
||||
popoverProps={{
|
||||
middlewares: { shift: { crossAxis: true } },
|
||||
withinPortal: false,
|
||||
}}
|
||||
readOnly={props.archived}
|
||||
required
|
||||
value={openingDateValue}
|
||||
valueFormat="DD.MM.YYYY"
|
||||
/>
|
||||
|
||||
<Text c="dimmed" size="sm">
|
||||
{t("bank.openingHint")}
|
||||
</Text>
|
||||
|
||||
{props.archived && <Text>{t("bank.archiveHint")}</Text>}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useBankMutations } from "../../api/mutations";
|
||||
import { type BankCard, useBankCard } from "../../api/queries";
|
||||
import { bankMoneyValue, bankToday } from "../../model/form-validation";
|
||||
import { bankCardSchema, type BankCardValues } from "../../model/form.schema";
|
||||
import { useBankFormFocus } from "../useBankFormFocus";
|
||||
|
||||
type BankCardFormProps = { card?: BankCard; onClose: () => void };
|
||||
|
||||
function useBankCardFormVm(props: BankCardFormProps) {
|
||||
useBankFormFocus();
|
||||
const [opened, setOpened] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
const [intent] = useState(() => crypto.randomUUID());
|
||||
const [version, setVersion] = useState(props.card?.version ?? 1);
|
||||
const [error, setError] = useState<unknown>();
|
||||
const [latest, setLatest] = useState<BankCard>();
|
||||
const fresh = useBankCard(props.card?.id);
|
||||
const mutation = useBankMutations();
|
||||
|
||||
const methods = useForm<BankCardValues>({
|
||||
defaultValues: {
|
||||
bankName: props.card?.bankName ?? "",
|
||||
lastFour: props.card?.lastFour ?? "",
|
||||
name: props.card?.name ?? "",
|
||||
openingBalance: props.card?.openingBalance.value ?? "0",
|
||||
openingDate: props.card?.openingDate ?? bankToday(),
|
||||
paymentSystem: props.card?.paymentSystem ?? "",
|
||||
},
|
||||
resolver: zodResolver(bankCardSchema),
|
||||
});
|
||||
|
||||
const isPending
|
||||
= !opened
|
||||
|| methods.formState.isSubmitting
|
||||
|| (fresh.isFetching && Boolean(error));
|
||||
|
||||
const onRequestClose = () => !isPending && setOpened(false);
|
||||
const dialogTitle = t(props.card ? "bank.editCard" : "bank.addCard");
|
||||
|
||||
const onSubmit = methods.handleSubmit(async (values) => {
|
||||
setError(undefined);
|
||||
|
||||
try {
|
||||
const metadata = {
|
||||
bankName:
|
||||
values.bankName
|
||||
// eslint-disable-next-line unicorn/no-null -- PATCH uses null to clear these optional card fields.
|
||||
|| null,
|
||||
lastFour:
|
||||
values.lastFour
|
||||
// eslint-disable-next-line unicorn/no-null -- PATCH uses null to clear these optional card fields.
|
||||
|| null,
|
||||
name: values.name,
|
||||
paymentSystem:
|
||||
values.paymentSystem
|
||||
// eslint-disable-next-line unicorn/no-null -- PATCH uses null to clear these optional card fields.
|
||||
|| null,
|
||||
};
|
||||
|
||||
const financial = {
|
||||
openingBalance: {
|
||||
currency: "RUB" as const,
|
||||
value: bankMoneyValue(values.openingBalance),
|
||||
},
|
||||
openingDate: values.openingDate,
|
||||
};
|
||||
|
||||
if (props.card)
|
||||
await mutation.updateCard.mutateAsync({
|
||||
body: {
|
||||
...metadata,
|
||||
...(!props.card.archivedAt && financial),
|
||||
version,
|
||||
},
|
||||
id: props.card.id,
|
||||
});
|
||||
else
|
||||
await mutation.createCard.mutateAsync({
|
||||
id: intent,
|
||||
...metadata,
|
||||
...financial,
|
||||
});
|
||||
setOpened(false);
|
||||
}
|
||||
catch (error_) {
|
||||
setError(error_);
|
||||
}
|
||||
});
|
||||
|
||||
const onReloadRecord = async () => {
|
||||
const response = await fresh.refetch();
|
||||
if (response.data && !response.error) setLatest(response.data);
|
||||
else setError(response.error);
|
||||
};
|
||||
|
||||
const onAdoptVersion = () => {
|
||||
if (!latest) return;
|
||||
setVersion(latest.version);
|
||||
setLatest(undefined);
|
||||
setError(undefined);
|
||||
};
|
||||
|
||||
const onCancel = () => setOpened(false);
|
||||
|
||||
return {
|
||||
dialogTitle,
|
||||
error,
|
||||
isPending,
|
||||
latest,
|
||||
methods,
|
||||
onAdoptVersion,
|
||||
onCancel,
|
||||
onReloadRecord,
|
||||
onRequestClose,
|
||||
onSubmit,
|
||||
opened,
|
||||
t,
|
||||
version,
|
||||
};
|
||||
}
|
||||
|
||||
export { useBankCardFormVm };
|
||||
Reference in New Issue
Block a user