refactor: colocate frontend component files
Frontend security / security (push) Successful in 9s
Frontend verify / verify (push) Successful in 5m10s
Conventional commits / commits (pull_request) Successful in 25s
Frontend security / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Successful in 5m10s

This commit is contained in:
2026-09-23 08:34:58 +03:00
parent 85fcfe3d07
commit b987fd8e78
190 changed files with 131 additions and 108 deletions
@@ -0,0 +1,113 @@
import { Alert } from "@mantine/core";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { type FC, useState } from "react";
import { FormProvider, useForm, useWatch } from "react-hook-form";
import type { OperationValues } from "./OperationForm.types";
import { OperationFormView } from "./OperationForm.view";
type ExampleProps = { pending?: boolean; conflict?: boolean };
const OperationExample: FC<ExampleProps> = (props) => {
const [opened, setOpened] = useState(true);
const [action, setAction] = useState("");
const methods = useForm<OperationValues>({
defaultValues: {
kind: "refund",
cardId: "demo",
targetCardId: "",
categoryId: "",
originalExpenseId: "purchase",
amount: "500",
date: "2026-08-01",
description: "Синтетический возврат",
},
});
const kind = useWatch({ control: methods.control, name: "kind" });
return (
<FormProvider {...methods}>
<OperationFormView
opened={opened}
pending={Boolean(props.pending)}
editing
categoriesOpen={false}
onClose={() => !props.pending && setOpened(false)}
onCancel={() => setOpened(false)}
onExitTransitionEnd={() => setAction("Форма закрыта")}
onSubmit={(event) => {
event.preventDefault();
setAction("Локальный submit");
}}
fields={{
editing: true,
refundCardId: "demo",
kind,
cards: [{ value: "demo", label: "Учебная карта" }],
targets: [],
categories: [],
defaultCategory: "",
kinds: [{ value: "refund", label: "Возврат" }],
dateInputValue: () => "2026-08-01",
validDate: (value) => value === "2026-08-01",
maxDate: () => "2026-08-27",
}}
refund={{
editing: true,
expenses: [
{
value: "purchase",
label: "01.07.2026 · Учебная покупка · 500 ₽",
},
],
onSelect: () => undefined,
limit: { currency: "RUB", value: "500" },
}}
loadingExpenses={false}
emptyExpenses={false}
sourcePage={1}
previousDisabled
nextDisabled
onPrevious={() => undefined}
onNext={() => undefined}
onAddCategory={() => undefined}
saveDisabled={false}
feedback={
props.conflict && (
<Alert color="orange" role="alert">
Синтетический конфликт версии
</Alert>
)
}
onReload={
props.conflict ? () => setAction("Локальный reload") : undefined
}
latest={
props.conflict
? {
version: 2,
details: "Учебная версия",
onAdopt: () => setAction("Локальное принятие версии"),
}
: undefined
}
/>
<output aria-live="polite">{action}</output>
</FormProvider>
);
};
const meta = {
title: "Product/OperationForm",
component: OperationExample,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component:
"Synthetic presentation без API/VM. Date callbacks фиксируют fixture 01.08.2026; денежная validation, recovery и nested category flow проверяются application tests. Actions только локальные.",
},
},
},
} satisfies Meta<typeof OperationExample>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Refund: Story = {};
export const Pending: Story = { args: { pending: true } };
export const Conflict: Story = { args: { conflict: true } };