import { zodResolver } from "@hookform/resolvers/zod"; import { useState } from "react"; import { useForm, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { bankDateInputValue, bankDateSchema, bankMoneyValue, type BankOperation, bankOperationKinds, bankToday, useBankCategories, useBankExpenses, useBankFormFocus, useBankOperation, useCreateBankOperation, useUpdateBankOperation, } from "@/entities/bank-card"; import { formatDate, formatMoney } from "@/shared/lib"; import type { OperationFormProps, OperationValues, } from "./OperationForm.types"; import { createOperationSchema } from "./OperationForm.schema"; const useOperationFormVm = (props: OperationFormProps) => { useBankFormFocus(); const [opened, setOpened] = useState(true); const { t } = useTranslation(); const [categoriesOpen, setCategoriesOpen] = useState(false); const [version, setVersion] = useState(props.operation?.version ?? 1); const [latest, setLatest] = useState(); const [sourcePage, setSourcePage] = useState(1); const [sourceId, setSourceId] = useState( props.operation?.originalExpenseId ?? "", ); const sourceQuery = useBankOperation(sourceId || undefined); const currentQuery = useBankOperation(props.operation?.id); const [refundLimit, setRefundLimit] = useState( props.operation?.availableRefund, ); const [intent] = useState(() => crypto.randomUUID()); const [submittedValues, setSubmittedValues] = useState(); const [error, setError] = useState(); const categories = useBankCategories(); const operationCreation = useCreateBankOperation(); const updateOperation = useUpdateBankOperation(); const methods = useForm({ defaultValues: { amount: props.operation?.amount.value ?? "", cardId: props.operation?.cardId ?? props.card.id, categoryId: props.operation?.categoryId ?? "", date: props.operation?.date ?? bankToday(), description: props.operation?.description ?? "", kind: props.operation?.kind ?? "expense", originalExpenseId: props.operation?.originalExpenseId ?? "", targetCardId: props.operation?.targetCardId ?? "", }, resolver: zodResolver( createOperationSchema({ cards: props.cards, categories: categories.data, expense: sourceQuery.data, limit: props.operation ? refundLimit : sourceQuery.data?.availableRefund, submittedValues, }), ), }); const kind = useWatch({ control: methods.control, name: "kind" }); const expenses = useBankExpenses( kind === "refund" ? props.card.id : undefined, { page: sourcePage }, ); const source = useWatch({ control: methods.control, name: "cardId" }); const isPending = !opened || methods.formState.isSubmitting; const options = props.cards .filter(c => !c.archivedAt) .map(c => ({ label: c.name, value: c.id })); const onSubmit = methods.handleSubmit(async (values) => { setSubmittedValues(values); setError(undefined); try { const body = { cardId: values.cardId, kind: values.kind, ...(values.kind === "transfer" && { targetCardId: values.targetCardId, }), ...(values.kind === "expense" && { categoryId: values.categoryId || categories.data?.find(c => c.isDefault)?.id, }), ...(values.kind === "refund" && { originalExpenseId: values.originalExpenseId, }), amount: { currency: "RUB" as const, value: bankMoneyValue(values.amount), }, date: values.date, description: values.description, }; if (props.operation) await updateOperation.mutateAsync({ body: { ...body, version }, id: props.operation.id, }); else await operationCreation.mutateAsync({ id: intent, ...body, }); setOpened(false); } catch (error_) { setError(error_); } }); const onReload = async () => { const query = props.operation ? currentQuery : sourceQuery; const result = await query.refetch(); if (result.data && !result.error) { if (props.operation) { setLatest(result.data); setRefundLimit(result.data.availableRefund); } else setError(undefined); } 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 : null, categoriesOpen, closeCategories: () => setCategoriesOpen(false), error, expensesError: expenses.isError ? expenses.error : null, methods, onCategoryCreated: (id: string) => { methods.setValue("categoryId", id); setCategoriesOpen(false); }, retryCategories: () => void categories.refetch(), retryExpenses: () => void expenses.refetch(), retrySource: () => void sourceQuery.refetch(), sourceError: sourceQuery.isError ? sourceQuery.error : null, view: { categoriesOpen, editing: Boolean(props.operation), emptyExpenses: Boolean( expenses.data && expenses.data.items.length === 0 && !props.operation, ), fields: { 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, kinds: bankOperationKinds.map(value => ({ label: t(`bank.kinds.${value}`), value, })), maxDate: bankToday, refundCardId: props.card.id, targets: options.filter(c => c.value !== source), validDate: (value: string) => bankDateSchema.safeParse(value).success, }, latest: latest ? { details: `${formatDate(latest.date)} · ${formatMoney(latest.amount)} · ${latest.description ?? ""}`, onAdopt: () => { setVersion(latest.version); setLatest(undefined); setError(undefined); }, version: latest.version, } : undefined, loadingExpenses: expenses.isLoading, nextDisabled: !expenses.data?.hasNext || expenses.isFetching, onAddCategory: () => setCategoriesOpen(true), onCancel: () => setOpened(false), onClose: () => !isPending && !categoriesOpen && setOpened(false), onExitTransitionEnd: props.onClose, onNext: () => setSourcePage(sourcePage + 1), onPrevious: () => setSourcePage(sourcePage - 1), onReload: error && (sourceId || props.operation) ? onReload : undefined, onSubmit, opened, pending: isPending, previousDisabled: sourcePage === 1 || expenses.isFetching, refund: { editing: Boolean(props.operation), expenses: selected && expenseOptions.every(item => item.value !== selected.value) ? [selected, ...expenseOptions] : expenseOptions, limit: props.operation ? refundLimit : sourceQuery.data?.availableRefund, onSelect: setSourceId, }, saveDisabled: (kind === "expense" && !categories.data) || (kind === "refund" && !sourceQuery.data), sourcePage, }, }; }; export { useOperationFormVm };