refactor: establish local frontend workspace boundaries
Frontend security / security (push) Successful in 9s
Frontend verify / verify (push) Successful in 5m27s
Frontend security / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 5m24s
Conventional commits / commits (pull_request) Failing after 26s
Frontend security / security (push) Successful in 9s
Frontend verify / verify (push) Successful in 5m27s
Frontend security / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 5m24s
Conventional commits / commits (pull_request) Failing after 26s
OpenSpec change: evolve-frontend-stack
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
import { Select, Stack, Text, Textarea, TextInput } from "@mantine/core";
|
||||
import {
|
||||
Button,
|
||||
Group,
|
||||
Modal,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
Textarea,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { DateInput } from "@mantine/dates";
|
||||
import type { FC } from "react";
|
||||
import type { FC, FormEventHandler, ReactNode } 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 type { BankOperation } from "@/entities/bank-card";
|
||||
import { formatMoney } from "@/shared/lib";
|
||||
import classes from "./OperationForm.module.css";
|
||||
import type { OperationValues } from "./OperationForm.types";
|
||||
|
||||
type Choice = { value: string; label: string };
|
||||
@@ -21,6 +26,10 @@ type OperationFieldsProps = {
|
||||
targets: Choice[];
|
||||
categories: Choice[];
|
||||
defaultCategory: string;
|
||||
kinds: Choice[];
|
||||
dateInputValue: (value: string) => string;
|
||||
validDate: (value: string) => boolean;
|
||||
maxDate: () => string;
|
||||
};
|
||||
const OperationFields: FC<OperationFieldsProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -39,9 +48,7 @@ const OperationFields: FC<OperationFieldsProps> = (props) => {
|
||||
<Select
|
||||
label={t("bank.kind")}
|
||||
required
|
||||
data={(["expense", "topup", "transfer", "refund"] as const).map(
|
||||
(value) => ({ value, label: t(`bank.kinds.${value}`) }),
|
||||
)}
|
||||
data={props.kinds}
|
||||
{...field}
|
||||
disabled={isSubmitting || props.editing}
|
||||
onChange={(value) => {
|
||||
@@ -99,24 +106,22 @@ const OperationFields: FC<OperationFieldsProps> = (props) => {
|
||||
label={t("bank.date")}
|
||||
required
|
||||
valueFormat="DD.MM.YYYY"
|
||||
value={
|
||||
bankDateSchema.safeParse(field.value).success ? field.value : null
|
||||
}
|
||||
value={props.validDate(field.value) ? field.value : null}
|
||||
dateParser={(value) =>
|
||||
bankDateSchema.safeParse(bankDateInputValue(value)).success
|
||||
? bankDateInputValue(value)
|
||||
props.validDate(props.dateInputValue(value))
|
||||
? props.dateInputValue(value)
|
||||
: null
|
||||
}
|
||||
fixOnBlur={false}
|
||||
onInput={(event) =>
|
||||
field.onChange(bankDateInputValue(event.currentTarget.value))
|
||||
field.onChange(props.dateInputValue(event.currentTarget.value))
|
||||
}
|
||||
onChange={(value) => field.onChange(value ?? "")}
|
||||
onBlur={(event) => {
|
||||
field.onChange(bankDateInputValue(event.currentTarget.value));
|
||||
field.onChange(props.dateInputValue(event.currentTarget.value));
|
||||
field.onBlur();
|
||||
}}
|
||||
maxDate={bankToday()}
|
||||
maxDate={props.maxDate()}
|
||||
popoverProps={{
|
||||
withinPortal: false,
|
||||
middlewares: { shift: { crossAxis: true } },
|
||||
@@ -153,7 +158,6 @@ const OperationFields: FC<OperationFieldsProps> = (props) => {
|
||||
type RefundFieldsProps = {
|
||||
editing: boolean;
|
||||
expenses: Choice[];
|
||||
selected?: Choice;
|
||||
onSelect: (id: string) => void;
|
||||
limit: BankOperation["availableRefund"] | undefined;
|
||||
};
|
||||
@@ -172,14 +176,7 @@ const RefundFields: FC<RefundFieldsProps> = (props) => {
|
||||
<Select
|
||||
label={t("bank.expense")}
|
||||
required
|
||||
data={
|
||||
props.selected &&
|
||||
!props.expenses.some(
|
||||
(item) => item.value === props.selected?.value,
|
||||
)
|
||||
? [props.selected, ...props.expenses]
|
||||
: props.expenses
|
||||
}
|
||||
data={props.expenses}
|
||||
{...field}
|
||||
onChange={(value) => {
|
||||
field.onChange(value ?? "");
|
||||
@@ -200,4 +197,119 @@ const RefundFields: FC<RefundFieldsProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export { OperationFields, RefundFields };
|
||||
type OperationFormViewProps = {
|
||||
opened: boolean;
|
||||
pending: boolean;
|
||||
categoriesOpen: boolean;
|
||||
editing: boolean;
|
||||
onClose: () => void;
|
||||
onExitTransitionEnd: () => void;
|
||||
onCancel: () => void;
|
||||
onSubmit: FormEventHandler<HTMLFormElement>;
|
||||
fields: OperationFieldsProps;
|
||||
refund: RefundFieldsProps;
|
||||
loadingExpenses: boolean;
|
||||
emptyExpenses: boolean;
|
||||
sourcePage: number;
|
||||
previousDisabled: boolean;
|
||||
nextDisabled: boolean;
|
||||
onPrevious: () => void;
|
||||
onNext: () => void;
|
||||
onAddCategory: () => void;
|
||||
onReload?: () => void;
|
||||
latest?: { version: number; details: string; onAdopt: () => void };
|
||||
saveDisabled: boolean;
|
||||
refundFeedback?: ReactNode;
|
||||
categoriesFeedback?: ReactNode;
|
||||
feedback?: ReactNode;
|
||||
};
|
||||
const OperationFormView: FC<OperationFormViewProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal
|
||||
opened={props.opened}
|
||||
onExitTransitionEnd={props.onExitTransitionEnd}
|
||||
onClose={props.onClose}
|
||||
title={t(props.editing ? "bank.editOperation" : "bank.addOperation")}
|
||||
closeOnEscape={!props.pending && !props.categoriesOpen}
|
||||
closeOnClickOutside={!props.pending && !props.categoriesOpen}
|
||||
closeButtonProps={{
|
||||
"aria-label": t("bank.close"),
|
||||
disabled: props.pending,
|
||||
}}
|
||||
>
|
||||
<form onSubmit={props.onSubmit}>
|
||||
<fieldset disabled={props.pending} className={classes.fieldset}>
|
||||
<Stack>
|
||||
<OperationFields {...props.fields} />
|
||||
{props.fields.kind === "refund" && (
|
||||
<>
|
||||
<RefundFields {...props.refund} />
|
||||
{props.loadingExpenses && (
|
||||
<Text role="status">{t("bank.loadingHistory")}</Text>
|
||||
)}
|
||||
{props.emptyExpenses && <Text>{t("bank.noPurchases")}</Text>}
|
||||
{!props.editing && (
|
||||
<Group>
|
||||
<Button
|
||||
variant="default"
|
||||
disabled={props.previousDisabled}
|
||||
onClick={props.onPrevious}
|
||||
>
|
||||
{t("bank.previous")}
|
||||
</Button>
|
||||
<Text>{t("bank.page", { page: props.sourcePage })}</Text>
|
||||
<Button
|
||||
variant="default"
|
||||
disabled={props.nextDisabled}
|
||||
onClick={props.onNext}
|
||||
>
|
||||
{t("bank.next")}
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
{props.refundFeedback}
|
||||
</>
|
||||
)}
|
||||
{props.fields.kind === "expense" && (
|
||||
<Button variant="subtle" onClick={props.onAddCategory}>
|
||||
{t("bank.addCategory")}
|
||||
</Button>
|
||||
)}
|
||||
{props.categoriesFeedback}
|
||||
{props.feedback}
|
||||
{props.onReload && (
|
||||
<Button variant="default" onClick={props.onReload}>
|
||||
{t("bank.reloadRecord")}
|
||||
</Button>
|
||||
)}
|
||||
{props.latest && (
|
||||
<>
|
||||
<Text>
|
||||
{t("bank.versionHint", { details: props.latest.details })}
|
||||
</Text>
|
||||
<Button variant="default" onClick={props.latest.onAdopt}>
|
||||
{t("bank.adoptVersion", { version: props.latest.version })}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Group>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={props.pending}
|
||||
disabled={props.saveDisabled}
|
||||
>
|
||||
{t("bank.save")}
|
||||
</Button>
|
||||
<Button variant="default" onClick={props.onCancel}>
|
||||
{t("bank.cancel")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export { OperationFormView };
|
||||
|
||||
Reference in New Issue
Block a user