Files
frontend/src/pages/card/ui/OperationForm.view.tsx
T

204 lines
5.0 KiB
TypeScript

import { Select, Stack, Text, Textarea, TextInput } from "@mantine/core";
import { DateInput } from "@mantine/dates";
import type { FC } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
type BankOperation,
bankDateInputValue,
bankDateSchema,
bankToday,
} from "@/entities/bank-card";
import { formatMoney } from "@/shared/lib";
import type { OperationValues } from "./OperationForm.types";
type Choice = { value: string; label: string };
type OperationFieldsProps = {
editing: boolean;
refundCardId: string;
kind: OperationValues["kind"];
cards: Choice[];
targets: Choice[];
categories: Choice[];
defaultCategory: string;
};
const OperationFields: FC<OperationFieldsProps> = (props) => {
const { t } = useTranslation();
const {
control,
register,
setValue,
formState: { errors, isSubmitting },
} = useFormContext<OperationValues>();
return (
<Stack>
<Controller
control={control}
name="kind"
render={({ field }) => (
<Select
label={t("bank.kind")}
required
data={(["expense", "topup", "transfer", "refund"] as const).map(
(value) => ({ value, label: t(`bank.kinds.${value}`) }),
)}
{...field}
disabled={isSubmitting || props.editing}
onChange={(value) => {
field.onChange(value ?? "expense");
if (value === "refund") setValue("cardId", props.refundCardId);
}}
/>
)}
/>
<Controller
control={control}
name="cardId"
render={({ field }) => (
<Select
label={t(
props.kind === "transfer" ? "bank.source" : "bank.operationCard",
)}
required
data={props.cards}
{...field}
disabled={isSubmitting || props.kind === "refund"}
error={errors.cardId?.message}
/>
)}
/>
{props.kind === "transfer" && (
<Controller
control={control}
name="targetCardId"
render={({ field }) => (
<Select
label={t("bank.destination")}
required
data={props.targets}
{...field}
disabled={isSubmitting}
error={errors.targetCardId?.message}
/>
)}
/>
)}
<TextInput
label={t("bank.amount")}
required
inputMode="decimal"
{...register("amount")}
error={errors.amount?.message}
/>
<Controller
control={control}
name="date"
render={({ field }) => (
<DateInput
{...field}
label={t("bank.date")}
required
valueFormat="DD.MM.YYYY"
value={
bankDateSchema.safeParse(field.value).success ? field.value : null
}
dateParser={(value) =>
bankDateSchema.safeParse(bankDateInputValue(value)).success
? bankDateInputValue(value)
: null
}
fixOnBlur={false}
onInput={(event) =>
field.onChange(bankDateInputValue(event.currentTarget.value))
}
onChange={(value) => field.onChange(value ?? "")}
onBlur={(event) => {
field.onChange(bankDateInputValue(event.currentTarget.value));
field.onBlur();
}}
maxDate={bankToday()}
popoverProps={{
withinPortal: false,
middlewares: { shift: { crossAxis: true } },
}}
error={errors.date?.message}
/>
)}
/>
{props.kind === "expense" && (
<Controller
control={control}
name="categoryId"
render={({ field }) => (
<Select
label={t("bank.category")}
required
data={props.categories}
{...field}
value={field.value || props.defaultCategory}
disabled={isSubmitting}
error={errors.categoryId?.message}
/>
)}
/>
)}
<Textarea
label={t("bank.description")}
{...register("description")}
error={errors.description?.message}
/>
</Stack>
);
};
type RefundFieldsProps = {
editing: boolean;
expenses: Choice[];
selected?: Choice;
onSelect: (id: string) => void;
limit: BankOperation["availableRefund"] | undefined;
};
const RefundFields: FC<RefundFieldsProps> = (props) => {
const { t } = useTranslation();
const {
control,
formState: { errors, isSubmitting },
} = useFormContext<OperationValues>();
return (
<Stack>
<Controller
control={control}
name="originalExpenseId"
render={({ field }) => (
<Select
label={t("bank.expense")}
required
data={
props.selected &&
!props.expenses.some(
(item) => item.value === props.selected?.value,
)
? [props.selected, ...props.expenses]
: props.expenses
}
{...field}
onChange={(value) => {
field.onChange(value ?? "");
props.onSelect(value ?? "");
}}
disabled={isSubmitting || props.editing}
error={errors.originalExpenseId?.message}
/>
)}
/>
{props.limit && (
<Text>
{t("bank.availableRefund", { amount: formatMoney(props.limit) })}
</Text>
)}
{props.editing && <Text>{t("bank.purchaseImmutable")}</Text>}
</Stack>
);
};
export { OperationFields, RefundFields };