125 lines
3.3 KiB
TypeScript
125 lines
3.3 KiB
TypeScript
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
|
|
import { Alert } from "@mantine/core";
|
|
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 = { conflict?: boolean; pending?: boolean };
|
|
|
|
const OperationExample: FC<ExampleProps> = (props) => {
|
|
const [opened, setOpened] = useState(true);
|
|
const [action, setAction] = useState("");
|
|
|
|
const methods = useForm<OperationValues>({
|
|
defaultValues: {
|
|
amount: "500",
|
|
cardId: "demo",
|
|
categoryId: "",
|
|
date: "2026-08-01",
|
|
description: "Синтетический возврат",
|
|
kind: "refund",
|
|
originalExpenseId: "purchase",
|
|
targetCardId: "",
|
|
},
|
|
});
|
|
|
|
const kind = useWatch({ control: methods.control, name: "kind" });
|
|
|
|
return (
|
|
<FormProvider {...methods}>
|
|
<OperationFormView
|
|
categoriesOpen={false}
|
|
editing
|
|
emptyExpenses={false}
|
|
feedback={
|
|
props.conflict && (
|
|
<Alert color="orange" role="alert">
|
|
Синтетический конфликт версии
|
|
</Alert>
|
|
)
|
|
}
|
|
fields={{
|
|
cards: [{ label: "Учебная карта", value: "demo" }],
|
|
categories: [],
|
|
dateInputValue: () => "2026-08-01",
|
|
defaultCategory: "",
|
|
editing: true,
|
|
kind,
|
|
kinds: [{ label: "Возврат", value: "refund" }],
|
|
maxDate: () => "2026-08-27",
|
|
refundCardId: "demo",
|
|
targets: [],
|
|
validDate: value => value === "2026-08-01",
|
|
}}
|
|
latest={
|
|
props.conflict
|
|
? {
|
|
details: "Учебная версия",
|
|
onAdopt: () => setAction("Локальное принятие версии"),
|
|
version: 2,
|
|
}
|
|
: undefined
|
|
}
|
|
loadingExpenses={false}
|
|
nextDisabled
|
|
onAddCategory={() => {}}
|
|
onCancel={() => setOpened(false)}
|
|
onClose={() => !props.pending && setOpened(false)}
|
|
onExitTransitionEnd={() => setAction("Форма закрыта")}
|
|
onNext={() => {}}
|
|
onPrevious={() => {}}
|
|
onReload={
|
|
props.conflict ? () => setAction("Локальный reload") : undefined
|
|
}
|
|
onSubmit={(event) => {
|
|
event.preventDefault();
|
|
setAction("Локальный submit");
|
|
}}
|
|
opened={opened}
|
|
pending={Boolean(props.pending)}
|
|
previousDisabled
|
|
refund={{
|
|
editing: true,
|
|
expenses: [
|
|
{
|
|
label: "01.07.2026 · Учебная покупка · 500 ₽",
|
|
value: "purchase",
|
|
},
|
|
],
|
|
limit: { currency: "RUB", value: "500" },
|
|
}}
|
|
saveDisabled={false}
|
|
sourcePage={1}
|
|
/>
|
|
|
|
<output aria-live="polite">{action}</output>
|
|
</FormProvider>
|
|
);
|
|
};
|
|
|
|
const meta = {
|
|
component: OperationExample,
|
|
parameters: {
|
|
docs: {
|
|
description: {
|
|
component:
|
|
"Synthetic presentation без API/VM. Date callbacks фиксируют fixture 01.08.2026; денежная validation, recovery и nested category flow проверяются application tests. Actions только локальные.",
|
|
},
|
|
},
|
|
},
|
|
tags: ["autodocs"],
|
|
title: "Product/OperationForm",
|
|
} 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 } };
|