import { zodResolver } from "@hookform/resolvers/zod"; import { Badge, Button, Group, Modal, Stack, Text, TextInput, } from "@mantine/core"; import { type FC, useState } from "react"; import { Controller, FormProvider, useForm, useFormContext, } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { z } from "zod"; import { validationMessage } from "@/shared/i18n"; import { useBankMutations } from "../api/mutations"; import { type ExpenseCategory, useBankCategories } from "../api/queries"; import { BankFeedback } from "./BankFeedback"; import classes from "./BankForms.module.css"; import { BankOperationIcon } from "./BankOperationIcon"; import { useBankFormFocus } from "./useBankFormFocus"; const MAX_CATEGORY_NAME_LENGTH = 80; type CategoryManagerProps = { onClose: () => void; onCreated?: (id: string) => void; }; type CategoryValues = { name: string }; const CategoryFields: FC = () => { const { t } = useTranslation(); const { control, formState: { errors }, } = useFormContext(); return ( ( )} /> ); }; const CategoryManager: FC = (props) => { useBankFormFocus(); const [opened, setOpened] = useState(true); const { t } = useTranslation(); const categories = useBankCategories(); const mutation = useBankMutations(); const [editing, setEditing] = useState(); const [error, setError] = useState(); const [latest, setLatest] = useState(); const methods = useForm({ resolver: zodResolver( z.object({ name: z .string() .trim() .refine( (value) => value.length > 0 && value.length <= MAX_CATEGORY_NAME_LENGTH, { error: validationMessage("bank.validation.category") }, ) .refine( (value) => !categories.data?.some( (c) => c.id !== editing?.id && c.name.toLocaleLowerCase("ru-RU") === value.toLocaleLowerCase("ru-RU"), ), { error: validationMessage("bank.validation.duplicate") }, ), }), ), defaultValues: { name: "" }, }); const pending = !opened || methods.formState.isSubmitting; return ( !pending && setOpened(false)} title={t("bank.categories")} closeOnClickOutside={!pending} closeOnEscape={!pending} closeButtonProps={{ "aria-label": t("bank.close"), disabled: pending }} >
{ setError(undefined); try { if (editing) await mutation.updateCategory.mutateAsync({ id: editing.id, body: { name: values.name, version: editing.version }, }); else { const result = await mutation.createCategory.mutateAsync({ name: values.name, }); if (result) props.onCreated?.(result.id); } setEditing(undefined); methods.reset({ name: "" }); } catch (failure) { setError(failure); } })} >
{categories.data?.map((category) => ( {category.name} {category.isDefault && ( {t("bank.defaultCategory")} )} ))} {categories.isLoading && ( {t("bank.loading")} )} {categories.isError && ( void categories.refetch()} /> )} {error ? ( <> {editing && ( )} ) : null} {latest && ( <> {t("bank.versionHint", { details: latest.name })} )} {editing && ( )}
); }; export { CategoryManager };