feat(frontend): update eslint rules

This commit is contained in:
Sergey Krylov 2025-09-17 06:14:33 +03:00
parent dd9ae0b7c0
commit 457f4bbed2
163 changed files with 1064 additions and 1102 deletions

View File

@ -1,26 +1,26 @@
export const items1 = [ export const items1 = [
{ {
id: '1',
expenseListId: '1',
title: 'Счет за интернет',
description: 'Ежемесячная плата за интернет',
date: '2024-04-01',
amount: 500, amount: 500,
currency: 'RUB',
count: 1, count: 1,
createdAt: '2024-04-01T12:00:00Z', createdAt: '2024-04-01T12:00:00Z',
currency: 'RUB',
date: '2024-04-01',
description: 'Ежемесячная плата за интернет',
expenseListId: '1',
id: '1',
title: 'Счет за интернет',
updatedAt: '2024-04-01T12:00:00Z', updatedAt: '2024-04-01T12:00:00Z',
}, },
{ {
id: '2',
expenseListId: '1',
title: 'Покупка продуктов',
description: 'Продукты на неделю',
date: '2024-04-02',
amount: 3000, amount: 3000,
currency: 'RUB',
count: 1, count: 1,
createdAt: '2024-04-02T12:00:00Z', createdAt: '2024-04-02T12:00:00Z',
currency: 'RUB',
date: '2024-04-02',
description: 'Продукты на неделю',
expenseListId: '1',
id: '2',
title: 'Покупка продуктов',
updatedAt: '2024-04-02T12:00:00Z', updatedAt: '2024-04-02T12:00:00Z',
}, },
]; ];
@ -31,42 +31,42 @@ export const items3 = null;
export const items4 = [ export const items4 = [
{ {
id: '3',
expenseListId: '4',
title: 'Бензин',
description: null,
date: '2024-04-03',
amount: 1500, amount: 1500,
currency: 'RUB',
count: null, count: null,
createdAt: '2024-04-03T12:00:00Z', createdAt: '2024-04-03T12:00:00Z',
currency: 'RUB',
date: '2024-04-03',
description: null,
expenseListId: '4',
id: '3',
title: 'Бензин',
updatedAt: '2024-04-03T12:00:00Z', updatedAt: '2024-04-03T12:00:00Z',
}, },
]; ];
export const items5 = [ export const items5 = [
{ {
id: '4',
expenseListId: '5',
title: 'Ноутбук',
description: 'Покупка нового ноутбука',
date: '2024-04-04',
amount: 50000, amount: 50000,
currency: 'RUB',
count: 1, count: 1,
createdAt: '2024-04-04T12:00:00Z', createdAt: '2024-04-04T12:00:00Z',
currency: 'RUB',
date: '2024-04-04',
description: 'Покупка нового ноутбука',
expenseListId: '5',
id: '4',
title: 'Ноутбук',
updatedAt: '2024-04-04T12:00:00Z', updatedAt: '2024-04-04T12:00:00Z',
}, },
{ {
id: '5',
expenseListId: '5',
title: 'Мышка',
description: 'Механическая мышка',
date: '2024-04-05',
amount: 3000, amount: 3000,
currency: 'RUB',
count: 1, count: 1,
createdAt: '2024-04-05T12:00:00Z', createdAt: '2024-04-05T12:00:00Z',
currency: 'RUB',
date: '2024-04-05',
description: 'Механическая мышка',
expenseListId: '5',
id: '5',
title: 'Мышка',
updatedAt: '2024-04-05T12:00:00Z', updatedAt: '2024-04-05T12:00:00Z',
}, },
]; ];

View File

@ -7,41 +7,41 @@ import {
} from './items'; } from './items';
export const list1 = { export const list1 = {
id: '1',
title: 'Расходы на быт',
items: items1,
createdAt: '2024-03-28T10:00:00Z', createdAt: '2024-03-28T10:00:00Z',
id: '1',
items: items1,
title: 'Расходы на быт',
updatedAt: '2024-04-02T12:00:00Z', updatedAt: '2024-04-02T12:00:00Z',
}; };
export const list2 = { export const list2 = {
id: '2',
title: 'Дополнительные расходы',
items: items2,
createdAt: '2024-04-01T10:00:00Z', createdAt: '2024-04-01T10:00:00Z',
id: '2',
items: items2,
title: 'Дополнительные расходы',
updatedAt: '2024-04-01T10:00:00Z', updatedAt: '2024-04-01T10:00:00Z',
}; };
export const list3 = { export const list3 = {
id: '3',
title: 'Кредиты',
items: items3,
createdAt: '2024-04-01T11:00:00Z', createdAt: '2024-04-01T11:00:00Z',
id: '3',
items: items3,
title: 'Кредиты',
updatedAt: '2024-04-01T11:00:00Z', updatedAt: '2024-04-01T11:00:00Z',
}; };
export const list4 = { export const list4 = {
id: '4',
title: 'Автомобиль',
items: items4,
createdAt: '2024-04-03T10:00:00Z', createdAt: '2024-04-03T10:00:00Z',
id: '4',
items: items4,
title: 'Автомобиль',
updatedAt: '2024-04-03T10:00:00Z', updatedAt: '2024-04-03T10:00:00Z',
}; };
export const list5 = { export const list5 = {
id: '5',
title: 'Техника',
items: items5,
createdAt: '2024-04-04T10:00:00Z', createdAt: '2024-04-04T10:00:00Z',
id: '5',
items: items5,
title: 'Техника',
updatedAt: '2024-04-05T10:00:00Z', updatedAt: '2024-04-05T10:00:00Z',
}; };

View File

@ -1,5 +1,3 @@
import { http, HttpResponse } from 'msw';
import { import {
list1, list1,
list2, list2,
@ -7,6 +5,7 @@ import {
list4, list4,
list5, list5,
} from '@mocks/browser/data/expenses/lists'; } from '@mocks/browser/data/expenses/lists';
import { http, HttpResponse } from 'msw';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';

View File

@ -1,3 +1,3 @@
export { getExpenseList } from './expense/list/get';
export { getExpenseItemListById } from './expense/list/:id/get';
export { loginHandler } from './auth/login/post'; export { loginHandler } from './auth/login/post';
export { getExpenseItemListById } from './expense/list/:id/get';
export { getExpenseList } from './expense/list/get';

View File

@ -1,8 +1,8 @@
import { setupWorker } from 'msw/browser'; import { setupWorker } from 'msw/browser';
import { import {
getExpenseList,
getExpenseItemListById, getExpenseItemListById,
getExpenseList,
loginHandler, loginHandler,
} from './handlers'; } from './handlers';

View File

@ -1,7 +1,7 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
export const createReactQueryWrapper = () => { export const createReactQueryWrapper = () => {
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {

View File

@ -1,6 +1,7 @@
import pluginQuery from '@tanstack/eslint-plugin-query'; import pluginQuery from '@tanstack/eslint-plugin-query';
// @ts-expect-error fix package // @ts-expect-error fix package
import config from 'eslint-config-ksv741'; import config from 'eslint-config-ksv741';
import perfectionist from 'eslint-plugin-perfectionist';
// eslint-disable-next-line import/no-anonymous-default-export // eslint-disable-next-line import/no-anonymous-default-export
export default [ export default [
@ -9,6 +10,10 @@ export default [
...config, ...config,
{ {
rules: { rules: {
'@stylistic/jsx-sort-props': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
'func-style': 'off',
'import/no-extraneous-dependencies': ['error', { 'import/no-extraneous-dependencies': ['error', {
devDependencies: [ devDependencies: [
'eslint.config.ts', 'eslint.config.ts',
@ -23,71 +28,13 @@ export default [
'__mocks__/**', '__mocks__/**',
], ],
}], }],
'import/order': [ 'import/order': 'off',
'error', {
pathGroups: [
{
pattern: '__mocks__/**',
group: 'builtin',
position: 'before',
},
{
pattern: 'apps/**',
group: 'internal',
position: 'after',
},
{
pattern: 'pages/**',
group: 'internal',
position: 'after',
},
{
pattern: 'widgets/**',
group: 'internal',
position: 'after',
},
{
pattern: 'features/**',
group: 'internal',
position: 'after',
},
{
pattern: 'entities/**',
group: 'internal',
position: 'after',
},
{
pattern: 'shared/**',
group: 'internal',
position: 'after',
},
],
distinctGroup: true,
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'object',
'index',
'type',
],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'func-style': 'off',
'jsx-a11y/click-events-have-key-events': 'off', 'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/no-static-element-interactions': 'off',
'jsx-a11y/no-noninteractive-element-interactions': 'off', 'jsx-a11y/no-noninteractive-element-interactions': 'off',
'jsx-a11y/no-static-element-interactions': 'off',
}, },
}, },
perfectionist.configs['recommended-natural'],
{ {
ignores: ['./src/shared/api/schema.ts'], ignores: ['./src/shared/api/schema.ts'],
}, },

View File

@ -1,6 +1,14 @@
import type { Config } from 'jest'; import type { Config } from 'jest';
export default { export default {
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
'@mocks/(.*)': '<rootDir>/__mocks__/$1',
'\\.(css|scss|sass|less)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/jest/fileMock.js',
},
setupFilesAfterEnv: ['<rootDir>/configs/jest/setupTests.ts'],
testEnvironment: '<rootDir>/configs/jest/jest-fixed-jsdom.ts',
transform: { transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest', { '^.+\\.(t|j)sx?$': ['@swc/jest', {
jsc: { jsc: {
@ -12,12 +20,4 @@ export default {
}, },
}], }],
}, },
testEnvironment: '<rootDir>/configs/jest/jest-fixed-jsdom.ts',
setupFilesAfterEnv: ['<rootDir>/configs/jest/setupTests.ts'],
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
'@mocks/(.*)': '<rootDir>/__mocks__/$1',
'\\.(css|scss|sass|less)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/jest/fileMock.js',
},
} as Config; } as Config;

View File

@ -4,6 +4,9 @@ import { pluginReact } from '@rsbuild/plugin-react';
import { pluginTypeCheck } from '@rsbuild/plugin-type-check'; import { pluginTypeCheck } from '@rsbuild/plugin-type-check';
export default defineConfig({ export default defineConfig({
html: {
title: 'Investments Control',
},
plugins: [ plugins: [
pluginReact(), pluginReact(),
pluginEslint({ pluginEslint({
@ -15,17 +18,14 @@ export default defineConfig({
}), }),
pluginTypeCheck(), pluginTypeCheck(),
], ],
html: {
title: 'Investments Control',
},
server: { server: {
proxy: { proxy: {
[`${process.env.PUBLIC_API_BASE_URL}`]: { [`${process.env.PUBLIC_API_BASE_URL}`]: {
target: process.env.BACKEND_BASE_URL,
changeOrigin: true, changeOrigin: true,
pathRewrite: { pathRewrite: {
[`^${process.env.PUBLIC_API_BASE_URL}`]: '', [`^${process.env.PUBLIC_API_BASE_URL}`]: '',
}, },
target: process.env.BACKEND_BASE_URL,
}, },
}, },
}, },

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type LoginRequestBody = paths['/auth/login']['post']['requestBody']['content']['application/json']; type LoginRequestBody = paths['/auth/login']['post']['requestBody']['content']['application/json'];
type LoginSuccessResponse = paths['/auth/login']['post']['responses']['200']['content']['application/json']; type LoginSuccessResponse = paths['/auth/login']['post']['responses']['200']['content']['application/json'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type LogoutSuccessResponse = paths['/auth/logout']['post']['responses']['200']['content']['application/json']; type LogoutSuccessResponse = paths['/auth/logout']['post']['responses']['200']['content']['application/json'];
export async function logout() { export async function logout() {

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type RegisterRequestBody = paths['/auth/register']['post']['requestBody']['content']['application/json']; type RegisterRequestBody = paths['/auth/register']['post']['requestBody']['content']['application/json'];
type RegisterSuccessResponse = paths['/auth/register']['post']['responses']['200']['content']['application/json']; type RegisterSuccessResponse = paths['/auth/register']['post']['responses']['200']['content']['application/json'];

View File

@ -13,16 +13,16 @@ export const useLogin = () => {
return useMutation({ return useMutation({
mutationFn: login, mutationFn: login,
onSuccess: async (data) => {
setAccessToken(data.accessToken);
localStorage.setItem('accessToken', data.accessToken);
await navigate(PAGES.Home);
},
onError: (error) => { onError: (error) => {
// eslint-disable-next-line // eslint-disable-next-line
console.log('Неуспешный логин', error); console.log('Неуспешный логин', error);
throw error; throw error;
}, },
onSuccess: async (data) => {
setAccessToken(data.accessToken);
localStorage.setItem('accessToken', data.accessToken);
await navigate(PAGES.Home);
},
}); });
}; };

View File

@ -1,3 +1,3 @@
export { useLogin } from './hooks/useLogin'; export { useLogin } from './hooks/useLogin';
export { useRegister } from './hooks/useRegister';
export { useLogout } from './hooks/useLogout'; export { useLogout } from './hooks/useLogout';
export { useRegister } from './hooks/useRegister';

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type AddBankCardBody = paths['/bank-card']['post']['requestBody']['content']['application/json']; export type AddBankCardBody = paths['/bank-card']['post']['requestBody']['content']['application/json'];
export type AddBankCardResponse = paths['/bank-card']['post']['responses']['200']['content']['application/json']; export type AddBankCardResponse = paths['/bank-card']['post']['responses']['200']['content']['application/json'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type DeleteBankCardPath = paths['/bank-card/{id}']['delete']['parameters']['path']; export type DeleteBankCardPath = paths['/bank-card/{id}']['delete']['parameters']['path'];
export type DeleteBankCardResponse = paths['/bank-card/{id}']['delete']['responses']['200']['content']['application/json']; export type DeleteBankCardResponse = paths['/bank-card/{id}']['delete']['responses']['200']['content']['application/json'];

View File

@ -1,12 +1,12 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
export type EditBankCardPath = paths['/bank-card/{id}']['patch']['parameters']['path']; import { api } from '@/shared/api/client';
export type EditBankCardBody = paths['/bank-card/{id}']['patch']['requestBody']['content']['application/json']; export type EditBankCardBody = paths['/bank-card/{id}']['patch']['requestBody']['content']['application/json'];
export type EditBankCardPath = paths['/bank-card/{id}']['patch']['parameters']['path'];
export type EditBankCardResponse = paths['/bank-card/{id}']['patch']['responses']['200']['content']['application/json']; export type EditBankCardResponse = paths['/bank-card/{id}']['patch']['responses']['200']['content']['application/json'];
export async function editBankCard({ path, data }: { path: EditBankCardPath; data: EditBankCardBody }) { export async function editBankCard({ data, path }: { data: EditBankCardBody; path: EditBankCardPath }) {
const { data: result } = await api.patch<EditBankCardResponse>(`/bank-card/${path.id}`, data); const { data: result } = await api.patch<EditBankCardResponse>(`/bank-card/${path.id}`, data);
return result; return result;

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type BankCardsResponse = paths['/bank-card']['get']['responses']['200']['content']['application/json']; type BankCardsResponse = paths['/bank-card']['get']['responses']['200']['content']['application/json'];
export async function getBankCards() { export async function getBankCards() {

View File

@ -1,17 +1,18 @@
export { useBankCards } from './hooks/useBankCards';
export { useAddBankCard } from './hooks/useAddBankCard';
export { useDeleteBankCard } from './hooks/useDeleteBankCard';
export { useEditBankCard } from './hooks/useEditBankCard';
export { AddBankCardForm } from './ui/AddBankCardForm';
export { DeleteBankCardForm } from './ui/DeleteBankCardForm';
export { EditBankCardForm } from './ui/EditBankCardForm';
export type { AddBankCardBody, AddBankCardResponse } from './api/addBankCard'; export type { AddBankCardBody, AddBankCardResponse } from './api/addBankCard';
export type { DeleteBankCardPath, DeleteBankCardResponse } from './api/deleteBankCard'; export type { DeleteBankCardPath, DeleteBankCardResponse } from './api/deleteBankCard';
export type { EditBankCardBody, EditBankCardPath, EditBankCardResponse } from './api/editBankCard'; export type { EditBankCardBody, EditBankCardPath, EditBankCardResponse } from './api/editBankCard';
export type { AddBankCardFormValues } from './ui/AddBankCardForm'; export { useAddBankCard } from './hooks/useAddBankCard';
export type { EditBankCardFormValues } from './ui/EditBankCardForm'; export { useBankCards } from './hooks/useBankCards';
export { useDeleteBankCard } from './hooks/useDeleteBankCard';
export { useEditBankCard } from './hooks/useEditBankCard';
export type { BankCards, BankCard } from './types'; export type { BankCard, BankCards } from './types';
export type { AddBankCardFormValues } from './ui/AddBankCardForm';
export { AddBankCardForm } from './ui/AddBankCardForm';
export { DeleteBankCardForm } from './ui/DeleteBankCardForm';
export type { EditBankCardFormValues } from './ui/EditBankCardForm';
export { EditBankCardForm } from './ui/EditBankCardForm';

View File

@ -1,4 +1,4 @@
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
export type BankCards = paths['/bank-card']['get']['responses']['200']['content']['application/json'];
export type BankCard = paths['/bank-card/{id}']['get']['responses']['200']['content']['application/json']; export type BankCard = paths['/bank-card/{id}']['get']['responses']['200']['content']['application/json'];
export type BankCards = paths['/bank-card']['get']['responses']['200']['content']['application/json'];

View File

@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import AddBankCardForm from './AddBankCardForm.ui';
import type { AddBankCardFormProps } from './AddBankCardForm.ui'; import type { AddBankCardFormProps } from './AddBankCardForm.ui';
import AddBankCardForm from './AddBankCardForm.ui';
const renderForm = (props?: Partial<AddBankCardFormProps>) => { const renderForm = (props?: Partial<AddBankCardFormProps>) => {
const onSubmit = jest.fn(); const onSubmit = jest.fn();
@ -15,10 +15,10 @@ const renderForm = (props?: Partial<AddBankCardFormProps>) => {
const submitButtonElem = screen.getByText('Добавить'); const submitButtonElem = screen.getByText('Добавить');
return { return {
nameInputElem,
balanceInputElem, balanceInputElem,
submitButtonElem, nameInputElem,
onSubmit, onSubmit,
submitButtonElem,
}; };
}; };
@ -44,10 +44,10 @@ describe('Test AddBankCardForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
nameInputElem,
balanceInputElem, balanceInputElem,
submitButtonElem, nameInputElem,
onSubmit, onSubmit,
submitButtonElem,
} = renderForm(); } = renderForm();
await userEvent.type(nameInputElem, 'Тестовая карта'); await userEvent.type(nameInputElem, 'Тестовая карта');
@ -57,8 +57,8 @@ describe('Test AddBankCardForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({ expect(onSubmit).toHaveBeenCalledWith({
name: 'Тестовая карта',
balance: 500, balance: 500,
name: 'Тестовая карта',
}); });
}); });
}); });

View File

@ -1,7 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './AddBankCardForm.module.css';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -9,12 +5,9 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
export type AddBankCardFormValues = { import { Input } from '@/shared/ui/Input';
name: string;
balance: number;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import classes from './AddBankCardForm.module.css';
export type AddBankCardFormProps = BaseFormProps & { export type AddBankCardFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
@ -22,11 +15,18 @@ export type AddBankCardFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
export type AddBankCardFormValues = {
balance: number;
name: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const AddBankCardForm: FC<AddBankCardFormProps> = (props) => { const AddBankCardForm: FC<AddBankCardFormProps> = (props) => {
const { const {
ref,
disabled, disabled,
onSubmit, onSubmit,
ref,
...formProps ...formProps
} = props; } = props;
@ -40,26 +40,26 @@ const AddBankCardForm: FC<AddBankCardFormProps> = (props) => {
if (typeof name === 'string' && typeof balance === 'string') { if (typeof name === 'string' && typeof balance === 'string') {
onSubmit?.({ onSubmit?.({
name,
balance: Number(balance), balance: Number(balance),
name,
}); });
} }
}; };
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...formProps} {...formProps}
> >
<h2>Добавить карту</h2> <h2>Добавить карту</h2>
<Input <Input
required
disabled={disabled} disabled={disabled}
label="Название" label="Название"
name="name" name="name"
required
/> />
<Input <Input

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import DeleteBankCardForm from './DeleteBankCardForm.ui';
import type { DeleteBankCardFormProps } from './DeleteBankCardForm.ui'; import type { DeleteBankCardFormProps } from './DeleteBankCardForm.ui';
import DeleteBankCardForm from './DeleteBankCardForm.ui';
const deletingBankCard: DeleteBankCardFormProps['bankCard'] = { const deletingBankCard: DeleteBankCardFormProps['bankCard'] = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d', id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Тестовая карта', name: 'Тестовая карта',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z', updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<DeleteBankCardFormProps>) => { const renderForm = (props?: Partial<DeleteBankCardFormProps>) => {
@ -28,8 +28,8 @@ const renderForm = (props?: Partial<DeleteBankCardFormProps>) => {
const submitButtonElem = screen.getByText('Удалить'); const submitButtonElem = screen.getByText('Удалить');
return { return {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
}; };
}; };
@ -43,8 +43,8 @@ describe('Test DeleteExpensesListForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
} = renderForm(); } = renderForm();
await userEvent.click(submitButtonElem); await userEvent.click(submitButtonElem);

View File

@ -1,4 +1,3 @@
import type { BankCard } from '../../../types';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -6,7 +5,7 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import type { BankCard } from '../../../types';
export type DeleteBankCardFormProps = BaseFormProps & { export type DeleteBankCardFormProps = BaseFormProps & {
bankCard: BankCard; bankCard: BankCard;
@ -15,6 +14,8 @@ export type DeleteBankCardFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const DeleteBankCardForm: FC<DeleteBankCardFormProps> = (props) => { const DeleteBankCardForm: FC<DeleteBankCardFormProps> = (props) => {
const { const {
bankCard, bankCard,
@ -30,7 +31,7 @@ const DeleteBankCardForm: FC<DeleteBankCardFormProps> = (props) => {
}; };
return ( return (
<form ref={ref} onSubmit={onFormSubmitHandler} {...restProps}> <form onSubmit={onFormSubmitHandler} ref={ref} {...restProps}>
<h2>Удалить список расходов</h2> <h2>Удалить список расходов</h2>
<p> <p>

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import EditBankCardForm from './EditBankCardForm.ui';
import type { EditBankCardFormProps } from './EditBankCardForm.ui'; import type { EditBankCardFormProps } from './EditBankCardForm.ui';
import EditBankCardForm from './EditBankCardForm.ui';
const editingBankCard = { const editingBankCard = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d', id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Тестовая карта', name: 'Тестовая карта',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z', updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<EditBankCardFormProps>) => { const renderForm = (props?: Partial<EditBankCardFormProps>) => {
@ -30,10 +30,10 @@ const renderForm = (props?: Partial<EditBankCardFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить'); const submitButtonElem = screen.getByText('Сохранить');
return { return {
titleInputElem,
balanceInputElem, balanceInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -47,8 +47,8 @@ describe('Test EditBankCardForm', () => {
test('should inputs have default value', () => { test('should inputs have default value', () => {
const { const {
titleInputElem,
balanceInputElem, balanceInputElem,
titleInputElem,
} = renderForm(); } = renderForm();
expect(titleInputElem).toHaveValue('Тестовая карта'); expect(titleInputElem).toHaveValue('Тестовая карта');
expect(balanceInputElem).toHaveValue(1000); expect(balanceInputElem).toHaveValue(1000);
@ -68,10 +68,10 @@ describe('Test EditBankCardForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
balanceInputElem, balanceInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.clear(titleInputElem); await userEvent.clear(titleInputElem);
@ -84,8 +84,8 @@ describe('Test EditBankCardForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({ expect(onSubmit).toHaveBeenCalledWith({
name: 'Новое название карты',
balance: 5000, balance: 5000,
name: 'Новое название карты',
}); });
}); });
}); });

View File

@ -1,8 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './EditBankCardForm.module.css';
import type { BankCard } from '@/entity/cards';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -10,12 +5,11 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import type { BankCard } from '@/entity/cards';
export type EditBankCardFormValues = { import { Input } from '@/shared/ui/Input';
name: string;
balance: number; import classes from './EditBankCardForm.module.css';
};
export type EditBankCardFormProps = BaseFormProps & { export type EditBankCardFormProps = BaseFormProps & {
bankCard: BankCard; bankCard: BankCard;
@ -24,6 +18,13 @@ export type EditBankCardFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
export type EditBankCardFormValues = {
balance: number;
name: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const EditBankCardForm: FC<EditBankCardFormProps> = (props) => { const EditBankCardForm: FC<EditBankCardFormProps> = (props) => {
const { const {
bankCard, bankCard,
@ -43,27 +44,27 @@ const EditBankCardForm: FC<EditBankCardFormProps> = (props) => {
if (typeof name === 'string' && typeof balance === 'string') { if (typeof name === 'string' && typeof balance === 'string') {
onSubmit?.({ onSubmit?.({
name,
balance: Number(balance), balance: Number(balance),
name,
}); });
} }
}; };
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps} {...restProps}
> >
<h2>Добавить карту</h2> <h2>Добавить карту</h2>
<Input <Input
required
defaultValue={bankCard.name} defaultValue={bankCard.name}
disabled={disabled} disabled={disabled}
label="Название" label="Название"
name="name" name="name"
required
/> />
<Input <Input

View File

@ -1,9 +1,9 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
export type AddCashBody = paths['/cash']['post']['requestBody']['content']['application/json']; import { api } from '@/shared/api/client';
export type AdCashResponse = paths['/cash']['post']['responses']['200']['content']['application/json']; export type AdCashResponse = paths['/cash']['post']['responses']['200']['content']['application/json'];
export type AddCashBody = paths['/cash']['post']['requestBody']['content']['application/json'];
export async function addCash(data: AddCashBody) { export async function addCash(data: AddCashBody) {
const { data: result } = await api.post<AdCashResponse>('/cash', data); const { data: result } = await api.post<AdCashResponse>('/cash', data);

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type DeleteCashPath = paths['/cash/{id}']['delete']['parameters']['path']; export type DeleteCashPath = paths['/cash/{id}']['delete']['parameters']['path'];
export type DeleteCashResponse = paths['/cash/{id}']['delete']['responses']['200']['content']['application/json']; export type DeleteCashResponse = paths['/cash/{id}']['delete']['responses']['200']['content']['application/json'];

View File

@ -1,12 +1,12 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
export type EditCashPath = paths['/cash/{id}']['patch']['parameters']['path']; import { api } from '@/shared/api/client';
export type EditCashBody = paths['/cash/{id}']['patch']['requestBody']['content']['application/json']; export type EditCashBody = paths['/cash/{id}']['patch']['requestBody']['content']['application/json'];
export type EditCashPath = paths['/cash/{id}']['patch']['parameters']['path'];
export type EditCashResponse = paths['/cash/{id}']['patch']['responses']['200']['content']['application/json']; export type EditCashResponse = paths['/cash/{id}']['patch']['responses']['200']['content']['application/json'];
export async function editCash({ path, data }: { path: EditCashPath; data: EditCashBody }) { export async function editCash({ data, path }: { data: EditCashBody; path: EditCashPath }) {
const { data: result } = await api.patch<EditCashResponse>(`/cash/${path.id}`, data); const { data: result } = await api.patch<EditCashResponse>(`/cash/${path.id}`, data);
return result; return result;

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type CashResponse = paths['/cash']['get']['responses']['200']['content']['application/json']; type CashResponse = paths['/cash']['get']['responses']['200']['content']['application/json'];
export async function getCash() { export async function getCash() {

View File

@ -1,16 +1,18 @@
export type { AdCashResponse, AddCashBody } from './api/addCash';
export type { DeleteCashPath, DeleteCashResponse } from './api/deleteCash';
export type { EditCashBody, EditCashPath, EditCashResponse } from './api/editCash';
export { useAddCash } from './hooks/useAddCash'; export { useAddCash } from './hooks/useAddCash';
export { useCash } from './hooks/useCash'; export { useCash } from './hooks/useCash';
export { useDeleteCash } from './hooks/useDeleteCash'; export { useDeleteCash } from './hooks/useDeleteCash';
export { useEditCash } from './hooks/useEditCash'; export { useEditCash } from './hooks/useEditCash';
export type { CashItem, CashList } from './types';
export { AddCashForm } from './ui/AddCashForm'; export { AddCashForm } from './ui/AddCashForm';
export { DeleteCashForm } from './ui/DeleteCashForm';
export { EditCashForm } from './ui/EditCashForm';
export type { CashList, CashItem } from './types';
export type { AddCashFormValues } from './ui/AddCashForm'; export type { AddCashFormValues } from './ui/AddCashForm';
export { DeleteCashForm } from './ui/DeleteCashForm';
export { EditCashForm } from './ui/EditCashForm';
export type { EditCashFormValues } from './ui/EditCashForm'; export type { EditCashFormValues } from './ui/EditCashForm';
export type { AddCashBody, AdCashResponse } from './api/addCash';
export type { DeleteCashPath, DeleteCashResponse } from './api/deleteCash';
export type { EditCashBody, EditCashPath, EditCashResponse } from './api/editCash';

View File

@ -1,4 +1,4 @@
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
export type CashList = paths['/cash']['get']['responses']['200']['content']['application/json'];
export type CashItem = paths['/cash/{id}']['get']['responses']['200']['content']['application/json']; export type CashItem = paths['/cash/{id}']['get']['responses']['200']['content']['application/json'];
export type CashList = paths['/cash']['get']['responses']['200']['content']['application/json'];

View File

@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import AddCashForm from './AddCashForm.ui';
import type { AddCashFormProps } from './AddCashForm.ui'; import type { AddCashFormProps } from './AddCashForm.ui';
import AddCashForm from './AddCashForm.ui';
const renderForm = (props?: Partial<AddCashFormProps>) => { const renderForm = (props?: Partial<AddCashFormProps>) => {
const onSubmit = jest.fn(); const onSubmit = jest.fn();
@ -15,10 +15,10 @@ const renderForm = (props?: Partial<AddCashFormProps>) => {
const submitButtonElem = screen.getByText('Добавить'); const submitButtonElem = screen.getByText('Добавить');
return { return {
nameInputElem,
balanceInputElem, balanceInputElem,
submitButtonElem, nameInputElem,
onSubmit, onSubmit,
submitButtonElem,
}; };
}; };
@ -44,10 +44,10 @@ describe('Test AddCashForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
nameInputElem,
balanceInputElem, balanceInputElem,
submitButtonElem, nameInputElem,
onSubmit, onSubmit,
submitButtonElem,
} = renderForm(); } = renderForm();
await userEvent.type(nameInputElem, 'Наличные'); await userEvent.type(nameInputElem, 'Наличные');
@ -57,8 +57,8 @@ describe('Test AddCashForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({ expect(onSubmit).toHaveBeenCalledWith({
name: 'Наличные',
balance: 500, balance: 500,
name: 'Наличные',
}); });
}); });
}); });

View File

@ -1,7 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './AddCashForm.module.css';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -9,12 +5,9 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
export type AddCashFormValues = { import { Input } from '@/shared/ui/Input';
name: string;
balance: number;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import classes from './AddCashForm.module.css';
export type AddCashFormProps = BaseFormProps & { export type AddCashFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
@ -22,11 +15,18 @@ export type AddCashFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
export type AddCashFormValues = {
balance: number;
name: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const AddCashForm: FC<AddCashFormProps> = (props) => { const AddCashForm: FC<AddCashFormProps> = (props) => {
const { const {
ref,
disabled, disabled,
onSubmit, onSubmit,
ref,
...formProps ...formProps
} = props; } = props;
@ -40,26 +40,26 @@ const AddCashForm: FC<AddCashFormProps> = (props) => {
if (typeof name === 'string' && typeof balance === 'string') { if (typeof name === 'string' && typeof balance === 'string') {
onSubmit?.({ onSubmit?.({
name,
balance: Number(balance), balance: Number(balance),
name,
}); });
} }
}; };
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...formProps} {...formProps}
> >
<h2>Добавить наличные</h2> <h2>Добавить наличные</h2>
<Input <Input
required
disabled={disabled} disabled={disabled}
label="Название" label="Название"
name="name" name="name"
required
/> />
<Input <Input

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import DeleteCashForm from './DeleteCashForm.ui';
import type { DeleteCashFormProps } from './DeleteCashForm.ui'; import type { DeleteCashFormProps } from './DeleteCashForm.ui';
import DeleteCashForm from './DeleteCashForm.ui';
const deletingCash: DeleteCashFormProps['cash'] = { const deletingCash: DeleteCashFormProps['cash'] = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d', id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Наличные', name: 'Наличные',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z', updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<DeleteCashFormProps>) => { const renderForm = (props?: Partial<DeleteCashFormProps>) => {
@ -28,8 +28,8 @@ const renderForm = (props?: Partial<DeleteCashFormProps>) => {
const submitButtonElem = screen.getByText('Удалить'); const submitButtonElem = screen.getByText('Удалить');
return { return {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
}; };
}; };
@ -43,8 +43,8 @@ describe('Test DeleteCashForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
} = renderForm(); } = renderForm();
await userEvent.click(submitButtonElem); await userEvent.click(submitButtonElem);

View File

@ -1,4 +1,3 @@
import type { CashItem } from '../../../types';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -6,7 +5,7 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import type { CashItem } from '../../../types';
export type DeleteCashFormProps = BaseFormProps & { export type DeleteCashFormProps = BaseFormProps & {
cash: CashItem; cash: CashItem;
@ -15,6 +14,8 @@ export type DeleteCashFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const DeleteCashForm: FC<DeleteCashFormProps> = (props) => { const DeleteCashForm: FC<DeleteCashFormProps> = (props) => {
const { const {
cash, cash,
@ -30,7 +31,7 @@ const DeleteCashForm: FC<DeleteCashFormProps> = (props) => {
}; };
return ( return (
<form ref={ref} onSubmit={onFormSubmitHandler} {...restProps}> <form onSubmit={onFormSubmitHandler} ref={ref} {...restProps}>
<h2>Удалить наличные</h2> <h2>Удалить наличные</h2>
<p> <p>

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import EditCashForm from './EditCashForm.ui';
import type { EditCashFormProps } from './EditCashForm.ui'; import type { EditCashFormProps } from './EditCashForm.ui';
import EditCashForm from './EditCashForm.ui';
const editingCash = { const editingCash = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d', id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Наличные', name: 'Наличные',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z', updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<EditCashFormProps>) => { const renderForm = (props?: Partial<EditCashFormProps>) => {
@ -30,10 +30,10 @@ const renderForm = (props?: Partial<EditCashFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить'); const submitButtonElem = screen.getByText('Сохранить');
return { return {
titleInputElem,
balanceInputElem, balanceInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -47,8 +47,8 @@ describe('Test EditCashForm', () => {
test('should inputs have default value', () => { test('should inputs have default value', () => {
const { const {
titleInputElem,
balanceInputElem, balanceInputElem,
titleInputElem,
} = renderForm(); } = renderForm();
expect(titleInputElem).toHaveValue('Наличные'); expect(titleInputElem).toHaveValue('Наличные');
expect(balanceInputElem).toHaveValue(1000); expect(balanceInputElem).toHaveValue(1000);
@ -68,10 +68,10 @@ describe('Test EditCashForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
balanceInputElem, balanceInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.clear(titleInputElem); await userEvent.clear(titleInputElem);
@ -84,8 +84,8 @@ describe('Test EditCashForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({ expect(onSubmit).toHaveBeenCalledWith({
name: 'Новое название наличных',
balance: 5000, balance: 5000,
name: 'Новое название наличных',
}); });
}); });
}); });

View File

@ -1,8 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './EditCashForm.module.css';
import type { CashItem } from '@/entity/cash';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -10,12 +5,11 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import type { CashItem } from '@/entity/cash';
export type EditCashFormValues = { import { Input } from '@/shared/ui/Input';
name: string;
balance: number; import classes from './EditCashForm.module.css';
};
export type EditCashFormProps = BaseFormProps & { export type EditCashFormProps = BaseFormProps & {
cash: CashItem; cash: CashItem;
@ -24,6 +18,13 @@ export type EditCashFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
export type EditCashFormValues = {
balance: number;
name: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const EditCashForm: FC<EditCashFormProps> = (props) => { const EditCashForm: FC<EditCashFormProps> = (props) => {
const { const {
cash, cash,
@ -43,27 +44,27 @@ const EditCashForm: FC<EditCashFormProps> = (props) => {
if (typeof name === 'string' && typeof balance === 'string') { if (typeof name === 'string' && typeof balance === 'string') {
onSubmit?.({ onSubmit?.({
name,
balance: Number(balance), balance: Number(balance),
name,
}); });
} }
}; };
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps} {...restProps}
> >
<h2>Редактировать наличные</h2> <h2>Редактировать наличные</h2>
<Input <Input
required
defaultValue={cash.name} defaultValue={cash.name}
disabled={disabled} disabled={disabled}
label="Название" label="Название"
name="name" name="name"
required
/> />
<Input <Input

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type AddExpenseItemBody = paths['/expenses/list/{id}/items']['post']['requestBody']['content']['application/json']; export type AddExpenseItemBody = paths['/expenses/list/{id}/items']['post']['requestBody']['content']['application/json'];
export type AddExpenseItemPath = paths['/expenses/list/{id}/items']['post']['parameters']['path']; export type AddExpenseItemPath = paths['/expenses/list/{id}/items']['post']['parameters']['path'];
export type AddExpenseItemSuccessResponse = paths['/expenses/list/{id}/items']['post']['responses']['200']['content']['application/json']; export type AddExpenseItemSuccessResponse = paths['/expenses/list/{id}/items']['post']['responses']['200']['content']['application/json'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type DeleteExpenseItemPath = paths['/expenses/list/{listId}/items/{id}']['delete']['parameters']['path']; export type DeleteExpenseItemPath = paths['/expenses/list/{listId}/items/{id}']['delete']['parameters']['path'];
export type DeleteExpenseItemSuccessResponse = paths['/expenses/list/{id}/items']['post']['responses']['200']['content']['application/json']; export type DeleteExpenseItemSuccessResponse = paths['/expenses/list/{id}/items']['post']['responses']['200']['content']['application/json'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type EditExpenseItemBody = paths['/expenses/list/{listId}/items/{id}']['patch']['requestBody']['content']['application/json']; export type EditExpenseItemBody = paths['/expenses/list/{listId}/items/{id}']['patch']['requestBody']['content']['application/json'];
export type EditExpenseItemPath = paths['/expenses/list/{listId}/items/{id}']['patch']['parameters']['path']; export type EditExpenseItemPath = paths['/expenses/list/{listId}/items/{id}']['patch']['parameters']['path'];
export type EditExpenseItemSuccessResponse = paths['/expenses/list/{listId}/items/{id}']['patch']['responses']['200']['content']['application/json']; export type EditExpenseItemSuccessResponse = paths['/expenses/list/{listId}/items/{id}']['patch']['responses']['200']['content']['application/json'];

View File

@ -2,10 +2,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { QueryKeys } from '@/shared/api/queryKeys'; import { QueryKeys } from '@/shared/api/queryKeys';
import { deleteExpenseItem } from '../api/deleteExpenseItem';
import type { DeleteExpenseItemPath } from '../api/deleteExpenseItem'; import type { DeleteExpenseItemPath } from '../api/deleteExpenseItem';
import { deleteExpenseItem } from '../api/deleteExpenseItem';
export const useDeleteExpenseItem = (path: DeleteExpenseItemPath) => { export const useDeleteExpenseItem = (path: DeleteExpenseItemPath) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@ -1,17 +1,17 @@
export type { AddExpenseItemBody, AddExpenseItemPath, AddExpenseItemSuccessResponse } from './api/addExpenseItem';
export type { DeleteExpenseItemPath, DeleteExpenseItemSuccessResponse } from './api/deleteExpenseItem';
export type { EditExpenseItemBody, EditExpenseItemPath, EditExpenseItemSuccessResponse } from './api/editExpenseItem';
export { useAddExpenseItem } from './hooks/useAddExpenseItem'; export { useAddExpenseItem } from './hooks/useAddExpenseItem';
export { useDeleteExpenseItem } from './hooks/useDeleteExpenseItem'; export { useDeleteExpenseItem } from './hooks/useDeleteExpenseItem';
export { useEditExpenseItem } from './hooks/useEditExpenseItem'; export { useEditExpenseItem } from './hooks/useEditExpenseItem';
export type { AddExpenseItemSuccessResponse, AddExpenseItemBody, AddExpenseItemPath } from './api/addExpenseItem'; export type { ExpenseListItem } from './types';
export type { DeleteExpenseItemPath, DeleteExpenseItemSuccessResponse } from './api/deleteExpenseItem';
export type { EditExpenseItemBody, EditExpenseItemPath, EditExpenseItemSuccessResponse } from './api/editExpenseItem';
export type { AddExpenseItemFormValues } from './ui/AddExpenseItemForm/AddExpenseItemForm.ui'; export type { AddExpenseItemFormValues } from './ui/AddExpenseItemForm/AddExpenseItemForm.ui';
export { default as AddExpenseItemForm } from './ui/AddExpenseItemForm/AddExpenseItemForm.ui'; export { default as AddExpenseItemForm } from './ui/AddExpenseItemForm/AddExpenseItemForm.ui';
export type { EditExpenseItemFormValues } from './ui/EditExpenseItemForm/EditExpenseItemForm.ui';
export { default as EditExpenseItemForm } from './ui/EditExpenseItemForm/EditExpenseItemForm.ui';
export { default as DeleteExpenseItemForm } from './ui/DeleteExpenseItemForm/DeleteExpenseItemForm.ui'; export { default as DeleteExpenseItemForm } from './ui/DeleteExpenseItemForm/DeleteExpenseItemForm.ui';
export type { ExpenseListItem } from './types'; export type { EditExpenseItemFormValues } from './ui/EditExpenseItemForm/EditExpenseItemForm.ui';
export { default as EditExpenseItemForm } from './ui/EditExpenseItemForm/EditExpenseItemForm.ui';

View File

@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import AddExpenseItemForm from './AddExpenseItemForm.ui';
import type { AddExpenseItemFormProps } from './AddExpenseItemForm.ui'; import type { AddExpenseItemFormProps } from './AddExpenseItemForm.ui';
import AddExpenseItemForm from './AddExpenseItemForm.ui';
const renderForm = (props?: Partial<AddExpenseItemFormProps>) => { const renderForm = (props?: Partial<AddExpenseItemFormProps>) => {
const onSubmit = jest.fn(); const onSubmit = jest.fn();
@ -18,13 +18,13 @@ const renderForm = (props?: Partial<AddExpenseItemFormProps>) => {
const submitButtonElem = screen.getByText('Добавить'); const submitButtonElem = screen.getByText('Добавить');
return { return {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
countInputElem, countInputElem,
submitButtonElem, dateInputElem,
descriptionInputElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -50,13 +50,13 @@ describe('Test AddExpenseItemForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
countInputElem, countInputElem,
submitButtonElem, dateInputElem,
descriptionInputElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.type(titleInputElem, 'Название элемента'); await userEvent.type(titleInputElem, 'Название элемента');
@ -69,12 +69,12 @@ describe('Test AddExpenseItemForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({ expect(onSubmit).toHaveBeenCalledWith({
title: 'Название элемента',
description: 'Описание элемента',
date: '2025-08-29T00:00:00.000Z',
amount: 12, amount: 12,
count: 1, count: 1,
currency: 'RUB', currency: 'RUB',
date: '2025-08-29T00:00:00.000Z',
description: 'Описание элемента',
title: 'Название элемента',
}); });
}); });
}); });

View File

@ -1,7 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './AddExpenseItemForm.module.css';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -9,16 +5,9 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
export type AddExpenseItemFormValues = { import { Input } from '@/shared/ui/Input';
title: string;
description: string;
date: string;
amount: number;
count: number;
currency: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import classes from './AddExpenseItemForm.module.css';
export type AddExpenseItemFormProps = BaseFormProps & { export type AddExpenseItemFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
@ -26,6 +15,17 @@ export type AddExpenseItemFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
export type AddExpenseItemFormValues = {
amount: number;
count: number;
currency: string;
date: string;
description: string;
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const AddExpenseItemForm: FC<AddExpenseItemFormProps> = (props) => { const AddExpenseItemForm: FC<AddExpenseItemFormProps> = (props) => {
const { const {
disabled, disabled,
@ -47,30 +47,30 @@ const AddExpenseItemForm: FC<AddExpenseItemFormProps> = (props) => {
if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string' && typeof count === 'string') { if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string' && typeof count === 'string') {
onSubmit?.({ onSubmit?.({
title,
date: new Date(date).toISOString(),
amount: Number(amount), amount: Number(amount),
description,
currency: 'RUB',
count: Number(count), count: Number(count),
currency: 'RUB',
date: new Date(date).toISOString(),
description,
title,
}); });
} }
}; };
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...formProps} {...formProps}
> >
<h2>Добавить список расходов</h2> <h2>Добавить список расходов</h2>
<Input <Input
required
disabled={disabled} disabled={disabled}
label="Название" label="Название"
name="title" name="title"
required
/> />
<Input <Input
@ -80,18 +80,18 @@ const AddExpenseItemForm: FC<AddExpenseItemFormProps> = (props) => {
/> />
<Input <Input
required
disabled={disabled} disabled={disabled}
label="Дата" label="Дата"
name="date" name="date"
required
type="date" type="date"
/> />
<Input <Input
required
disabled={disabled} disabled={disabled}
label="Сумма" label="Сумма"
name="amount" name="amount"
required
step="0.01" step="0.01"
type="number" type="number"
/> />

View File

@ -1,21 +1,21 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import DeleteExpenseItemForm from './DeleteExpenseItemForm.ui';
import type { DeleteExpenseItemFormProps } from './DeleteExpenseItemForm.ui'; import type { DeleteExpenseItemFormProps } from './DeleteExpenseItemForm.ui';
import DeleteExpenseItemForm from './DeleteExpenseItemForm.ui';
const deletingItem = { const deletingItem = {
id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6',
title: 'Mock item',
amount: 12, amount: 12,
count: 12, count: 12,
updatedAt: '2025-08-29T07:57:40.503Z',
expenseListId: '10cc093c-3875-4d33-a40a-df526266e262',
createdAt: '2025-08-29T07:57:40.503Z', createdAt: '2025-08-29T07:57:40.503Z',
currency: 'RUB', currency: 'RUB',
date: '2025-08-22T00:00:03.000Z', date: '2025-08-22T00:00:03.000Z',
description: 'Mock description', description: 'Mock description',
expenseListId: '10cc093c-3875-4d33-a40a-df526266e262',
id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6',
title: 'Mock item',
updatedAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<DeleteExpenseItemFormProps>) => { const renderForm = (props?: Partial<DeleteExpenseItemFormProps>) => {
@ -33,8 +33,8 @@ const renderForm = (props?: Partial<DeleteExpenseItemFormProps>) => {
const submitButtonElem = screen.getByText('Удалить'); const submitButtonElem = screen.getByText('Удалить');
return { return {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
}; };
}; };
@ -48,8 +48,8 @@ describe('Test DeleteExpenseItemForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
} = renderForm(); } = renderForm();
await userEvent.click(submitButtonElem); await userEvent.click(submitButtonElem);

View File

@ -1,4 +1,3 @@
import type { ExpenseListItem } from '../../types';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -6,21 +5,23 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import type { ExpenseListItem } from '../../types';
export type DeleteExpenseItemFormProps = BaseFormProps & { export type DeleteExpenseItemFormProps = BaseFormProps & {
disabled?: boolean;
item: ExpenseListItem; item: ExpenseListItem;
onSubmit?: (item: ExpenseListItem) => Promise<void> | void; onSubmit?: (item: ExpenseListItem) => Promise<void> | void;
disabled?: boolean;
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const DeleteExpenseItemForm: FC<DeleteExpenseItemFormProps> = (props) => { const DeleteExpenseItemForm: FC<DeleteExpenseItemFormProps> = (props) => {
const { const {
disabled,
item, item,
onSubmit, onSubmit,
ref, ref,
disabled,
...restProps ...restProps
} = props; } = props;
const onFormSubmitHandler: FormEventHandler<HTMLFormElement> = (event) => { const onFormSubmitHandler: FormEventHandler<HTMLFormElement> = (event) => {
@ -29,7 +30,7 @@ const DeleteExpenseItemForm: FC<DeleteExpenseItemFormProps> = (props) => {
}; };
return ( return (
<form ref={ref} onSubmit={onFormSubmitHandler} {...restProps}> <form onSubmit={onFormSubmitHandler} ref={ref} {...restProps}>
<h2>Удалить элемент ?</h2> <h2>Удалить элемент ?</h2>
<button disabled={disabled} type="submit">Удалить</button> <button disabled={disabled} type="submit">Удалить</button>

View File

@ -1,21 +1,21 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import EditExpenseItemForm from './EditExpenseItemForm.ui';
import type { EditExpenseItemFormProps } from './EditExpenseItemForm.ui'; import type { EditExpenseItemFormProps } from './EditExpenseItemForm.ui';
import EditExpenseItemForm from './EditExpenseItemForm.ui';
const editingItem = { const editingItem = {
id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6',
title: 'Mock item',
amount: 12, amount: 12,
count: 12, count: 12,
updatedAt: '2025-08-29T07:57:40.503Z',
expenseListId: '10cc093c-3875-4d33-a40a-df526266e262',
createdAt: '2025-08-29T07:57:40.503Z', createdAt: '2025-08-29T07:57:40.503Z',
currency: 'RUB', currency: 'RUB',
date: '2025-08-22T00:00:03.000Z', date: '2025-08-22T00:00:03.000Z',
description: 'Mock description', description: 'Mock description',
expenseListId: '10cc093c-3875-4d33-a40a-df526266e262',
id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6',
title: 'Mock item',
updatedAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<EditExpenseItemFormProps>) => { const renderForm = (props?: Partial<EditExpenseItemFormProps>) => {
@ -38,13 +38,13 @@ const renderForm = (props?: Partial<EditExpenseItemFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить'); const submitButtonElem = screen.getByText('Сохранить');
return { return {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
countInputElem, countInputElem,
submitButtonElem, dateInputElem,
descriptionInputElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -58,11 +58,11 @@ describe('Test EditExpenseItemForm', () => {
test('should inputs have default value', () => { test('should inputs have default value', () => {
const { const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
countInputElem, countInputElem,
dateInputElem,
descriptionInputElem,
titleInputElem,
} = renderForm(); } = renderForm();
expect(titleInputElem).toHaveValue('Mock item'); expect(titleInputElem).toHaveValue('Mock item');
expect(descriptionInputElem).toHaveValue('Mock description'); expect(descriptionInputElem).toHaveValue('Mock description');
@ -85,13 +85,13 @@ describe('Test EditExpenseItemForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
countInputElem, countInputElem,
submitButtonElem, dateInputElem,
descriptionInputElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.clear(titleInputElem); await userEvent.clear(titleInputElem);
@ -113,12 +113,12 @@ describe('Test EditExpenseItemForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({ expect(onSubmit).toHaveBeenCalledWith({
title: 'Название элемента',
description: 'Описание элемента',
date: '2025-08-29T00:00:00.000Z',
amount: 12, amount: 12,
count: 1, count: 1,
currency: 'RUB', currency: 'RUB',
date: '2025-08-29T00:00:00.000Z',
description: 'Описание элемента',
title: 'Название элемента',
}); });
}); });
}); });

View File

@ -1,8 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './EditExpenseItemForm.module.css';
import type { ExpenseListItem } from '../../types';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -10,29 +5,35 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
import { Input } from '@/shared/ui/Input';
import type { ExpenseListItem } from '../../types';
import classes from './EditExpenseItemForm.module.css';
export type EditExpenseItemFormProps = BaseFormProps & {
disabled?: boolean;
item: ExpenseListItem;
onSubmit?: (data: EditExpenseItemFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>;
};
export type EditExpenseItemFormValues = { export type EditExpenseItemFormValues = {
title: string;
description: string;
date: string;
amount: number; amount: number;
count: number; count: number;
currency: string; currency: string;
date: string;
description: string;
title: string;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
export type EditExpenseItemFormProps = BaseFormProps & {
disabled?: boolean;
onSubmit?: (data: EditExpenseItemFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>;
item: ExpenseListItem;
};
const EditExpenseItemForm: FC<EditExpenseItemFormProps> = (props) => { const EditExpenseItemForm: FC<EditExpenseItemFormProps> = (props) => {
const { const {
disabled, disabled,
item,
onSubmit, onSubmit,
ref, ref,
item,
...formProps ...formProps
} = props; } = props;
@ -49,21 +50,21 @@ const EditExpenseItemForm: FC<EditExpenseItemFormProps> = (props) => {
if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string' && typeof count === 'string') { if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string' && typeof count === 'string') {
await onSubmit?.({ await onSubmit?.({
title,
date: new Date(date).toISOString(),
amount: Number(amount), amount: Number(amount),
description,
currency: 'RUB',
count: Number(count), count: Number(count),
currency: 'RUB',
date: new Date(date).toISOString(),
description,
title,
}); });
} }
}; };
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...formProps} {...formProps}
> >
<h2>Добавить список расходов</h2> <h2>Добавить список расходов</h2>

View File

@ -1,9 +1,9 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
export type AddExpenseListPath = paths['/expenses/list']['post']['parameters']['path']; import { api } from '@/shared/api/client';
export type AddExpenseListBody = paths['/expenses/list']['post']['requestBody']['content']['application/json']; export type AddExpenseListBody = paths['/expenses/list']['post']['requestBody']['content']['application/json'];
export type AddExpenseListPath = paths['/expenses/list']['post']['parameters']['path'];
export type AddExpenseListSuccessResponse = paths['/expenses/list']['post']['responses']['200']['content']['application/json']; export type AddExpenseListSuccessResponse = paths['/expenses/list']['post']['responses']['200']['content']['application/json'];
export async function addExpensesList(data: AddExpenseListBody) { export async function addExpensesList(data: AddExpenseListBody) {

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type DeleteExpenseListPath = paths['/expenses/list/{id}']['delete']['parameters']['path']; export type DeleteExpenseListPath = paths['/expenses/list/{id}']['delete']['parameters']['path'];
export type DeleteExpenseSuccessResponse = paths['/expenses/list/{id}']['delete']['responses']['200']['content']['application/json']; export type DeleteExpenseSuccessResponse = paths['/expenses/list/{id}']['delete']['responses']['200']['content']['application/json'];

View File

@ -1,14 +1,14 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
export type EditExpenseListPath = paths['/expenses/list/{id}']['patch']['parameters']['path']; import { api } from '@/shared/api/client';
export type EditExpenseListBody = paths['/expenses/list/{id}']['patch']['requestBody']['content']['application/json']; export type EditExpenseListBody = paths['/expenses/list/{id}']['patch']['requestBody']['content']['application/json'];
export type EditExpenseListPath = paths['/expenses/list/{id}']['patch']['parameters']['path'];
export type EditExpenseListSuccessResponse = paths['/expenses/list/{id}']['patch']['responses']['200']['content']['application/json']; export type EditExpenseListSuccessResponse = paths['/expenses/list/{id}']['patch']['responses']['200']['content']['application/json'];
export async function editExpensesList({ data, path }: { export async function editExpensesList({ data, path }: {
path: EditExpenseListPath;
data: EditExpenseListBody; data: EditExpenseListBody;
path: EditExpenseListPath;
}) { }) {
const { id } = path; const { id } = path;
const { data: result } = await api.patch<EditExpenseListSuccessResponse>(`/expenses/list/${id}`, data); const { data: result } = await api.patch<EditExpenseListSuccessResponse>(`/expenses/list/${id}`, data);

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type ExpenseListPath = paths['/expenses/list/{id}']['get']['parameters']['path']; type ExpenseListPath = paths['/expenses/list/{id}']['get']['parameters']['path'];
type ExpenseListResponse = paths['/expenses/list/{id}']['get']['responses']['200']['content']['application/json']; type ExpenseListResponse = paths['/expenses/list/{id}']['get']['responses']['200']['content']['application/json'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type ExpensesListResponse = paths['/expenses/list']['get']['responses']['200']['content']['application/json']; type ExpensesListResponse = paths['/expenses/list']['get']['responses']['200']['content']['application/json'];
export async function getExpenseLists() { export async function getExpenseLists() {

View File

@ -2,10 +2,10 @@ import { useQuery } from '@tanstack/react-query';
import { QueryKeys } from '@/shared/api/queryKeys'; import { QueryKeys } from '@/shared/api/queryKeys';
import { getExpenseList } from '../api/getExpenseList';
import type { ExpenseList } from '../types'; import type { ExpenseList } from '../types';
import { getExpenseList } from '../api/getExpenseList';
export const useExpenseList = (id: ExpenseList['id']) => useQuery({ export const useExpenseList = (id: ExpenseList['id']) => useQuery({
queryFn: async () => getExpenseList({ id }), queryFn: async () => getExpenseList({ id }),
queryKey: [QueryKeys.ExpenseList, id], queryKey: [QueryKeys.ExpenseList, id],

View File

@ -1,10 +1,14 @@
export type { ExpenseList } from './types'; export type { AddExpenseListBody, AddExpenseListPath, AddExpenseListSuccessResponse } from './api/addExpensesList';
export { useDeleteExpensesList } from './hooks/useDeleteExpensesList'; export type { DeleteExpenseListPath, DeleteExpenseSuccessResponse } from './api/deleteExpensesList';
export { useAddExpensesList } from './hooks/useAddExpensesList'; export type { EditExpenseListBody, EditExpenseListPath, EditExpenseListSuccessResponse } from './api/editExpensesList';
export { useExpensesList } from './hooks/useExpensesList';
export { useEditExpensesList } from './hooks/useEditExpensesList';
export { useAddExpensesList } from './hooks/useAddExpensesList';
export { useDeleteExpensesList } from './hooks/useDeleteExpensesList';
export { useEditExpensesList } from './hooks/useEditExpensesList';
export { useExpenseList } from './hooks/useExpenseList'; export { useExpenseList } from './hooks/useExpenseList';
export { useExpensesList } from './hooks/useExpensesList';
export type { ExpenseList } from './types';
export type { AddExpenseListFormValues } from './ui/AddExpensesListForm/ui/AddExpensesListForm.ui'; export type { AddExpenseListFormValues } from './ui/AddExpensesListForm/ui/AddExpensesListForm.ui';
export { default as AddExpensesListForm } from './ui/AddExpensesListForm/ui/AddExpensesListForm.ui'; export { default as AddExpensesListForm } from './ui/AddExpensesListForm/ui/AddExpensesListForm.ui';
@ -13,7 +17,3 @@ export { default as DeleteExpensesListForm } from './ui/DeleteExpensesListForm/u
export type { EditExpenseListFormValues } from './ui/EditExpensesListForm/ui/EditExpensesListForm.ui'; export type { EditExpenseListFormValues } from './ui/EditExpensesListForm/ui/EditExpensesListForm.ui';
export { default as EditExpensesListForm } from './ui/EditExpensesListForm/ui/EditExpensesListForm.ui'; export { default as EditExpensesListForm } from './ui/EditExpensesListForm/ui/EditExpensesListForm.ui';
export type { AddExpenseListPath, AddExpenseListBody, AddExpenseListSuccessResponse } from './api/addExpensesList';
export type { DeleteExpenseListPath, DeleteExpenseSuccessResponse } from './api/deleteExpensesList';
export type { EditExpenseListPath, EditExpenseListBody, EditExpenseListSuccessResponse } from './api/editExpensesList';

View File

@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import AddExpensesListForm from './AddExpensesListForm.ui';
import type { AddExpenseListFormProps } from './AddExpensesListForm.ui'; import type { AddExpenseListFormProps } from './AddExpensesListForm.ui';
import AddExpensesListForm from './AddExpensesListForm.ui';
const renderForm = (props?: Partial<AddExpenseListFormProps>) => { const renderForm = (props?: Partial<AddExpenseListFormProps>) => {
const onSubmit = jest.fn(); const onSubmit = jest.fn();
@ -14,9 +14,9 @@ const renderForm = (props?: Partial<AddExpenseListFormProps>) => {
const submitButtonElem = screen.getByText('Добавить'); const submitButtonElem = screen.getByText('Добавить');
return { return {
titleInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -42,9 +42,9 @@ describe('Test AddExpensesListForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.type(titleInputElem, 'Название списка'); await userEvent.type(titleInputElem, 'Название списка');

View File

@ -1,31 +1,31 @@
import type {
FC,
FormEventHandler,
FormHTMLAttributes,
RefObject,
} from 'react';
import { Input } from '@/shared/ui/Input'; import { Input } from '@/shared/ui/Input';
import classes from './AddExpenseListForm.module.css'; import classes from './AddExpenseListForm.module.css';
import type {
FormHTMLAttributes,
RefObject,
FormEventHandler,
FC,
} from 'react';
export type AddExpenseListFormValues = {
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
export type AddExpenseListFormProps = BaseFormProps & { export type AddExpenseListFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
onSubmit: (data: AddExpenseListFormValues) => Promise<void> | void; onSubmit: (data: AddExpenseListFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
export type AddExpenseListFormValues = {
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const AddExpensesListForm: FC<AddExpenseListFormProps> = (props) => { const AddExpensesListForm: FC<AddExpenseListFormProps> = (props) => {
const { const {
ref,
disabled, disabled,
onSubmit, onSubmit,
ref,
...restProps ...restProps
} = props; } = props;
@ -45,9 +45,9 @@ const AddExpensesListForm: FC<AddExpenseListFormProps> = (props) => {
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps} {...restProps}
> >
<h2>Добавить список расходов</h2> <h2>Добавить список расходов</h2>

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import DeleteExpensesListForm from './DeleteExpensesListForm.ui';
import type { DeleteExpenseListFormProps } from './DeleteExpensesListForm.ui'; import type { DeleteExpenseListFormProps } from './DeleteExpensesListForm.ui';
import DeleteExpensesListForm from './DeleteExpensesListForm.ui';
const deletingList = { const deletingList = {
id: '10cc093c-3875-4d33-a40a-df526266e262',
title: 'Mock list',
items: [],
updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z', createdAt: '2025-08-29T07:57:40.503Z',
id: '10cc093c-3875-4d33-a40a-df526266e262',
items: [],
title: 'Mock list',
updatedAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<DeleteExpenseListFormProps>) => { const renderForm = (props?: Partial<DeleteExpenseListFormProps>) => {
@ -28,8 +28,8 @@ const renderForm = (props?: Partial<DeleteExpenseListFormProps>) => {
const submitButtonElem = screen.getByText('Удалить'); const submitButtonElem = screen.getByText('Удалить');
return { return {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
}; };
}; };
@ -43,8 +43,8 @@ describe('Test DeleteExpensesListForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
} = renderForm(); } = renderForm();
await userEvent.click(submitButtonElem); await userEvent.click(submitButtonElem);

View File

@ -1,4 +1,3 @@
import type { ExpenseList } from '@/entity/expenses/list';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -6,21 +5,23 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import type { ExpenseList } from '@/entity/expenses/list';
export type DeleteExpenseListFormProps = BaseFormProps & { export type DeleteExpenseListFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
list: ExpenseList;
onSubmit?: (list: ExpenseList) => Promise<void> | void; onSubmit?: (list: ExpenseList) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
list: ExpenseList;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const DeleteExpensesListForm: FC<DeleteExpenseListFormProps> = (props) => { const DeleteExpensesListForm: FC<DeleteExpenseListFormProps> = (props) => {
const { const {
ref,
disabled, disabled,
onSubmit,
list, list,
onSubmit,
ref,
...restProps ...restProps
} = props; } = props;
@ -30,7 +31,7 @@ const DeleteExpensesListForm: FC<DeleteExpenseListFormProps> = (props) => {
}; };
return ( return (
<form ref={ref} onSubmit={onFormSubmitHandler} {...restProps}> <form onSubmit={onFormSubmitHandler} ref={ref} {...restProps}>
<h2>Удалить список расходов</h2> <h2>Удалить список расходов</h2>
<p> <p>

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import EditExpensesListForm from './EditExpensesListForm.ui';
import type { EditExpensesListFormProps } from './EditExpensesListForm.ui'; import type { EditExpensesListFormProps } from './EditExpensesListForm.ui';
import EditExpensesListForm from './EditExpensesListForm.ui';
const editingList = { const editingList = {
id: '10cc093c-3875-4d33-a40a-df526266e262',
title: 'Mock list',
items: [],
updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z', createdAt: '2025-08-29T07:57:40.503Z',
id: '10cc093c-3875-4d33-a40a-df526266e262',
items: [],
title: 'Mock list',
updatedAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<EditExpensesListFormProps>) => { const renderForm = (props?: Partial<EditExpensesListFormProps>) => {
@ -29,9 +29,9 @@ const renderForm = (props?: Partial<EditExpensesListFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить'); const submitButtonElem = screen.getByText('Сохранить');
return { return {
titleInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -64,9 +64,9 @@ describe('Test EditExpensesListForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.clear(titleInputElem); await userEvent.clear(titleInputElem);

View File

@ -1,8 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './EditExpenseListForm.module.css';
import type { ExpenseList } from '@/entity/expenses/list';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -10,25 +5,31 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
import type { ExpenseList } from '@/entity/expenses/list';
import { Input } from '@/shared/ui/Input';
import classes from './EditExpenseListForm.module.css';
export type EditExpenseListFormValues = { export type EditExpenseListFormValues = {
title: string; title: string;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
export type EditExpensesListFormProps = BaseFormProps & { export type EditExpensesListFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
list: ExpenseList;
onSubmit: (data: EditExpenseListFormValues) => Promise<void> | void; onSubmit: (data: EditExpenseListFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
list: ExpenseList;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const EditExpensesListForm: FC<EditExpensesListFormProps> = (props) => { const EditExpensesListForm: FC<EditExpensesListFormProps> = (props) => {
const { const {
ref,
disabled, disabled,
onSubmit,
list, list,
onSubmit,
ref,
...restProps ...restProps
} = props; } = props;
@ -46,9 +47,9 @@ const EditExpensesListForm: FC<EditExpensesListFormProps> = (props) => {
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps} {...restProps}
> >
<h2>Редактировать список расходов</h2> <h2>Редактировать список расходов</h2>

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type AddIncomeItemBody = paths['/incomes/list/{id}/items']['post']['requestBody']['content']['application/json']; export type AddIncomeItemBody = paths['/incomes/list/{id}/items']['post']['requestBody']['content']['application/json'];
export type AddIncomeItemPath = paths['/incomes/list/{id}/items']['post']['parameters']['path']; export type AddIncomeItemPath = paths['/incomes/list/{id}/items']['post']['parameters']['path'];
export type AddIncomeItemSuccessResponse = paths['/incomes/list/{id}/items']['post']['responses']['200']['content']['application/json']; export type AddIncomeItemSuccessResponse = paths['/incomes/list/{id}/items']['post']['responses']['200']['content']['application/json'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type DeleteIncomeItemPath = paths['/incomes/list/{listId}/items/{id}']['delete']['parameters']['path']; export type DeleteIncomeItemPath = paths['/incomes/list/{listId}/items/{id}']['delete']['parameters']['path'];
export type DeleteIncomeItemSuccessResponse = paths['/incomes/list/{id}/items']['post']['responses']['200']['content']['application/json']; export type DeleteIncomeItemSuccessResponse = paths['/incomes/list/{id}/items']['post']['responses']['200']['content']['application/json'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type EditIncomeItemBody = paths['/incomes/list/{listId}/items/{id}']['patch']['requestBody']['content']['application/json']; export type EditIncomeItemBody = paths['/incomes/list/{listId}/items/{id}']['patch']['requestBody']['content']['application/json'];
export type EditIncomeItemPath = paths['/incomes/list/{listId}/items/{id}']['patch']['parameters']['path']; export type EditIncomeItemPath = paths['/incomes/list/{listId}/items/{id}']['patch']['parameters']['path'];
export type EditIncomeItemSuccessResponse = paths['/incomes/list/{listId}/items/{id}']['patch']['responses']['200']['content']['application/json']; export type EditIncomeItemSuccessResponse = paths['/incomes/list/{listId}/items/{id}']['patch']['responses']['200']['content']['application/json'];

View File

@ -2,10 +2,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { QueryKeys } from '@/shared/api/queryKeys'; import { QueryKeys } from '@/shared/api/queryKeys';
import { deleteIncomeItem } from '../api/deleteIncomeItem';
import type { DeleteIncomeItemPath } from '../api/deleteIncomeItem'; import type { DeleteIncomeItemPath } from '../api/deleteIncomeItem';
import { deleteIncomeItem } from '../api/deleteIncomeItem';
export const useDeleteIncomeItem = (path: DeleteIncomeItemPath) => { export const useDeleteIncomeItem = (path: DeleteIncomeItemPath) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@ -1,17 +1,17 @@
export type { AddIncomeItemBody, AddIncomeItemPath, AddIncomeItemSuccessResponse } from './api/addIncomeItem';
export type { DeleteIncomeItemPath, DeleteIncomeItemSuccessResponse } from './api/deleteIncomeItem';
export type { EditIncomeItemBody, EditIncomeItemPath, EditIncomeItemSuccessResponse } from './api/editIncomeItem';
export { useAddIncomeItem } from './hooks/useAddIncomeItem'; export { useAddIncomeItem } from './hooks/useAddIncomeItem';
export { useDeleteIncomeItem } from './hooks/useDeleteIncomeItem'; export { useDeleteIncomeItem } from './hooks/useDeleteIncomeItem';
export { useEditIncomeItem } from './hooks/useEditIncomeItem'; export { useEditIncomeItem } from './hooks/useEditIncomeItem';
export type { AddIncomeItemPath, AddIncomeItemBody, AddIncomeItemSuccessResponse } from './api/addIncomeItem'; export type { IncomeListItem } from './types';
export type { DeleteIncomeItemPath, DeleteIncomeItemSuccessResponse } from './api/deleteIncomeItem';
export type { EditIncomeItemPath, EditIncomeItemBody, EditIncomeItemSuccessResponse } from './api/editIncomeItem';
export type { AddIncomeItemFormValues } from './ui/AddIncomeItemForm/AddIncomeItemForm.ui'; export type { AddIncomeItemFormValues } from './ui/AddIncomeItemForm/AddIncomeItemForm.ui';
export { default as AddIncomeItemForm } from './ui/AddIncomeItemForm/AddIncomeItemForm.ui'; export { default as AddIncomeItemForm } from './ui/AddIncomeItemForm/AddIncomeItemForm.ui';
export type { EditIncomeItemFormValues } from './ui/EditIncomeItemForm/EditIncomeItemForm.ui';
export { default as EditIncomeItemForm } from './ui/EditIncomeItemForm/EditIncomeItemForm.ui';
export { default as DeleteIncomeItemForm } from './ui/DeleteIncomeItemForm/DeleteIncomeItemForm.ui'; export { default as DeleteIncomeItemForm } from './ui/DeleteIncomeItemForm/DeleteIncomeItemForm.ui';
export type { IncomeListItem } from './types'; export type { EditIncomeItemFormValues } from './ui/EditIncomeItemForm/EditIncomeItemForm.ui';
export { default as EditIncomeItemForm } from './ui/EditIncomeItemForm/EditIncomeItemForm.ui';

View File

@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import AddIncomeItemForm from './AddIncomeItemForm.ui';
import type { AddIncomeItemFormProps } from './AddIncomeItemForm.ui'; import type { AddIncomeItemFormProps } from './AddIncomeItemForm.ui';
import AddIncomeItemForm from './AddIncomeItemForm.ui';
const renderForm = (props?: Partial<AddIncomeItemFormProps>) => { const renderForm = (props?: Partial<AddIncomeItemFormProps>) => {
const onSubmit = jest.fn(); const onSubmit = jest.fn();
@ -17,12 +17,12 @@ const renderForm = (props?: Partial<AddIncomeItemFormProps>) => {
const submitButtonElem = screen.getByText('Добавить'); const submitButtonElem = screen.getByText('Добавить');
return { return {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
submitButtonElem, dateInputElem,
descriptionInputElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -48,12 +48,12 @@ describe('Test AddIncomeItemForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
submitButtonElem, dateInputElem,
descriptionInputElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.type(titleInputElem, 'Название элемента'); await userEvent.type(titleInputElem, 'Название элемента');
@ -65,11 +65,11 @@ describe('Test AddIncomeItemForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({ expect(onSubmit).toHaveBeenCalledWith({
title: 'Название элемента',
description: 'Описание элемента',
date: '2025-08-29T00:00:00.000Z',
amount: 12, amount: 12,
currency: 'RUB', currency: 'RUB',
date: '2025-08-29T00:00:00.000Z',
description: 'Описание элемента',
title: 'Название элемента',
}); });
}); });
}); });

View File

@ -1,7 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './AddIncomeItemForm.module.css';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -9,15 +5,9 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
export type AddIncomeItemFormValues = { import { Input } from '@/shared/ui/Input';
title: string;
description: string;
date: string;
amount: number;
currency: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import classes from './AddIncomeItemForm.module.css';
export type AddIncomeItemFormProps = BaseFormProps & { export type AddIncomeItemFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
@ -25,6 +15,16 @@ export type AddIncomeItemFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
export type AddIncomeItemFormValues = {
amount: number;
currency: string;
date: string;
description: string;
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const AddIncomeItemForm: FC<AddIncomeItemFormProps> = (props) => { const AddIncomeItemForm: FC<AddIncomeItemFormProps> = (props) => {
const { const {
disabled, disabled,
@ -45,29 +45,29 @@ const AddIncomeItemForm: FC<AddIncomeItemFormProps> = (props) => {
if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string') { if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string') {
onSubmit?.({ onSubmit?.({
title,
date: new Date(date).toISOString(),
amount: Number(amount), amount: Number(amount),
description,
currency: 'RUB', currency: 'RUB',
date: new Date(date).toISOString(),
description,
title,
}); });
} }
}; };
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...formProps} {...formProps}
> >
<h2>Добавить список доходов</h2> <h2>Добавить список доходов</h2>
<Input <Input
required
disabled={disabled} disabled={disabled}
label="Название" label="Название"
name="title" name="title"
required
/> />
<Input <Input
@ -77,18 +77,18 @@ const AddIncomeItemForm: FC<AddIncomeItemFormProps> = (props) => {
/> />
<Input <Input
required
disabled={disabled} disabled={disabled}
label="Дата" label="Дата"
name="date" name="date"
required
type="date" type="date"
/> />
<Input <Input
required
disabled={disabled} disabled={disabled}
label="Сумма" label="Сумма"
name="amount" name="amount"
required
step="0.01" step="0.01"
type="number" type="number"
/> />

View File

@ -1,20 +1,20 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import DeleteIncomeItemForm from './DeleteIncomeItemForm.ui';
import type { DeleteIncomeItemFormProps } from './DeleteIncomeItemForm.ui'; import type { DeleteIncomeItemFormProps } from './DeleteIncomeItemForm.ui';
import DeleteIncomeItemForm from './DeleteIncomeItemForm.ui';
const deletingItem = { const deletingItem = {
id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6',
title: 'Mock item',
amount: 12, amount: 12,
updatedAt: '2025-08-29T07:57:40.503Z',
incomeListId: '10cc093c-3875-4d33-a40a-df526266e262',
createdAt: '2025-08-29T07:57:40.503Z', createdAt: '2025-08-29T07:57:40.503Z',
currency: 'RUB', currency: 'RUB',
date: '2025-08-22T00:00:03.000Z', date: '2025-08-22T00:00:03.000Z',
description: 'Mock description', description: 'Mock description',
id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6',
incomeListId: '10cc093c-3875-4d33-a40a-df526266e262',
title: 'Mock item',
updatedAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<DeleteIncomeItemFormProps>) => { const renderForm = (props?: Partial<DeleteIncomeItemFormProps>) => {
@ -32,8 +32,8 @@ const renderForm = (props?: Partial<DeleteIncomeItemFormProps>) => {
const submitButtonElem = screen.getByText('Удалить'); const submitButtonElem = screen.getByText('Удалить');
return { return {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
}; };
}; };
@ -47,8 +47,8 @@ describe('Test DeleteIncomeItemForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
} = renderForm(); } = renderForm();
await userEvent.click(submitButtonElem); await userEvent.click(submitButtonElem);

View File

@ -1,4 +1,3 @@
import type { IncomeListItem } from '../../types';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -6,21 +5,23 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import type { IncomeListItem } from '../../types';
export type DeleteIncomeItemFormProps = BaseFormProps & { export type DeleteIncomeItemFormProps = BaseFormProps & {
disabled?: boolean;
item: IncomeListItem; item: IncomeListItem;
onSubmit?: (item: IncomeListItem) => Promise<void> | void; onSubmit?: (item: IncomeListItem) => Promise<void> | void;
disabled?: boolean;
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const DeleteIncomeItemForm: FC<DeleteIncomeItemFormProps> = (props) => { const DeleteIncomeItemForm: FC<DeleteIncomeItemFormProps> = (props) => {
const { const {
disabled,
item, item,
onSubmit, onSubmit,
ref, ref,
disabled,
...restProps ...restProps
} = props; } = props;
const onFormSubmitHandler: FormEventHandler<HTMLFormElement> = (event) => { const onFormSubmitHandler: FormEventHandler<HTMLFormElement> = (event) => {
@ -29,7 +30,7 @@ const DeleteIncomeItemForm: FC<DeleteIncomeItemFormProps> = (props) => {
}; };
return ( return (
<form ref={ref} onSubmit={onFormSubmitHandler} {...restProps}> <form onSubmit={onFormSubmitHandler} ref={ref} {...restProps}>
<h2>Удалить элемент ?</h2> <h2>Удалить элемент ?</h2>
<button disabled={disabled} type="submit">Удалить</button> <button disabled={disabled} type="submit">Удалить</button>

View File

@ -1,21 +1,21 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import EditIncomeItemForm from './EditIncomeItemForm.ui';
import type { EditIncomeItemFormProps } from './EditIncomeItemForm.ui'; import type { EditIncomeItemFormProps } from './EditIncomeItemForm.ui';
import EditIncomeItemForm from './EditIncomeItemForm.ui';
const editingItem = { const editingItem = {
id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6',
title: 'Mock item',
amount: 12, amount: 12,
count: 12, count: 12,
updatedAt: '2025-08-29T07:57:40.503Z',
incomeListId: '10cc093c-3875-4d33-a40a-df526266e262',
createdAt: '2025-08-29T07:57:40.503Z', createdAt: '2025-08-29T07:57:40.503Z',
currency: 'RUB', currency: 'RUB',
date: '2025-08-22T00:00:03.000Z', date: '2025-08-22T00:00:03.000Z',
description: 'Mock description', description: 'Mock description',
id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6',
incomeListId: '10cc093c-3875-4d33-a40a-df526266e262',
title: 'Mock item',
updatedAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<EditIncomeItemFormProps>) => { const renderForm = (props?: Partial<EditIncomeItemFormProps>) => {
@ -37,12 +37,12 @@ const renderForm = (props?: Partial<EditIncomeItemFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить'); const submitButtonElem = screen.getByText('Сохранить');
return { return {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
submitButtonElem, dateInputElem,
descriptionInputElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -56,10 +56,10 @@ describe('Test EditIncomeItemForm', () => {
test('should inputs have default value', () => { test('should inputs have default value', () => {
const { const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
dateInputElem,
descriptionInputElem,
titleInputElem,
} = renderForm(); } = renderForm();
expect(titleInputElem).toHaveValue('Mock item'); expect(titleInputElem).toHaveValue('Mock item');
expect(descriptionInputElem).toHaveValue('Mock description'); expect(descriptionInputElem).toHaveValue('Mock description');
@ -81,12 +81,12 @@ describe('Test EditIncomeItemForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem, amountInputElem,
submitButtonElem, dateInputElem,
descriptionInputElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.clear(titleInputElem); await userEvent.clear(titleInputElem);
@ -105,11 +105,11 @@ describe('Test EditIncomeItemForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({ expect(onSubmit).toHaveBeenCalledWith({
title: 'Название элемента',
description: 'Описание элемента',
date: '2025-08-29T00:00:00.000Z',
amount: 12, amount: 12,
currency: 'RUB', currency: 'RUB',
date: '2025-08-29T00:00:00.000Z',
description: 'Описание элемента',
title: 'Название элемента',
}); });
}); });
}); });

View File

@ -1,8 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './EditIncomeItemForm.module.css';
import type { IncomeListItem } from '../../types';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -10,28 +5,34 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
export type EditIncomeItemFormValues = { import { Input } from '@/shared/ui/Input';
title: string;
description: string; import type { IncomeListItem } from '../../types';
date: string;
amount: number; import classes from './EditIncomeItemForm.module.css';
currency: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
export type EditIncomeItemFormProps = BaseFormProps & { export type EditIncomeItemFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
item: IncomeListItem;
onSubmit?: (data: EditIncomeItemFormValues) => Promise<void> | void; onSubmit?: (data: EditIncomeItemFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
item: IncomeListItem;
}; };
export type EditIncomeItemFormValues = {
amount: number;
currency: string;
date: string;
description: string;
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const EditIncomeItemForm: FC<EditIncomeItemFormProps> = (props) => { const EditIncomeItemForm: FC<EditIncomeItemFormProps> = (props) => {
const { const {
disabled, disabled,
item,
onSubmit, onSubmit,
ref, ref,
item,
...formProps ...formProps
} = props; } = props;
@ -47,20 +48,20 @@ const EditIncomeItemForm: FC<EditIncomeItemFormProps> = (props) => {
if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string') { if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string') {
await onSubmit?.({ await onSubmit?.({
title,
date: new Date(date).toISOString(),
amount: Number(amount), amount: Number(amount),
description,
currency: 'RUB', currency: 'RUB',
date: new Date(date).toISOString(),
description,
title,
}); });
} }
}; };
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...formProps} {...formProps}
> >
<h2>Изменить доход</h2> <h2>Изменить доход</h2>

View File

@ -1,9 +1,9 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
export type AddIncomeListPath = paths['/incomes/list']['post']['parameters']['path']; import { api } from '@/shared/api/client';
export type AddIncomeListBody = paths['/incomes/list']['post']['requestBody']['content']['application/json']; export type AddIncomeListBody = paths['/incomes/list']['post']['requestBody']['content']['application/json'];
export type AddIncomeListPath = paths['/incomes/list']['post']['parameters']['path'];
export type AddIncomeListSuccessResponse = paths['/incomes/list']['post']['responses']['200']['content']['application/json']; export type AddIncomeListSuccessResponse = paths['/incomes/list']['post']['responses']['200']['content']['application/json'];
export async function addIncomeList(data: AddIncomeListBody) { export async function addIncomeList(data: AddIncomeListBody) {

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
export type DeleteIncomeListPath = paths['/incomes/list/{id}']['delete']['parameters']['path']; export type DeleteIncomeListPath = paths['/incomes/list/{id}']['delete']['parameters']['path'];
export type DeleteIncomeListSuccessResponse = paths['/incomes/list/{id}']['delete']['responses']['200']['content']['application/json']; export type DeleteIncomeListSuccessResponse = paths['/incomes/list/{id}']['delete']['responses']['200']['content']['application/json'];

View File

@ -1,14 +1,14 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
export type EditIncomeListPath = paths['/incomes/list/{id}']['patch']['parameters']['path']; import { api } from '@/shared/api/client';
export type EditIncomeListBody = paths['/incomes/list/{id}']['patch']['requestBody']['content']['application/json']; export type EditIncomeListBody = paths['/incomes/list/{id}']['patch']['requestBody']['content']['application/json'];
export type EditIncomeListPath = paths['/incomes/list/{id}']['patch']['parameters']['path'];
export type EditIncomeListSuccessResponse = paths['/incomes/list/{id}']['patch']['responses']['200']['content']['application/json']; export type EditIncomeListSuccessResponse = paths['/incomes/list/{id}']['patch']['responses']['200']['content']['application/json'];
export async function editIncomeList({ data, path }: { export async function editIncomeList({ data, path }: {
path: EditIncomeListPath;
data: EditIncomeListBody; data: EditIncomeListBody;
path: EditIncomeListPath;
}) { }) {
const { id } = path; const { id } = path;
const { data: result } = await api.patch<EditIncomeListSuccessResponse>(`/incomes/list/${id}`, data); const { data: result } = await api.patch<EditIncomeListSuccessResponse>(`/incomes/list/${id}`, data);

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type IncomeListPath = paths['/incomes/list/{id}']['get']['parameters']['path']; type IncomeListPath = paths['/incomes/list/{id}']['get']['parameters']['path'];
type IncomeListResponse = paths['/incomes/list/{id}']['get']['responses']['200']['content']['application/json']; type IncomeListResponse = paths['/incomes/list/{id}']['get']['responses']['200']['content']['application/json'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema'; import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type IncomesListResponse = paths['/incomes/list']['get']['responses']['200']['content']['application/json']; type IncomesListResponse = paths['/incomes/list']['get']['responses']['200']['content']['application/json'];
export async function getIncomeLists() { export async function getIncomeLists() {

View File

@ -2,10 +2,10 @@ import { useQuery } from '@tanstack/react-query';
import { QueryKeys } from '@/shared/api/queryKeys'; import { QueryKeys } from '@/shared/api/queryKeys';
import { getIncomeList } from '../api/getIncomeList';
import type { IncomeList } from '../types'; import type { IncomeList } from '../types';
import { getIncomeList } from '../api/getIncomeList';
export const useIncomeList = (id: IncomeList['id']) => useQuery({ export const useIncomeList = (id: IncomeList['id']) => useQuery({
queryFn: async () => getIncomeList({ id }), queryFn: async () => getIncomeList({ id }),
queryKey: [QueryKeys.IncomesList, id], queryKey: [QueryKeys.IncomesList, id],

View File

@ -1,10 +1,14 @@
export type { IncomeList } from './types'; export type { AddIncomeListBody, AddIncomeListPath, AddIncomeListSuccessResponse } from './api/addIncomeList';
export { useDeleteIncomesList } from './hooks/useDeleteIncomesList'; export type { DeleteIncomeListPath, DeleteIncomeListSuccessResponse } from './api/deleteIncomeList';
export { useAddIncomesList } from './hooks/useAddIncomesList'; export type { EditIncomeListBody, EditIncomeListPath, EditIncomeListSuccessResponse } from './api/editIncomeList';
export { useIncomesList } from './hooks/useIncomesList';
export { useEditIncomesList } from './hooks/useEditIncomesList';
export { useAddIncomesList } from './hooks/useAddIncomesList';
export { useDeleteIncomesList } from './hooks/useDeleteIncomesList';
export { useEditIncomesList } from './hooks/useEditIncomesList';
export { useIncomeList } from './hooks/useIncomeList'; export { useIncomeList } from './hooks/useIncomeList';
export { useIncomesList } from './hooks/useIncomesList';
export type { IncomeList } from './types';
export type { AddIncomeListFormValues } from './ui/AddIncomeListForm/ui/AddIncomeListForm.ui'; export type { AddIncomeListFormValues } from './ui/AddIncomeListForm/ui/AddIncomeListForm.ui';
export { default as AddIncomeListForm } from './ui/AddIncomeListForm/ui/AddIncomeListForm.ui'; export { default as AddIncomeListForm } from './ui/AddIncomeListForm/ui/AddIncomeListForm.ui';
@ -13,7 +17,3 @@ export { default as DeleteIncomeListForm } from './ui/DeleteIncomeListForm/ui/De
export type { EditIncomeListFormValues } from './ui/EditIncomeListForm/ui/EditIncomeListForm.ui'; export type { EditIncomeListFormValues } from './ui/EditIncomeListForm/ui/EditIncomeListForm.ui';
export { default as EditIncomeListForm } from './ui/EditIncomeListForm/ui/EditIncomeListForm.ui'; export { default as EditIncomeListForm } from './ui/EditIncomeListForm/ui/EditIncomeListForm.ui';
export type { AddIncomeListBody, AddIncomeListPath, AddIncomeListSuccessResponse } from './api/addIncomeList';
export type { DeleteIncomeListPath, DeleteIncomeListSuccessResponse } from './api/deleteIncomeList';
export type { EditIncomeListBody, EditIncomeListPath, EditIncomeListSuccessResponse } from './api/editIncomeList';

View File

@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import AddIncomeListForm from './AddIncomeListForm.ui';
import type { AddIncomeListFormProps } from './AddIncomeListForm.ui'; import type { AddIncomeListFormProps } from './AddIncomeListForm.ui';
import AddIncomeListForm from './AddIncomeListForm.ui';
const renderForm = (props?: Partial<AddIncomeListFormProps>) => { const renderForm = (props?: Partial<AddIncomeListFormProps>) => {
const onSubmit = jest.fn(); const onSubmit = jest.fn();
@ -14,9 +14,9 @@ const renderForm = (props?: Partial<AddIncomeListFormProps>) => {
const submitButtonElem = screen.getByText('Добавить'); const submitButtonElem = screen.getByText('Добавить');
return { return {
titleInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -42,9 +42,9 @@ describe('Test AddIncomeListForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.type(titleInputElem, 'Название списка'); await userEvent.type(titleInputElem, 'Название списка');

View File

@ -1,31 +1,31 @@
import type {
FC,
FormEventHandler,
FormHTMLAttributes,
RefObject,
} from 'react';
import { Input } from '@/shared/ui/Input'; import { Input } from '@/shared/ui/Input';
import classes from './AddIncomeListForm.module.css'; import classes from './AddIncomeListForm.module.css';
import type {
FormHTMLAttributes,
RefObject,
FormEventHandler,
FC,
} from 'react';
export type AddIncomeListFormValues = {
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
export type AddIncomeListFormProps = BaseFormProps & { export type AddIncomeListFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
onSubmit: (data: AddIncomeListFormValues) => Promise<void> | void; onSubmit: (data: AddIncomeListFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
}; };
export type AddIncomeListFormValues = {
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const AddIncomeListForm: FC<AddIncomeListFormProps> = (props) => { const AddIncomeListForm: FC<AddIncomeListFormProps> = (props) => {
const { const {
ref,
disabled, disabled,
onSubmit, onSubmit,
ref,
...restProps ...restProps
} = props; } = props;
@ -45,9 +45,9 @@ const AddIncomeListForm: FC<AddIncomeListFormProps> = (props) => {
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps} {...restProps}
> >
<h2>Добавить список доходов</h2> <h2>Добавить список доходов</h2>

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import DeleteIncomeListForm from './DeleteIncomeListForm.ui';
import type { DeleteIncomeListFormProps } from './DeleteIncomeListForm.ui'; import type { DeleteIncomeListFormProps } from './DeleteIncomeListForm.ui';
import DeleteIncomeListForm from './DeleteIncomeListForm.ui';
const deletingList = { const deletingList = {
id: '10cc093c-3875-4d33-a40a-df526266e262',
title: 'Mock list',
items: [],
updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z', createdAt: '2025-08-29T07:57:40.503Z',
id: '10cc093c-3875-4d33-a40a-df526266e262',
items: [],
title: 'Mock list',
updatedAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<DeleteIncomeListFormProps>) => { const renderForm = (props?: Partial<DeleteIncomeListFormProps>) => {
@ -28,8 +28,8 @@ const renderForm = (props?: Partial<DeleteIncomeListFormProps>) => {
const submitButtonElem = screen.getByText('Удалить'); const submitButtonElem = screen.getByText('Удалить');
return { return {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
}; };
}; };
@ -43,8 +43,8 @@ describe('Test DeleteIncomeListForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
} = renderForm(); } = renderForm();
await userEvent.click(submitButtonElem); await userEvent.click(submitButtonElem);

View File

@ -1,4 +1,3 @@
import type { IncomeList } from '../../../types';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -6,21 +5,23 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; import type { IncomeList } from '../../../types';
export type DeleteIncomeListFormProps = BaseFormProps & { export type DeleteIncomeListFormProps = BaseFormProps & {
disabled?: boolean; disabled?: boolean;
list: IncomeList;
onSubmit?: (list: IncomeList) => Promise<void> | void; onSubmit?: (list: IncomeList) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>; ref?: RefObject<HTMLFormElement>;
list: IncomeList;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const DeleteIncomeListForm: FC<DeleteIncomeListFormProps> = (props) => { const DeleteIncomeListForm: FC<DeleteIncomeListFormProps> = (props) => {
const { const {
ref,
disabled, disabled,
onSubmit,
list, list,
onSubmit,
ref,
...restProps ...restProps
} = props; } = props;
@ -30,7 +31,7 @@ const DeleteIncomeListForm: FC<DeleteIncomeListFormProps> = (props) => {
}; };
return ( return (
<form ref={ref} onSubmit={onFormSubmitHandler} {...restProps}> <form onSubmit={onFormSubmitHandler} ref={ref} {...restProps}>
<h2>Удалить список доходов</h2> <h2>Удалить список доходов</h2>
<p> <p>

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import EditIncomeListForm from './EditIncomeListForm.ui';
import type { EditExpensesListFormProps } from './EditIncomeListForm.ui'; import type { EditExpensesListFormProps } from './EditIncomeListForm.ui';
import EditIncomeListForm from './EditIncomeListForm.ui';
const editingList = { const editingList = {
id: '10cc093c-3875-4d33-a40a-df526266e262',
title: 'Mock list',
items: [],
updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z', createdAt: '2025-08-29T07:57:40.503Z',
id: '10cc093c-3875-4d33-a40a-df526266e262',
items: [],
title: 'Mock list',
updatedAt: '2025-08-29T07:57:40.503Z',
}; };
const renderForm = (props?: Partial<EditExpensesListFormProps>) => { const renderForm = (props?: Partial<EditExpensesListFormProps>) => {
@ -29,9 +29,9 @@ const renderForm = (props?: Partial<EditExpensesListFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить'); const submitButtonElem = screen.getByText('Сохранить');
return { return {
titleInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
}; };
}; };
@ -64,9 +64,9 @@ describe('Test EditIncomeListForm', () => {
test('should call onSubmit with values', async () => { test('should call onSubmit with values', async () => {
const { const {
titleInputElem,
submitButtonElem,
onSubmit, onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm(); } = renderForm();
await userEvent.clear(titleInputElem); await userEvent.clear(titleInputElem);

View File

@ -1,8 +1,3 @@
import { Input } from '@/shared/ui/Input';
import classes from './EditIncomeListForm.module.css';
import type { IncomeList } from '../../../types';
import type { import type {
FC, FC,
FormEventHandler, FormEventHandler,
@ -10,25 +5,31 @@ import type {
RefObject, RefObject,
} from 'react'; } from 'react';
import { Input } from '@/shared/ui/Input';
import type { IncomeList } from '../../../types';
import classes from './EditIncomeListForm.module.css';
export type EditExpensesListFormProps = BaseFormProps & {
disabled?: boolean;
list: IncomeList;
onSubmit: (data: EditIncomeListFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>;
};
export type EditIncomeListFormValues = { export type EditIncomeListFormValues = {
title: string; title: string;
}; };
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>; type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
export type EditExpensesListFormProps = BaseFormProps & {
disabled?: boolean;
onSubmit: (data: EditIncomeListFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>;
list: IncomeList;
};
const EditExpensesListForm: FC<EditExpensesListFormProps> = (props) => { const EditExpensesListForm: FC<EditExpensesListFormProps> = (props) => {
const { const {
ref,
disabled, disabled,
onSubmit,
list, list,
onSubmit,
ref,
...restProps ...restProps
} = props; } = props;
@ -46,9 +47,9 @@ const EditExpensesListForm: FC<EditExpensesListFormProps> = (props) => {
return ( return (
<form <form
ref={ref}
className={classes.form} className={classes.form}
onSubmit={onFormSubmitHandler} onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps} {...restProps}
> >
<h2>Редактировать список доходов</h2> <h2>Редактировать список доходов</h2>

View File

@ -1,7 +1,7 @@
import { useLogout } from '@/entity/account'; import { useLogout } from '@/entity/account';
const LogoutButton = () => { const LogoutButton = () => {
const { mutate: logout, isPending } = useLogout(); const { isPending, mutate: logout } = useLogout();
const onClickHandler = () => { const onClickHandler = () => {
logout(); logout();
@ -10,8 +10,8 @@ const LogoutButton = () => {
return ( return (
<button <button
disabled={isPending} disabled={isPending}
type="button"
onClick={onClickHandler} onClick={onClickHandler}
type="button"
> >
Logout Logout
</button> </button>

View File

@ -1,9 +1,9 @@
import { type MouseEventHandler, useState } from 'react'; import { type MouseEventHandler, useState } from 'react';
import { useAddBankCard } from '@/entity/cards';
import type { AddBankCardFormValues } from '@/entity/cards'; import type { AddBankCardFormValues } from '@/entity/cards';
import { useAddBankCard } from '@/entity/cards';
type UseAddBankCardButtonArgs = { type UseAddBankCardButtonArgs = {
onClick?: MouseEventHandler<HTMLButtonElement>; onClick?: MouseEventHandler<HTMLButtonElement>;
}; };
@ -12,7 +12,7 @@ export const useAddBankCardButton = (args: UseAddBankCardButtonArgs) => {
const { onClick } = args; const { onClick } = args;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: addBankCardMutation, isPending } = useAddBankCard(); const { isPending, mutateAsync: addBankCardMutation } = useAddBankCard();
const onCloseModalHandler = () => { const onCloseModalHandler = () => {
setIsOpen(false); setIsOpen(false);
@ -31,8 +31,8 @@ export const useAddBankCardButton = (args: UseAddBankCardButtonArgs) => {
return { return {
isOpen, isOpen,
isPending, isPending,
onCloseModalHandler,
onClickHandler, onClickHandler,
onCloseModalHandler,
onFormSubmitHandler, onFormSubmitHandler,
}; };
}; };

View File

@ -1,14 +1,13 @@
import { server } from '@mocks/jest/server';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import { render, screen, within } from '@testing-library/react'; import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw'; import { http, HttpResponse } from 'msw';
import { server } from '@mocks/jest/server'; import type { AddBankCardBody, AddBankCardResponse } from '@/entity/cards';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import AddBankCardButton from './AddBankCardButton.ui'; import AddBankCardButton from './AddBankCardButton.ui';
import type { AddBankCardBody, AddBankCardResponse } from '@/entity/cards';
const renderButton = () => { const renderButton = () => {
render( render(
<AddBankCardButton data-testid="test-button" />, <AddBankCardButton data-testid="test-button" />,
@ -44,11 +43,11 @@ const submitHandler = (requestSpy: jest.Mock) => http.post<never, AddBankCardBod
requestSpy(body); requestSpy(body);
return HttpResponse.json({ return HttpResponse.json({
balance: 999,
createdAt: '2025-08-29T07:57:40.503Z',
id: '10cc093c-3875-4d33-a40a-df526266e262', id: '10cc093c-3875-4d33-a40a-df526266e262',
name: 'Название карты', name: 'Название карты',
balance: 999,
updatedAt: '2025-08-29T07:57:40.503Z', updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
}); });
}, },
); );

View File

@ -1,37 +1,37 @@
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
import { AddBankCardForm } from '@/entity/cards'; import { AddBankCardForm } from '@/entity/cards';
import { Modal } from '@/shared/ui/Modal'; import { Modal } from '@/shared/ui/Modal';
import { useAddBankCardButton } from '../hooks/useAddBankCardButton'; import { useAddBankCardButton } from '../hooks/useAddBankCardButton';
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
type AddBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & { type AddBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
ref?: Ref<HTMLButtonElement>; ref?: Ref<HTMLButtonElement>;
}; };
const AddBankCardButton: FC<AddBankCardButtonProps> = (props) => { const AddBankCardButton: FC<AddBankCardButtonProps> = (props) => {
const { ref, onClick, ...restProps } = props; const { onClick, ref, ...restProps } = props;
const { const {
isOpen, isOpen,
isPending, isPending,
onCloseModalHandler,
onClickHandler, onClickHandler,
onCloseModalHandler,
onFormSubmitHandler, onFormSubmitHandler,
} = useAddBankCardButton({ onClick }); } = useAddBankCardButton({ onClick });
return ( return (
<> <>
<button <button
onClick={onClickHandler}
ref={ref} ref={ref}
type="button" type="button"
onClick={onClickHandler}
{...restProps} {...restProps}
> >
Добавить Добавить
</button> </button>
<Modal open={isOpen} onClose={onCloseModalHandler}> <Modal onClose={onCloseModalHandler} open={isOpen}>
<AddBankCardForm disabled={isPending} onSubmit={onFormSubmitHandler} /> <AddBankCardForm disabled={isPending} onSubmit={onFormSubmitHandler} />
</Modal> </Modal>
</> </>

View File

@ -1,9 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { useDeleteBankCard } from '@/entity/cards';
import type { BankCard } from '@/entity/cards'; import type { BankCard } from '@/entity/cards';
import { useDeleteBankCard } from '@/entity/cards';
type UseDeleteBankCardButtonArgs = { type UseDeleteBankCardButtonArgs = {
id: BankCard['id']; id: BankCard['id'];
}; };
@ -12,7 +12,7 @@ export const useDeleteBankCardButton = (args: UseDeleteBankCardButtonArgs) => {
const { id } = args; const { id } = args;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: deleteBankCardAsync, isPending } = useDeleteBankCard(); const { isPending, mutateAsync: deleteBankCardAsync } = useDeleteBankCard();
const onClickHandler = () => { const onClickHandler = () => {
setIsOpen(true); setIsOpen(true);

View File

@ -1,21 +1,20 @@
import { server } from '@mocks/jest/server';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import { render, screen, within } from '@testing-library/react'; import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw'; import { http, HttpResponse } from 'msw';
import { server } from '@mocks/jest/server';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import DeleteBankCardButton from './DeleteBankCardButton.ui';
import type { BankCard } from '@/entity/cards'; import type { BankCard } from '@/entity/cards';
import type { DeleteExpenseListPath } from '@/entity/expenses/list'; import type { DeleteExpenseListPath } from '@/entity/expenses/list';
import DeleteBankCardButton from './DeleteBankCardButton.ui';
const deletingBankCard: BankCard = { const deletingBankCard: BankCard = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d', id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Тестовая карта', name: 'Тестовая карта',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z', updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
}; };
const renderButton = () => { const renderButton = () => {

View File

@ -1,11 +1,12 @@
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
import type { BankCard } from '@/entity/cards';
import { DeleteBankCardForm } from '@/entity/cards'; import { DeleteBankCardForm } from '@/entity/cards';
import { Modal } from '@/shared/ui/Modal'; import { Modal } from '@/shared/ui/Modal';
import { useDeleteBankCardButton } from '../hooks/useDeleteBankCardButton'; import { useDeleteBankCardButton } from '../hooks/useDeleteBankCardButton';
import type { BankCard } from '@/entity/cards';
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
export type DeleteBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & { export type DeleteBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
bankCard: BankCard; bankCard: BankCard;
ref?: Ref<HTMLButtonElement>; ref?: Ref<HTMLButtonElement>;
@ -14,26 +15,26 @@ export type DeleteBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement>
const DeleteBankCardButton: FC<DeleteBankCardButtonProps> = (props) => { const DeleteBankCardButton: FC<DeleteBankCardButtonProps> = (props) => {
const { const {
bankCard, bankCard,
ref,
onClick, onClick,
ref,
...restProps ...restProps
} = props; } = props;
const { const {
isOpen, isOpen,
isPending, isPending,
onCloseModalHandler,
onClickHandler, onClickHandler,
onCloseModalHandler,
onFormSubmitHandler, onFormSubmitHandler,
} = useDeleteBankCardButton({ id: bankCard.id }); } = useDeleteBankCardButton({ id: bankCard.id });
return ( return (
<> <>
<button type="button" onClick={onClickHandler} {...restProps}> <button onClick={onClickHandler} type="button" {...restProps}>
Удалить Удалить
</button> </button>
<Modal open={isOpen} onClose={onCloseModalHandler}> <Modal onClose={onCloseModalHandler} open={isOpen}>
<DeleteBankCardForm <DeleteBankCardForm
bankCard={bankCard} bankCard={bankCard}
disabled={isPending} disabled={isPending}

View File

@ -1,9 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { useEditBankCard } from '@/entity/cards';
import type { BankCard, EditBankCardFormValues } from '@/entity/cards'; import type { BankCard, EditBankCardFormValues } from '@/entity/cards';
import { useEditBankCard } from '@/entity/cards';
type UseDeleteBankCardButtonArgs = { type UseDeleteBankCardButtonArgs = {
bankCard: BankCard; bankCard: BankCard;
}; };
@ -12,7 +12,7 @@ export const useEditBankCardButton = (args: UseDeleteBankCardButtonArgs) => {
const { bankCard } = args; const { bankCard } = args;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: editBankCardAsync, isPending } = useEditBankCard(); const { isPending, mutateAsync: editBankCardAsync } = useEditBankCard();
const onClickHandler = () => { const onClickHandler = () => {
setIsOpen(true); setIsOpen(true);

View File

@ -1,12 +1,9 @@
import { server } from '@mocks/jest/server';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import { render, screen, within } from '@testing-library/react'; import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw'; import { http, HttpResponse } from 'msw';
import { server } from '@mocks/jest/server';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import EditBankCardButton from './EditBankCardButton.ui';
import type { import type {
BankCard, BankCard,
EditBankCardBody, EditBankCardBody,
@ -14,12 +11,14 @@ import type {
EditBankCardResponse, EditBankCardResponse,
} from '@/entity/cards'; } from '@/entity/cards';
import EditBankCardButton from './EditBankCardButton.ui';
const editingBankCard: BankCard = { const editingBankCard: BankCard = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d', id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Тестовая карта', name: 'Тестовая карта',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z', updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
}; };
const fillForm = async (data: EditBankCardBody) => { const fillForm = async (data: EditBankCardBody) => {
@ -64,11 +63,11 @@ const submitHandler = (requestSpy: jest.Mock) => http.patch<EditBankCardPath, Ed
requestSpy(body); requestSpy(body);
return HttpResponse.json({ return HttpResponse.json({
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d', id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Тестовая карта', name: 'Тестовая карта',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z', updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
}); });
}, },
); );

View File

@ -1,11 +1,12 @@
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
import type { BankCard } from '@/entity/cards';
import { EditBankCardForm } from '@/entity/cards'; import { EditBankCardForm } from '@/entity/cards';
import { Modal } from '@/shared/ui/Modal'; import { Modal } from '@/shared/ui/Modal';
import { useEditBankCardButton } from '../hooks/useEditBankCardButton'; import { useEditBankCardButton } from '../hooks/useEditBankCardButton';
import type { BankCard } from '@/entity/cards';
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
export type EditBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & { export type EditBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
bankCard: BankCard; bankCard: BankCard;
ref?: Ref<HTMLButtonElement>; ref?: Ref<HTMLButtonElement>;
@ -13,26 +14,26 @@ export type EditBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
const EditBankCardButton: FC<EditBankCardButtonProps> = (props) => { const EditBankCardButton: FC<EditBankCardButtonProps> = (props) => {
const { const {
bankCard, bankCard,
ref,
onClick, onClick,
ref,
...restProps ...restProps
} = props; } = props;
const { const {
isOpen, isOpen,
isPending, isPending,
onCloseModalHandler,
onClickHandler, onClickHandler,
onCloseModalHandler,
onFormSubmitHandler, onFormSubmitHandler,
} = useEditBankCardButton({ bankCard }); } = useEditBankCardButton({ bankCard });
return ( return (
<> <>
<button type="button" onClick={onClickHandler} {...restProps}> <button onClick={onClickHandler} type="button" {...restProps}>
Редактировать Редактировать
</button> </button>
<Modal open={isOpen} onClose={onCloseModalHandler}> <Modal onClose={onCloseModalHandler} open={isOpen}>
<EditBankCardForm <EditBankCardForm
bankCard={bankCard} bankCard={bankCard}
disabled={isPending} disabled={isPending}

View File

@ -1,9 +1,9 @@
import { type MouseEventHandler, useState } from 'react'; import { type MouseEventHandler, useState } from 'react';
import { useAddCash } from '@/entity/cash';
import type { AddCashFormValues } from '@/entity/cash'; import type { AddCashFormValues } from '@/entity/cash';
import { useAddCash } from '@/entity/cash';
type UseAddCashButtonArgs = { type UseAddCashButtonArgs = {
onClick?: MouseEventHandler<HTMLButtonElement>; onClick?: MouseEventHandler<HTMLButtonElement>;
}; };
@ -12,7 +12,7 @@ export const useAddCashButton = (args: UseAddCashButtonArgs) => {
const { onClick } = args; const { onClick } = args;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: addCashMutation, isPending } = useAddCash(); const { isPending, mutateAsync: addCashMutation } = useAddCash();
const onCloseModalHandler = () => { const onCloseModalHandler = () => {
setIsOpen(false); setIsOpen(false);
@ -31,8 +31,8 @@ export const useAddCashButton = (args: UseAddCashButtonArgs) => {
return { return {
isOpen, isOpen,
isPending, isPending,
onCloseModalHandler,
onClickHandler, onClickHandler,
onCloseModalHandler,
onFormSubmitHandler, onFormSubmitHandler,
}; };
}; };

View File

@ -1,14 +1,13 @@
import { server } from '@mocks/jest/server';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import { render, screen, within } from '@testing-library/react'; import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw'; import { http, HttpResponse } from 'msw';
import { server } from '@mocks/jest/server'; import type { AdCashResponse, AddCashBody } from '@/entity/cash';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import AddCashButton from './AddCashButton.ui'; import AddCashButton from './AddCashButton.ui';
import type { AddCashBody, AdCashResponse } from '@/entity/cash';
const renderButton = () => { const renderButton = () => {
render( render(
<AddCashButton data-testid="test-button" />, <AddCashButton data-testid="test-button" />,
@ -44,11 +43,11 @@ const submitHandler = (requestSpy: jest.Mock) => http.post<never, AddCashBody, A
requestSpy(body); requestSpy(body);
return HttpResponse.json({ return HttpResponse.json({
balance: 999,
createdAt: '2025-08-29T07:57:40.503Z',
id: '10cc093c-3875-4d33-a40a-df526266e262', id: '10cc093c-3875-4d33-a40a-df526266e262',
name: 'Наличные', name: 'Наличные',
balance: 999,
updatedAt: '2025-08-29T07:57:40.503Z', updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
}); });
}, },
); );

View File

@ -1,37 +1,37 @@
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
import { AddCashForm } from '@/entity/cash'; import { AddCashForm } from '@/entity/cash';
import { Modal } from '@/shared/ui/Modal'; import { Modal } from '@/shared/ui/Modal';
import { useAddCashButton } from '../hooks/useAddCashButton'; import { useAddCashButton } from '../hooks/useAddCashButton';
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
type AddCashButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & { type AddCashButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
ref?: Ref<HTMLButtonElement>; ref?: Ref<HTMLButtonElement>;
}; };
const AddCashButton: FC<AddCashButtonProps> = (props) => { const AddCashButton: FC<AddCashButtonProps> = (props) => {
const { ref, onClick, ...restProps } = props; const { onClick, ref, ...restProps } = props;
const { const {
isOpen, isOpen,
isPending, isPending,
onCloseModalHandler,
onClickHandler, onClickHandler,
onCloseModalHandler,
onFormSubmitHandler, onFormSubmitHandler,
} = useAddCashButton({ onClick }); } = useAddCashButton({ onClick });
return ( return (
<> <>
<button <button
onClick={onClickHandler}
ref={ref} ref={ref}
type="button" type="button"
onClick={onClickHandler}
{...restProps} {...restProps}
> >
Добавить Добавить
</button> </button>
<Modal open={isOpen} onClose={onCloseModalHandler}> <Modal onClose={onCloseModalHandler} open={isOpen}>
<AddCashForm disabled={isPending} onSubmit={onFormSubmitHandler} /> <AddCashForm disabled={isPending} onSubmit={onFormSubmitHandler} />
</Modal> </Modal>
</> </>

View File

@ -1,9 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { useDeleteCash } from '@/entity/cash';
import type { CashItem } from '@/entity/cash'; import type { CashItem } from '@/entity/cash';
import { useDeleteCash } from '@/entity/cash';
type UseDeleteCashButtonArgs = { type UseDeleteCashButtonArgs = {
id: CashItem['id']; id: CashItem['id'];
}; };
@ -12,7 +12,7 @@ export const useDeleteCashButton = (args: UseDeleteCashButtonArgs) => {
const { id } = args; const { id } = args;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: deleteCashAsync, isPending } = useDeleteCash(); const { isPending, mutateAsync: deleteCashAsync } = useDeleteCash();
const onClickHandler = () => { const onClickHandler = () => {
setIsOpen(true); setIsOpen(true);

Some files were not shown because too many files have changed in this diff Show More