204 lines
5.0 KiB
TypeScript
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 };
|