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

View File

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

View File

@ -1,5 +1,3 @@
import { http, HttpResponse } from 'msw';
import {
list1,
list2,
@ -7,6 +5,7 @@ import {
list4,
list5,
} from '@mocks/browser/data/expenses/lists';
import { http, HttpResponse } from 'msw';
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 { getExpenseItemListById } from './expense/list/:id/get';
export { getExpenseList } from './expense/list/get';

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import pluginQuery from '@tanstack/eslint-plugin-query';
// @ts-expect-error fix package
import config from 'eslint-config-ksv741';
import perfectionist from 'eslint-plugin-perfectionist';
// eslint-disable-next-line import/no-anonymous-default-export
export default [
@ -9,6 +10,10 @@ export default [
...config,
{
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', {
devDependencies: [
'eslint.config.ts',
@ -23,71 +28,13 @@ export default [
'__mocks__/**',
],
}],
'import/order': [
'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',
'import/order': '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-static-element-interactions': 'off',
},
},
perfectionist.configs['recommended-natural'],
{
ignores: ['./src/shared/api/schema.ts'],
},

View File

@ -1,6 +1,14 @@
import type { Config } from 'jest';
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: {
'^.+\\.(t|j)sx?$': ['@swc/jest', {
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;

View File

@ -4,6 +4,9 @@ import { pluginReact } from '@rsbuild/plugin-react';
import { pluginTypeCheck } from '@rsbuild/plugin-type-check';
export default defineConfig({
html: {
title: 'Investments Control',
},
plugins: [
pluginReact(),
pluginEslint({
@ -15,17 +18,14 @@ export default defineConfig({
}),
pluginTypeCheck(),
],
html: {
title: 'Investments Control',
},
server: {
proxy: {
[`${process.env.PUBLIC_API_BASE_URL}`]: {
target: process.env.BACKEND_BASE_URL,
changeOrigin: true,
pathRewrite: {
[`^${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 { api } from '@/shared/api/client';
type LoginRequestBody = paths['/auth/login']['post']['requestBody']['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 { api } from '@/shared/api/client';
type LogoutSuccessResponse = paths['/auth/logout']['post']['responses']['200']['content']['application/json'];
export async function logout() {

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type RegisterRequestBody = paths['/auth/register']['post']['requestBody']['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({
mutationFn: login,
onSuccess: async (data) => {
setAccessToken(data.accessToken);
localStorage.setItem('accessToken', data.accessToken);
await navigate(PAGES.Home);
},
onError: (error) => {
// eslint-disable-next-line
console.log('Неуспешный логин', 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 { useRegister } from './hooks/useRegister';
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 { api } from '@/shared/api/client';
export type AddBankCardBody = paths['/bank-card']['post']['requestBody']['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 { api } from '@/shared/api/client';
export type DeleteBankCardPath = paths['/bank-card/{id}']['delete']['parameters']['path'];
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';
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 EditBankCardPath = paths['/bank-card/{id}']['patch']['parameters']['path'];
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);
return result;

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type BankCardsResponse = paths['/bank-card']['get']['responses']['200']['content']['application/json'];
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 { DeleteBankCardPath, DeleteBankCardResponse } from './api/deleteBankCard';
export type { EditBankCardBody, EditBankCardPath, EditBankCardResponse } from './api/editBankCard';
export type { AddBankCardFormValues } from './ui/AddBankCardForm';
export type { EditBankCardFormValues } from './ui/EditBankCardForm';
export { useAddBankCard } from './hooks/useAddBankCard';
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';
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 BankCards = paths['/bank-card']['get']['responses']['200']['content']['application/json'];

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import EditBankCardForm from './EditBankCardForm.ui';
import type { EditBankCardFormProps } from './EditBankCardForm.ui';
import EditBankCardForm from './EditBankCardForm.ui';
const editingBankCard = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Тестовая карта',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
};
const renderForm = (props?: Partial<EditBankCardFormProps>) => {
@ -30,10 +30,10 @@ const renderForm = (props?: Partial<EditBankCardFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить');
return {
titleInputElem,
balanceInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
};
};
@ -47,8 +47,8 @@ describe('Test EditBankCardForm', () => {
test('should inputs have default value', () => {
const {
titleInputElem,
balanceInputElem,
titleInputElem,
} = renderForm();
expect(titleInputElem).toHaveValue('Тестовая карта');
expect(balanceInputElem).toHaveValue(1000);
@ -68,10 +68,10 @@ describe('Test EditBankCardForm', () => {
test('should call onSubmit with values', async () => {
const {
titleInputElem,
balanceInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm();
await userEvent.clear(titleInputElem);
@ -84,8 +84,8 @@ describe('Test EditBankCardForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({
name: 'Новое название карты',
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 {
FC,
FormEventHandler,
@ -10,12 +5,11 @@ import type {
RefObject,
} from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
import type { BankCard } from '@/entity/cards';
export type EditBankCardFormValues = {
name: string;
balance: number;
};
import { Input } from '@/shared/ui/Input';
import classes from './EditBankCardForm.module.css';
export type EditBankCardFormProps = BaseFormProps & {
bankCard: BankCard;
@ -24,6 +18,13 @@ export type EditBankCardFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>;
};
export type EditBankCardFormValues = {
balance: number;
name: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const EditBankCardForm: FC<EditBankCardFormProps> = (props) => {
const {
bankCard,
@ -43,27 +44,27 @@ const EditBankCardForm: FC<EditBankCardFormProps> = (props) => {
if (typeof name === 'string' && typeof balance === 'string') {
onSubmit?.({
name,
balance: Number(balance),
name,
});
}
};
return (
<form
ref={ref}
className={classes.form}
onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps}
>
<h2>Добавить карту</h2>
<Input
required
defaultValue={bankCard.name}
disabled={disabled}
label="Название"
name="name"
required
/>
<Input

View File

@ -1,9 +1,9 @@
import { api } from '@/shared/api/client';
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 AddCashBody = paths['/cash']['post']['requestBody']['content']['application/json'];
export async function addCash(data: AddCashBody) {
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 { api } from '@/shared/api/client';
export type DeleteCashPath = paths['/cash/{id}']['delete']['parameters']['path'];
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';
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 EditCashPath = paths['/cash/{id}']['patch']['parameters']['path'];
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);
return result;

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
import type { paths } from '@/shared/api/schema';
import { api } from '@/shared/api/client';
type CashResponse = paths['/cash']['get']['responses']['200']['content']['application/json'];
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 { useCash } from './hooks/useCash';
export { useDeleteCash } from './hooks/useDeleteCash';
export { useEditCash } from './hooks/useEditCash';
export type { CashItem, CashList } from './types';
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 { DeleteCashForm } from './ui/DeleteCashForm';
export { EditCashForm } 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';
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 CashList = paths['/cash']['get']['responses']['200']['content']['application/json'];

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import EditCashForm from './EditCashForm.ui';
import type { EditCashFormProps } from './EditCashForm.ui';
import EditCashForm from './EditCashForm.ui';
const editingCash = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Наличные',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
};
const renderForm = (props?: Partial<EditCashFormProps>) => {
@ -30,10 +30,10 @@ const renderForm = (props?: Partial<EditCashFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить');
return {
titleInputElem,
balanceInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
};
};
@ -47,8 +47,8 @@ describe('Test EditCashForm', () => {
test('should inputs have default value', () => {
const {
titleInputElem,
balanceInputElem,
titleInputElem,
} = renderForm();
expect(titleInputElem).toHaveValue('Наличные');
expect(balanceInputElem).toHaveValue(1000);
@ -68,10 +68,10 @@ describe('Test EditCashForm', () => {
test('should call onSubmit with values', async () => {
const {
titleInputElem,
balanceInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm();
await userEvent.clear(titleInputElem);
@ -84,8 +84,8 @@ describe('Test EditCashForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({
name: 'Новое название наличных',
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 {
FC,
FormEventHandler,
@ -10,12 +5,11 @@ import type {
RefObject,
} from 'react';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
import type { CashItem } from '@/entity/cash';
export type EditCashFormValues = {
name: string;
balance: number;
};
import { Input } from '@/shared/ui/Input';
import classes from './EditCashForm.module.css';
export type EditCashFormProps = BaseFormProps & {
cash: CashItem;
@ -24,6 +18,13 @@ export type EditCashFormProps = BaseFormProps & {
ref?: RefObject<HTMLFormElement>;
};
export type EditCashFormValues = {
balance: number;
name: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const EditCashForm: FC<EditCashFormProps> = (props) => {
const {
cash,
@ -43,27 +44,27 @@ const EditCashForm: FC<EditCashFormProps> = (props) => {
if (typeof name === 'string' && typeof balance === 'string') {
onSubmit?.({
name,
balance: Number(balance),
name,
});
}
};
return (
<form
ref={ref}
className={classes.form}
onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps}
>
<h2>Редактировать наличные</h2>
<Input
required
defaultValue={cash.name}
disabled={disabled}
label="Название"
name="name"
required
/>
<Input

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
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 AddExpenseItemPath = paths['/expenses/list/{id}/items']['post']['parameters']['path'];
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 { api } from '@/shared/api/client';
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'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
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 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'];

View File

@ -2,10 +2,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { QueryKeys } from '@/shared/api/queryKeys';
import { deleteExpenseItem } from '../api/deleteExpenseItem';
import type { DeleteExpenseItemPath } from '../api/deleteExpenseItem';
import { deleteExpenseItem } from '../api/deleteExpenseItem';
export const useDeleteExpenseItem = (path: DeleteExpenseItemPath) => {
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 { useDeleteExpenseItem } from './hooks/useDeleteExpenseItem';
export { useEditExpenseItem } from './hooks/useEditExpenseItem';
export type { AddExpenseItemSuccessResponse, AddExpenseItemBody, AddExpenseItemPath } from './api/addExpenseItem';
export type { DeleteExpenseItemPath, DeleteExpenseItemSuccessResponse } from './api/deleteExpenseItem';
export type { EditExpenseItemBody, EditExpenseItemPath, EditExpenseItemSuccessResponse } from './api/editExpenseItem';
export type { ExpenseListItem } from './types';
export type { AddExpenseItemFormValues } 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 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 userEvent from '@testing-library/user-event';
import AddExpenseItemForm from './AddExpenseItemForm.ui';
import type { AddExpenseItemFormProps } from './AddExpenseItemForm.ui';
import AddExpenseItemForm from './AddExpenseItemForm.ui';
const renderForm = (props?: Partial<AddExpenseItemFormProps>) => {
const onSubmit = jest.fn();
@ -18,13 +18,13 @@ const renderForm = (props?: Partial<AddExpenseItemFormProps>) => {
const submitButtonElem = screen.getByText('Добавить');
return {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem,
countInputElem,
submitButtonElem,
dateInputElem,
descriptionInputElem,
onSubmit,
submitButtonElem,
titleInputElem,
};
};
@ -50,13 +50,13 @@ describe('Test AddExpenseItemForm', () => {
test('should call onSubmit with values', async () => {
const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem,
countInputElem,
submitButtonElem,
dateInputElem,
descriptionInputElem,
onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm();
await userEvent.type(titleInputElem, 'Название элемента');
@ -69,12 +69,12 @@ describe('Test AddExpenseItemForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({
title: 'Название элемента',
description: 'Описание элемента',
date: '2025-08-29T00:00:00.000Z',
amount: 12,
count: 1,
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 {
FC,
FormEventHandler,
@ -9,16 +5,9 @@ import type {
RefObject,
} from 'react';
export type AddExpenseItemFormValues = {
title: string;
description: string;
date: string;
amount: number;
count: number;
currency: string;
};
import { Input } from '@/shared/ui/Input';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
import classes from './AddExpenseItemForm.module.css';
export type AddExpenseItemFormProps = BaseFormProps & {
disabled?: boolean;
@ -26,6 +15,17 @@ export type AddExpenseItemFormProps = BaseFormProps & {
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 {
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') {
onSubmit?.({
title,
date: new Date(date).toISOString(),
amount: Number(amount),
description,
currency: 'RUB',
count: Number(count),
currency: 'RUB',
date: new Date(date).toISOString(),
description,
title,
});
}
};
return (
<form
ref={ref}
className={classes.form}
onSubmit={onFormSubmitHandler}
ref={ref}
{...formProps}
>
<h2>Добавить список расходов</h2>
<Input
required
disabled={disabled}
label="Название"
name="title"
required
/>
<Input
@ -80,18 +80,18 @@ const AddExpenseItemForm: FC<AddExpenseItemFormProps> = (props) => {
/>
<Input
required
disabled={disabled}
label="Дата"
name="date"
required
type="date"
/>
<Input
required
disabled={disabled}
label="Сумма"
name="amount"
required
step="0.01"
type="number"
/>

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import { api } from '@/shared/api/client';
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 AddExpenseListPath = paths['/expenses/list']['post']['parameters']['path'];
export type AddExpenseListSuccessResponse = paths['/expenses/list']['post']['responses']['200']['content']['application/json'];
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 { api } from '@/shared/api/client';
export type DeleteExpenseListPath = paths['/expenses/list/{id}']['delete']['parameters']['path'];
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';
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 EditExpenseListPath = paths['/expenses/list/{id}']['patch']['parameters']['path'];
export type EditExpenseListSuccessResponse = paths['/expenses/list/{id}']['patch']['responses']['200']['content']['application/json'];
export async function editExpensesList({ data, path }: {
path: EditExpenseListPath;
data: EditExpenseListBody;
path: EditExpenseListPath;
}) {
const { id } = path;
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 { api } from '@/shared/api/client';
type ExpenseListPath = paths['/expenses/list/{id}']['get']['parameters']['path'];
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 { api } from '@/shared/api/client';
type ExpensesListResponse = paths['/expenses/list']['get']['responses']['200']['content']['application/json'];
export async function getExpenseLists() {

View File

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

View File

@ -1,10 +1,14 @@
export type { ExpenseList } from './types';
export { useDeleteExpensesList } from './hooks/useDeleteExpensesList';
export { useAddExpensesList } from './hooks/useAddExpensesList';
export { useExpensesList } from './hooks/useExpensesList';
export { useEditExpensesList } from './hooks/useEditExpensesList';
export type { AddExpenseListBody, AddExpenseListPath, AddExpenseListSuccessResponse } from './api/addExpensesList';
export type { DeleteExpenseListPath, DeleteExpenseSuccessResponse } from './api/deleteExpensesList';
export type { EditExpenseListBody, EditExpenseListPath, EditExpenseListSuccessResponse } from './api/editExpensesList';
export { useAddExpensesList } from './hooks/useAddExpensesList';
export { useDeleteExpensesList } from './hooks/useDeleteExpensesList';
export { useEditExpensesList } from './hooks/useEditExpensesList';
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 { 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 { 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 userEvent from '@testing-library/user-event';
import AddExpensesListForm from './AddExpensesListForm.ui';
import type { AddExpenseListFormProps } from './AddExpensesListForm.ui';
import AddExpensesListForm from './AddExpensesListForm.ui';
const renderForm = (props?: Partial<AddExpenseListFormProps>) => {
const onSubmit = jest.fn();
@ -14,9 +14,9 @@ const renderForm = (props?: Partial<AddExpenseListFormProps>) => {
const submitButtonElem = screen.getByText('Добавить');
return {
titleInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
};
};
@ -42,9 +42,9 @@ describe('Test AddExpensesListForm', () => {
test('should call onSubmit with values', async () => {
const {
titleInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm();
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 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 & {
disabled?: boolean;
onSubmit: (data: AddExpenseListFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>;
};
export type AddExpenseListFormValues = {
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const AddExpensesListForm: FC<AddExpenseListFormProps> = (props) => {
const {
ref,
disabled,
onSubmit,
ref,
...restProps
} = props;
@ -45,9 +45,9 @@ const AddExpensesListForm: FC<AddExpenseListFormProps> = (props) => {
return (
<form
ref={ref}
className={classes.form}
onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps}
>
<h2>Добавить список расходов</h2>

View File

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

View File

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

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import EditExpensesListForm from './EditExpensesListForm.ui';
import type { EditExpensesListFormProps } from './EditExpensesListForm.ui';
import EditExpensesListForm from './EditExpensesListForm.ui';
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',
id: '10cc093c-3875-4d33-a40a-df526266e262',
items: [],
title: 'Mock list',
updatedAt: '2025-08-29T07:57:40.503Z',
};
const renderForm = (props?: Partial<EditExpensesListFormProps>) => {
@ -29,9 +29,9 @@ const renderForm = (props?: Partial<EditExpensesListFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить');
return {
titleInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
};
};
@ -64,9 +64,9 @@ describe('Test EditExpensesListForm', () => {
test('should call onSubmit with values', async () => {
const {
titleInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm();
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 {
FC,
FormEventHandler,
@ -10,25 +5,31 @@ import type {
RefObject,
} from 'react';
import type { ExpenseList } from '@/entity/expenses/list';
import { Input } from '@/shared/ui/Input';
import classes from './EditExpenseListForm.module.css';
export type EditExpenseListFormValues = {
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
export type EditExpensesListFormProps = BaseFormProps & {
disabled?: boolean;
list: ExpenseList;
onSubmit: (data: EditExpenseListFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>;
list: ExpenseList;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const EditExpensesListForm: FC<EditExpensesListFormProps> = (props) => {
const {
ref,
disabled,
onSubmit,
list,
onSubmit,
ref,
...restProps
} = props;
@ -46,9 +47,9 @@ const EditExpensesListForm: FC<EditExpensesListFormProps> = (props) => {
return (
<form
ref={ref}
className={classes.form}
onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps}
>
<h2>Редактировать список расходов</h2>

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
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 AddIncomeItemPath = paths['/incomes/list/{id}/items']['post']['parameters']['path'];
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 { api } from '@/shared/api/client';
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'];

View File

@ -1,7 +1,7 @@
import { api } from '@/shared/api/client';
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 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'];

View File

@ -2,10 +2,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { QueryKeys } from '@/shared/api/queryKeys';
import { deleteIncomeItem } from '../api/deleteIncomeItem';
import type { DeleteIncomeItemPath } from '../api/deleteIncomeItem';
import { deleteIncomeItem } from '../api/deleteIncomeItem';
export const useDeleteIncomeItem = (path: DeleteIncomeItemPath) => {
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 { useDeleteIncomeItem } from './hooks/useDeleteIncomeItem';
export { useEditIncomeItem } from './hooks/useEditIncomeItem';
export type { AddIncomeItemPath, AddIncomeItemBody, AddIncomeItemSuccessResponse } from './api/addIncomeItem';
export type { DeleteIncomeItemPath, DeleteIncomeItemSuccessResponse } from './api/deleteIncomeItem';
export type { EditIncomeItemPath, EditIncomeItemBody, EditIncomeItemSuccessResponse } from './api/editIncomeItem';
export type { IncomeListItem } from './types';
export type { AddIncomeItemFormValues } 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 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 userEvent from '@testing-library/user-event';
import AddIncomeItemForm from './AddIncomeItemForm.ui';
import type { AddIncomeItemFormProps } from './AddIncomeItemForm.ui';
import AddIncomeItemForm from './AddIncomeItemForm.ui';
const renderForm = (props?: Partial<AddIncomeItemFormProps>) => {
const onSubmit = jest.fn();
@ -17,12 +17,12 @@ const renderForm = (props?: Partial<AddIncomeItemFormProps>) => {
const submitButtonElem = screen.getByText('Добавить');
return {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem,
submitButtonElem,
dateInputElem,
descriptionInputElem,
onSubmit,
submitButtonElem,
titleInputElem,
};
};
@ -48,12 +48,12 @@ describe('Test AddIncomeItemForm', () => {
test('should call onSubmit with values', async () => {
const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem,
submitButtonElem,
dateInputElem,
descriptionInputElem,
onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm();
await userEvent.type(titleInputElem, 'Название элемента');
@ -65,11 +65,11 @@ describe('Test AddIncomeItemForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({
title: 'Название элемента',
description: 'Описание элемента',
date: '2025-08-29T00:00:00.000Z',
amount: 12,
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 {
FC,
FormEventHandler,
@ -9,15 +5,9 @@ import type {
RefObject,
} from 'react';
export type AddIncomeItemFormValues = {
title: string;
description: string;
date: string;
amount: number;
currency: string;
};
import { Input } from '@/shared/ui/Input';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
import classes from './AddIncomeItemForm.module.css';
export type AddIncomeItemFormProps = BaseFormProps & {
disabled?: boolean;
@ -25,6 +15,16 @@ export type AddIncomeItemFormProps = BaseFormProps & {
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 {
disabled,
@ -45,29 +45,29 @@ const AddIncomeItemForm: FC<AddIncomeItemFormProps> = (props) => {
if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string') {
onSubmit?.({
title,
date: new Date(date).toISOString(),
amount: Number(amount),
description,
currency: 'RUB',
date: new Date(date).toISOString(),
description,
title,
});
}
};
return (
<form
ref={ref}
className={classes.form}
onSubmit={onFormSubmitHandler}
ref={ref}
{...formProps}
>
<h2>Добавить список доходов</h2>
<Input
required
disabled={disabled}
label="Название"
name="title"
required
/>
<Input
@ -77,18 +77,18 @@ const AddIncomeItemForm: FC<AddIncomeItemFormProps> = (props) => {
/>
<Input
required
disabled={disabled}
label="Дата"
name="date"
required
type="date"
/>
<Input
required
disabled={disabled}
label="Сумма"
name="amount"
required
step="0.01"
type="number"
/>

View File

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

View File

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

View File

@ -1,21 +1,21 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import EditIncomeItemForm from './EditIncomeItemForm.ui';
import type { EditIncomeItemFormProps } from './EditIncomeItemForm.ui';
import EditIncomeItemForm from './EditIncomeItemForm.ui';
const editingItem = {
id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6',
title: 'Mock item',
amount: 12,
count: 12,
updatedAt: '2025-08-29T07:57:40.503Z',
incomeListId: '10cc093c-3875-4d33-a40a-df526266e262',
createdAt: '2025-08-29T07:57:40.503Z',
currency: 'RUB',
date: '2025-08-22T00:00:03.000Z',
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>) => {
@ -37,12 +37,12 @@ const renderForm = (props?: Partial<EditIncomeItemFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить');
return {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem,
submitButtonElem,
dateInputElem,
descriptionInputElem,
onSubmit,
submitButtonElem,
titleInputElem,
};
};
@ -56,10 +56,10 @@ describe('Test EditIncomeItemForm', () => {
test('should inputs have default value', () => {
const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem,
dateInputElem,
descriptionInputElem,
titleInputElem,
} = renderForm();
expect(titleInputElem).toHaveValue('Mock item');
expect(descriptionInputElem).toHaveValue('Mock description');
@ -81,12 +81,12 @@ describe('Test EditIncomeItemForm', () => {
test('should call onSubmit with values', async () => {
const {
titleInputElem,
descriptionInputElem,
dateInputElem,
amountInputElem,
submitButtonElem,
dateInputElem,
descriptionInputElem,
onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm();
await userEvent.clear(titleInputElem);
@ -105,11 +105,11 @@ describe('Test EditIncomeItemForm', () => {
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({
title: 'Название элемента',
description: 'Описание элемента',
date: '2025-08-29T00:00:00.000Z',
amount: 12,
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 {
FC,
FormEventHandler,
@ -10,28 +5,34 @@ import type {
RefObject,
} from 'react';
export type EditIncomeItemFormValues = {
title: string;
description: string;
date: string;
amount: number;
currency: string;
};
import { Input } from '@/shared/ui/Input';
import type { IncomeListItem } from '../../types';
import classes from './EditIncomeItemForm.module.css';
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
export type EditIncomeItemFormProps = BaseFormProps & {
disabled?: boolean;
item: IncomeListItem;
onSubmit?: (data: EditIncomeItemFormValues) => Promise<void> | void;
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 {
disabled,
item,
onSubmit,
ref,
item,
...formProps
} = props;
@ -47,20 +48,20 @@ const EditIncomeItemForm: FC<EditIncomeItemFormProps> = (props) => {
if (typeof title === 'string' && typeof date === 'string' && typeof amount === 'string' && typeof description === 'string') {
await onSubmit?.({
title,
date: new Date(date).toISOString(),
amount: Number(amount),
description,
currency: 'RUB',
date: new Date(date).toISOString(),
description,
title,
});
}
};
return (
<form
ref={ref}
className={classes.form}
onSubmit={onFormSubmitHandler}
ref={ref}
{...formProps}
>
<h2>Изменить доход</h2>

View File

@ -1,9 +1,9 @@
import { api } from '@/shared/api/client';
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 AddIncomeListPath = paths['/incomes/list']['post']['parameters']['path'];
export type AddIncomeListSuccessResponse = paths['/incomes/list']['post']['responses']['200']['content']['application/json'];
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 { api } from '@/shared/api/client';
export type DeleteIncomeListPath = paths['/incomes/list/{id}']['delete']['parameters']['path'];
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';
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 EditIncomeListPath = paths['/incomes/list/{id}']['patch']['parameters']['path'];
export type EditIncomeListSuccessResponse = paths['/incomes/list/{id}']['patch']['responses']['200']['content']['application/json'];
export async function editIncomeList({ data, path }: {
path: EditIncomeListPath;
data: EditIncomeListBody;
path: EditIncomeListPath;
}) {
const { id } = path;
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 { api } from '@/shared/api/client';
type IncomeListPath = paths['/incomes/list/{id}']['get']['parameters']['path'];
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 { api } from '@/shared/api/client';
type IncomesListResponse = paths['/incomes/list']['get']['responses']['200']['content']['application/json'];
export async function getIncomeLists() {

View File

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

View File

@ -1,10 +1,14 @@
export type { IncomeList } from './types';
export { useDeleteIncomesList } from './hooks/useDeleteIncomesList';
export { useAddIncomesList } from './hooks/useAddIncomesList';
export { useIncomesList } from './hooks/useIncomesList';
export { useEditIncomesList } from './hooks/useEditIncomesList';
export type { AddIncomeListBody, AddIncomeListPath, AddIncomeListSuccessResponse } from './api/addIncomeList';
export type { DeleteIncomeListPath, DeleteIncomeListSuccessResponse } from './api/deleteIncomeList';
export type { EditIncomeListBody, EditIncomeListPath, EditIncomeListSuccessResponse } from './api/editIncomeList';
export { useAddIncomesList } from './hooks/useAddIncomesList';
export { useDeleteIncomesList } from './hooks/useDeleteIncomesList';
export { useEditIncomesList } from './hooks/useEditIncomesList';
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 { 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 { 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 userEvent from '@testing-library/user-event';
import AddIncomeListForm from './AddIncomeListForm.ui';
import type { AddIncomeListFormProps } from './AddIncomeListForm.ui';
import AddIncomeListForm from './AddIncomeListForm.ui';
const renderForm = (props?: Partial<AddIncomeListFormProps>) => {
const onSubmit = jest.fn();
@ -14,9 +14,9 @@ const renderForm = (props?: Partial<AddIncomeListFormProps>) => {
const submitButtonElem = screen.getByText('Добавить');
return {
titleInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
};
};
@ -42,9 +42,9 @@ describe('Test AddIncomeListForm', () => {
test('should call onSubmit with values', async () => {
const {
titleInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm();
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 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 & {
disabled?: boolean;
onSubmit: (data: AddIncomeListFormValues) => Promise<void> | void;
ref?: RefObject<HTMLFormElement>;
};
export type AddIncomeListFormValues = {
title: string;
};
type BaseFormProps = Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>;
const AddIncomeListForm: FC<AddIncomeListFormProps> = (props) => {
const {
ref,
disabled,
onSubmit,
ref,
...restProps
} = props;
@ -45,9 +45,9 @@ const AddIncomeListForm: FC<AddIncomeListFormProps> = (props) => {
return (
<form
ref={ref}
className={classes.form}
onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps}
>
<h2>Добавить список доходов</h2>

View File

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

View File

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

View File

@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import EditIncomeListForm from './EditIncomeListForm.ui';
import type { EditExpensesListFormProps } from './EditIncomeListForm.ui';
import EditIncomeListForm from './EditIncomeListForm.ui';
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',
id: '10cc093c-3875-4d33-a40a-df526266e262',
items: [],
title: 'Mock list',
updatedAt: '2025-08-29T07:57:40.503Z',
};
const renderForm = (props?: Partial<EditExpensesListFormProps>) => {
@ -29,9 +29,9 @@ const renderForm = (props?: Partial<EditExpensesListFormProps>) => {
const submitButtonElem = screen.getByText('Сохранить');
return {
titleInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
};
};
@ -64,9 +64,9 @@ describe('Test EditIncomeListForm', () => {
test('should call onSubmit with values', async () => {
const {
titleInputElem,
submitButtonElem,
onSubmit,
submitButtonElem,
titleInputElem,
} = renderForm();
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 {
FC,
FormEventHandler,
@ -10,25 +5,31 @@ import type {
RefObject,
} 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 = {
title: string;
};
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 {
ref,
disabled,
onSubmit,
list,
onSubmit,
ref,
...restProps
} = props;
@ -46,9 +47,9 @@ const EditExpensesListForm: FC<EditExpensesListFormProps> = (props) => {
return (
<form
ref={ref}
className={classes.form}
onSubmit={onFormSubmitHandler}
ref={ref}
{...restProps}
>
<h2>Редактировать список доходов</h2>

View File

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

View File

@ -1,9 +1,9 @@
import { type MouseEventHandler, useState } from 'react';
import { useAddBankCard } from '@/entity/cards';
import type { AddBankCardFormValues } from '@/entity/cards';
import { useAddBankCard } from '@/entity/cards';
type UseAddBankCardButtonArgs = {
onClick?: MouseEventHandler<HTMLButtonElement>;
};
@ -12,7 +12,7 @@ export const useAddBankCardButton = (args: UseAddBankCardButtonArgs) => {
const { onClick } = args;
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: addBankCardMutation, isPending } = useAddBankCard();
const { isPending, mutateAsync: addBankCardMutation } = useAddBankCard();
const onCloseModalHandler = () => {
setIsOpen(false);
@ -31,8 +31,8 @@ export const useAddBankCardButton = (args: UseAddBankCardButtonArgs) => {
return {
isOpen,
isPending,
onCloseModalHandler,
onClickHandler,
onCloseModalHandler,
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 userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { server } from '@mocks/jest/server';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import type { AddBankCardBody, AddBankCardResponse } from '@/entity/cards';
import AddBankCardButton from './AddBankCardButton.ui';
import type { AddBankCardBody, AddBankCardResponse } from '@/entity/cards';
const renderButton = () => {
render(
<AddBankCardButton data-testid="test-button" />,
@ -44,11 +43,11 @@ const submitHandler = (requestSpy: jest.Mock) => http.post<never, AddBankCardBod
requestSpy(body);
return HttpResponse.json({
balance: 999,
createdAt: '2025-08-29T07:57:40.503Z',
id: '10cc093c-3875-4d33-a40a-df526266e262',
name: 'Название карты',
balance: 999,
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 { Modal } from '@/shared/ui/Modal';
import { useAddBankCardButton } from '../hooks/useAddBankCardButton';
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
type AddBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
ref?: Ref<HTMLButtonElement>;
};
const AddBankCardButton: FC<AddBankCardButtonProps> = (props) => {
const { ref, onClick, ...restProps } = props;
const { onClick, ref, ...restProps } = props;
const {
isOpen,
isPending,
onCloseModalHandler,
onClickHandler,
onCloseModalHandler,
onFormSubmitHandler,
} = useAddBankCardButton({ onClick });
return (
<>
<button
onClick={onClickHandler}
ref={ref}
type="button"
onClick={onClickHandler}
{...restProps}
>
Добавить
</button>
<Modal open={isOpen} onClose={onCloseModalHandler}>
<Modal onClose={onCloseModalHandler} open={isOpen}>
<AddBankCardForm disabled={isPending} onSubmit={onFormSubmitHandler} />
</Modal>
</>

View File

@ -1,9 +1,9 @@
import { useState } from 'react';
import { useDeleteBankCard } from '@/entity/cards';
import type { BankCard } from '@/entity/cards';
import { useDeleteBankCard } from '@/entity/cards';
type UseDeleteBankCardButtonArgs = {
id: BankCard['id'];
};
@ -12,7 +12,7 @@ export const useDeleteBankCardButton = (args: UseDeleteBankCardButtonArgs) => {
const { id } = args;
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: deleteBankCardAsync, isPending } = useDeleteBankCard();
const { isPending, mutateAsync: deleteBankCardAsync } = useDeleteBankCard();
const onClickHandler = () => {
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 userEvent from '@testing-library/user-event';
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 { DeleteExpenseListPath } from '@/entity/expenses/list';
import DeleteBankCardButton from './DeleteBankCardButton.ui';
const deletingBankCard: BankCard = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Тестовая карта',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
};
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 { Modal } from '@/shared/ui/Modal';
import { useDeleteBankCardButton } from '../hooks/useDeleteBankCardButton';
import type { BankCard } from '@/entity/cards';
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
export type DeleteBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
bankCard: BankCard;
ref?: Ref<HTMLButtonElement>;
@ -14,26 +15,26 @@ export type DeleteBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement>
const DeleteBankCardButton: FC<DeleteBankCardButtonProps> = (props) => {
const {
bankCard,
ref,
onClick,
ref,
...restProps
} = props;
const {
isOpen,
isPending,
onCloseModalHandler,
onClickHandler,
onCloseModalHandler,
onFormSubmitHandler,
} = useDeleteBankCardButton({ id: bankCard.id });
return (
<>
<button type="button" onClick={onClickHandler} {...restProps}>
<button onClick={onClickHandler} type="button" {...restProps}>
Удалить
</button>
<Modal open={isOpen} onClose={onCloseModalHandler}>
<Modal onClose={onCloseModalHandler} open={isOpen}>
<DeleteBankCardForm
bankCard={bankCard}
disabled={isPending}

View File

@ -1,9 +1,9 @@
import { useState } from 'react';
import { useEditBankCard } from '@/entity/cards';
import type { BankCard, EditBankCardFormValues } from '@/entity/cards';
import { useEditBankCard } from '@/entity/cards';
type UseDeleteBankCardButtonArgs = {
bankCard: BankCard;
};
@ -12,7 +12,7 @@ export const useEditBankCardButton = (args: UseDeleteBankCardButtonArgs) => {
const { bankCard } = args;
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: editBankCardAsync, isPending } = useEditBankCard();
const { isPending, mutateAsync: editBankCardAsync } = useEditBankCard();
const onClickHandler = () => {
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 userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { server } from '@mocks/jest/server';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import EditBankCardButton from './EditBankCardButton.ui';
import type {
BankCard,
EditBankCardBody,
@ -14,12 +11,14 @@ import type {
EditBankCardResponse,
} from '@/entity/cards';
import EditBankCardButton from './EditBankCardButton.ui';
const editingBankCard: BankCard = {
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Тестовая карта',
balance: 1000,
updatedAt: '2025-08-29T07:57:40.503Z',
createdAt: '2025-08-29T07:57:40.503Z',
};
const fillForm = async (data: EditBankCardBody) => {
@ -64,11 +63,11 @@ const submitHandler = (requestSpy: jest.Mock) => http.patch<EditBankCardPath, Ed
requestSpy(body);
return HttpResponse.json({
balance: 1000,
createdAt: '2025-08-29T07:57:40.503Z',
id: 'af6da4e1-f24f-4b43-8556-fa885626897d',
name: 'Тестовая карта',
balance: 1000,
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 { Modal } from '@/shared/ui/Modal';
import { useEditBankCardButton } from '../hooks/useEditBankCardButton';
import type { BankCard } from '@/entity/cards';
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
export type EditBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
bankCard: BankCard;
ref?: Ref<HTMLButtonElement>;
@ -13,26 +14,26 @@ export type EditBankCardButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
const EditBankCardButton: FC<EditBankCardButtonProps> = (props) => {
const {
bankCard,
ref,
onClick,
ref,
...restProps
} = props;
const {
isOpen,
isPending,
onCloseModalHandler,
onClickHandler,
onCloseModalHandler,
onFormSubmitHandler,
} = useEditBankCardButton({ bankCard });
return (
<>
<button type="button" onClick={onClickHandler} {...restProps}>
<button onClick={onClickHandler} type="button" {...restProps}>
Редактировать
</button>
<Modal open={isOpen} onClose={onCloseModalHandler}>
<Modal onClose={onCloseModalHandler} open={isOpen}>
<EditBankCardForm
bankCard={bankCard}
disabled={isPending}

View File

@ -1,9 +1,9 @@
import { type MouseEventHandler, useState } from 'react';
import { useAddCash } from '@/entity/cash';
import type { AddCashFormValues } from '@/entity/cash';
import { useAddCash } from '@/entity/cash';
type UseAddCashButtonArgs = {
onClick?: MouseEventHandler<HTMLButtonElement>;
};
@ -12,7 +12,7 @@ export const useAddCashButton = (args: UseAddCashButtonArgs) => {
const { onClick } = args;
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: addCashMutation, isPending } = useAddCash();
const { isPending, mutateAsync: addCashMutation } = useAddCash();
const onCloseModalHandler = () => {
setIsOpen(false);
@ -31,8 +31,8 @@ export const useAddCashButton = (args: UseAddCashButtonArgs) => {
return {
isOpen,
isPending,
onCloseModalHandler,
onClickHandler,
onCloseModalHandler,
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 userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { server } from '@mocks/jest/server';
import { createReactQueryWrapper } from '@mocks/jest/wrappers';
import type { AdCashResponse, AddCashBody } from '@/entity/cash';
import AddCashButton from './AddCashButton.ui';
import type { AddCashBody, AdCashResponse } from '@/entity/cash';
const renderButton = () => {
render(
<AddCashButton data-testid="test-button" />,
@ -44,11 +43,11 @@ const submitHandler = (requestSpy: jest.Mock) => http.post<never, AddCashBody, A
requestSpy(body);
return HttpResponse.json({
balance: 999,
createdAt: '2025-08-29T07:57:40.503Z',
id: '10cc093c-3875-4d33-a40a-df526266e262',
name: 'Наличные',
balance: 999,
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 { Modal } from '@/shared/ui/Modal';
import { useAddCashButton } from '../hooks/useAddCashButton';
import type { ButtonHTMLAttributes, FC, Ref } from 'react';
type AddCashButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
ref?: Ref<HTMLButtonElement>;
};
const AddCashButton: FC<AddCashButtonProps> = (props) => {
const { ref, onClick, ...restProps } = props;
const { onClick, ref, ...restProps } = props;
const {
isOpen,
isPending,
onCloseModalHandler,
onClickHandler,
onCloseModalHandler,
onFormSubmitHandler,
} = useAddCashButton({ onClick });
return (
<>
<button
onClick={onClickHandler}
ref={ref}
type="button"
onClick={onClickHandler}
{...restProps}
>
Добавить
</button>
<Modal open={isOpen} onClose={onCloseModalHandler}>
<Modal onClose={onCloseModalHandler} open={isOpen}>
<AddCashForm disabled={isPending} onSubmit={onFormSubmitHandler} />
</Modal>
</>

View File

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

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