From 27caa4250704600da580c742b51fad9f8fd9d2aa Mon Sep 17 00:00:00 2001 From: PavloMolytovnyk Date: Tue, 14 Jul 2026 12:52:24 +0300 Subject: [PATCH 1/4] Added and changes some files --- cypress/integration/page.spec.js | 6 +- src/App.tsx | 193 ++++++++++++++++-- .../ErrorNotification/ErrorNotification.tsx | 26 +++ src/Components/Footer/Footer.tsx | 71 +++++++ src/Components/Header/Header.tsx | 61 ++++++ src/Components/TodoList/TodoList.tsx | 106 ++++++++++ src/api/todos.ts | 20 ++ src/types/SortType.ts | 5 + src/types/Todo.ts | 6 + src/utils/fetchClient.ts | 46 +++++ 10 files changed, 522 insertions(+), 18 deletions(-) create mode 100644 src/Components/ErrorNotification/ErrorNotification.tsx create mode 100644 src/Components/Footer/Footer.tsx create mode 100644 src/Components/Header/Header.tsx create mode 100644 src/Components/TodoList/TodoList.tsx create mode 100644 src/api/todos.ts create mode 100644 src/types/SortType.ts create mode 100644 src/types/Todo.ts create mode 100644 src/utils/fetchClient.ts diff --git a/cypress/integration/page.spec.js b/cypress/integration/page.spec.js index fcac2e3d12..a32790a82f 100644 --- a/cypress/integration/page.spec.js +++ b/cypress/integration/page.spec.js @@ -979,7 +979,7 @@ describe('', () => { }); }); - describe('Todo Toggling', () => { + describe.skip('Todo Toggling', () => { beforeEach(() => { page.mockLoad().as('loadRequest'); page.visit(); @@ -1131,7 +1131,7 @@ describe('', () => { }); }); - describe('Toggle All Button', () => { + describe.skip('Toggle All Button', () => { describe('if there are no todos', () => { it('should not be visible while loading todos', () => { page.mockLoad({ body: [] }).as('loadRequest'); @@ -1385,7 +1385,7 @@ describe('', () => { }); }); - describe('Renaming', () => { + describe.skip('Renaming', () => { beforeEach(() => { page.mockLoad().as('loadRequest'); page.visit(); diff --git a/src/App.tsx b/src/App.tsx index 81e011f432..e06bf551eb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,189 @@ -/* eslint-disable max-len */ +/* eslint-disable jsx-a11y/label-has-associated-control */ /* eslint-disable jsx-a11y/control-has-associated-label */ -import React from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { UserWarning } from './UserWarning'; - -const USER_ID = 0; +import { addTodos, deleteTodos, getTodos, USER_ID } from './api/todos'; +import { Todo } from './types/Todo'; +import { TodoList } from './Components/TodoList/TodoList'; +import { Footer } from './Components/Footer/Footer'; +import { SortType } from './types/SortType'; +/* eslint-disable-next-line max-len */ +import { ErrorNotification } from './Components/ErrorNotification/ErrorNotification'; +import { Header } from './Components/Header/Header'; export const App: React.FC = () => { + const [todos, setTodos] = useState([]); + const [sortType, setSortType] = useState(SortType.all); + const [tempTodo, setTempTodo] = useState(null); + const [deletePostsId, setDeletePostsId] = useState([]); + const inputRef = useRef(null); + + const [errorMessage, setErrorMessage] = useState(''); + + useEffect(() => { + setErrorMessage(''); + + getTodos() + .then(todosFromServer => setTodos(todosFromServer)) + .catch(() => { + setErrorMessage('Unable to load todos'); + setTimeout(() => { + setErrorMessage(''); + }, 3000); + + throw new Error(); + }); + }, []); + + const filteredTodos = todos.filter(todo => { + switch (sortType) { + case SortType.Active: { + return !todo.completed; + } + + case SortType.Completed: { + return todo.completed; + } + + case SortType.all: + default: + return todo; + } + }); + + function addDataToServer(listValue: string) { + setErrorMessage(''); + const normalValue = listValue.trim(); + + if (!normalValue) { + setErrorMessage('Title should not be empty'); + + setTimeout(() => { + setErrorMessage(''); + }, 3000); + + return Promise.resolve(false); + } + + const newTodoData = { + title: listValue, + completed: false, + userId: USER_ID, + }; + + setTempTodo({ + id: 0, + ...newTodoData, + }); + + return addTodos(newTodoData) + .then(newTodo => { + setTodos(currentTodo => [...currentTodo, newTodo]); + + return true; + }) + .catch(() => { + setErrorMessage('Unable to add a todo'); + + setTimeout(() => { + setErrorMessage(''); + }, 3000); + + return false; + }) + .finally(() => { + setTempTodo(null); + }); + } + + function deleteDataFromServer(postId: number) { + setErrorMessage(''); + setDeletePostsId(currentIds => [...currentIds, postId]); + + if (!postId) { + setErrorMessage('Title should not be empty to delete'); + + setTimeout(() => { + setErrorMessage(''); + }, 3000); + + return; + } + + deleteTodos(postId) + .then(() => { + setTodos(currentTodos => + currentTodos.filter(todo => todo.id !== postId), + ); + }) + .catch(() => { + setErrorMessage('Unable to delete a todo'); + + setTimeout(() => { + setErrorMessage(''); + }, 3000); + }) + .finally(() => { + setDeletePostsId(idOfPosts => idOfPosts.filter(id => id !== postId)); + inputRef.current?.focus(); + }); + } + + function deleteAllCompletedFromServer() { + const completedId = todos + .filter(todoFromArray => todoFromArray.completed) + .map(todo => todo.id); + + for (const id of completedId) { + deleteDataFromServer(id); + } + } + + const activeTodoCount = todos.filter(todo => !todo.completed).length; + + const hasCompletedTodos = todos.length > activeTodoCount; + if (!USER_ID) { return ; } return ( -
-

- Copy all you need from the prev task: -
- - React Todo App - Add and Delete - -

- -

Styles are already copied

-
+
+

todos

+ +
+
+ + {todos.length > 0 && ( + <> + + +
+ + )} +
+ + setErrorMessage(catchError)} + /> + {/* + Unable to update a todo */} +
); }; diff --git a/src/Components/ErrorNotification/ErrorNotification.tsx b/src/Components/ErrorNotification/ErrorNotification.tsx new file mode 100644 index 0000000000..0d1c3fa089 --- /dev/null +++ b/src/Components/ErrorNotification/ErrorNotification.tsx @@ -0,0 +1,26 @@ +import classNames from 'classnames'; + +interface ErrorProps { + error: string; + setError: (cathcError: string) => void; +} + +export const ErrorNotification = ({ error, setError }: ErrorProps) => { + return ( +
+
+ ); +}; diff --git a/src/Components/Footer/Footer.tsx b/src/Components/Footer/Footer.tsx new file mode 100644 index 0000000000..2bf39adc0f --- /dev/null +++ b/src/Components/Footer/Footer.tsx @@ -0,0 +1,71 @@ +import classNames from 'classnames'; +import { SortType } from '../../types/SortType'; + +interface FooterProps { + activeTodosCount: number; + currentSortType: SortType; + hasCompletedTodos: boolean; + onSortChange: (value: SortType) => void; + deletedAllCompleted: () => void; +} + +export const Footer = ({ + activeTodosCount, + currentSortType, + hasCompletedTodos, + onSortChange, + deletedAllCompleted, +}: FooterProps) => { + return ( + + ); +}; diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx new file mode 100644 index 0000000000..e1e945a8b1 --- /dev/null +++ b/src/Components/Header/Header.tsx @@ -0,0 +1,61 @@ +import classNames from 'classnames'; +import { useState } from 'react'; + +interface HeaderProps { + active: number; + onChange: (value: string) => Promise; + inputRef: React.RefObject; +} + +export const Header = ({ active, onChange, inputRef }: HeaderProps) => { + const [listValue, setListValue] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + const isAllActive = !active ? true : false; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + const normalValue = listValue.trim(); + + setIsSubmitting(true); + + onChange(normalValue) + .then(isSuccess => { + if (isSuccess) { + setListValue(''); + } + }) + .finally(() => { + setIsSubmitting(false); + setTimeout(() => { + inputRef.current?.focus(); + }, 0); + }); + }; + + return ( +
+ {/* this button should have `active` class only if all todos are completed */} +
+ ); +}; diff --git a/src/Components/TodoList/TodoList.tsx b/src/Components/TodoList/TodoList.tsx new file mode 100644 index 0000000000..a15289843d --- /dev/null +++ b/src/Components/TodoList/TodoList.tsx @@ -0,0 +1,106 @@ +import classNames from 'classnames'; +import { Todo } from '../../types/Todo'; +import React from 'react'; + +interface TodoListProps { + filteredTodos: Todo[]; + tempTodo: Todo | null; + deleteData: (id: number) => void; + deleteId: number[]; +} + +export const TodoList = ({ + filteredTodos, + tempTodo, + deleteData, + deleteId, +}: TodoListProps) => { + const handleDelete = (id: number) => { + deleteData(id); + }; + + return ( +
+ {filteredTodos.map(todo => ( +
+ + + + {todo.title} + + + +
+ {/* eslint-disable-next-line max-len */} +
+
+
+
+ ))} + + {tempTodo && ( +
+ + + + {tempTodo.title} + + + + +
+
+
+
+
+ )} +
+ ); +}; diff --git a/src/api/todos.ts b/src/api/todos.ts new file mode 100644 index 0000000000..77941e8b19 --- /dev/null +++ b/src/api/todos.ts @@ -0,0 +1,20 @@ +import { Todo } from '../types/Todo'; +import { client } from '../utils/fetchClient'; + +export const USER_ID = 4356; + +export const getTodos = () => { + return client.get(`/todos?userId=${USER_ID}`); +}; + +export const addTodos = (data: Omit) => { + return client.post(`/todos`, data); +}; + +export const deleteTodos = (postId: number) => { + return client.delete(`/todos/${postId}`); +}; + +export const changeTodos = (data: Omit) => { + return client.patch(`/todos/${USER_ID}`, data); +}; diff --git a/src/types/SortType.ts b/src/types/SortType.ts new file mode 100644 index 0000000000..47d9fffd18 --- /dev/null +++ b/src/types/SortType.ts @@ -0,0 +1,5 @@ +export enum SortType { + all = 'all', + Active = 'active', + Completed = 'completed', +} diff --git a/src/types/Todo.ts b/src/types/Todo.ts new file mode 100644 index 0000000000..3f52a5fdde --- /dev/null +++ b/src/types/Todo.ts @@ -0,0 +1,6 @@ +export interface Todo { + id: number; + userId: number; + title: string; + completed: boolean; +} diff --git a/src/utils/fetchClient.ts b/src/utils/fetchClient.ts new file mode 100644 index 0000000000..708ac4c17b --- /dev/null +++ b/src/utils/fetchClient.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +const BASE_URL = 'https://mate.academy/students-api'; + +// returns a promise resolved after a given delay +function wait(delay: number) { + return new Promise(resolve => { + setTimeout(resolve, delay); + }); +} + +// To have autocompletion and avoid mistypes +type RequestMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE'; + +function request( + url: string, + method: RequestMethod = 'GET', + data: any = null, // we can send any data to the server +): Promise { + const options: RequestInit = { method }; + + if (data) { + // We add body and Content-Type only for the requests with data + options.body = JSON.stringify(data); + options.headers = { + 'Content-Type': 'application/json; charset=UTF-8', + }; + } + + // DON'T change the delay it is required for tests + return wait(100) + .then(() => fetch(BASE_URL + url, options)) + .then(response => { + if (!response.ok) { + throw new Error(); + } + + return response.json(); + }); +} + +export const client = { + get: (url: string) => request(url), + post: (url: string, data: any) => request(url, 'POST', data), + patch: (url: string, data: any) => request(url, 'PATCH', data), + delete: (url: string) => request(url, 'DELETE'), +}; From 1a8f8706ee3f727cf248c87cb4da7e2a56c8ec32 Mon Sep 17 00:00:00 2001 From: PavloMolytovnyk Date: Tue, 14 Jul 2026 16:48:01 +0300 Subject: [PATCH 2/4] Added toggle property --- cypress/integration/page.spec.js | 6 +-- package-lock.json | 9 ++-- package.json | 2 +- src/App.tsx | 79 +++++++++++++++++++++++++--- src/Components/Header/Header.tsx | 26 ++++++--- src/Components/TodoList/TodoList.tsx | 11 +++- src/api/todos.ts | 4 +- 7 files changed, 111 insertions(+), 26 deletions(-) diff --git a/cypress/integration/page.spec.js b/cypress/integration/page.spec.js index a32790a82f..080d387fb4 100644 --- a/cypress/integration/page.spec.js +++ b/cypress/integration/page.spec.js @@ -519,7 +519,7 @@ describe('', () => { }); // this test may be flaky - it.skip('should replace loader with a created todo', () => { + it('should replace loader with a created todo', () => { page.flushJSTimers(); todos.assertCount(6); todos.assertNotLoading(5); @@ -979,7 +979,7 @@ describe('', () => { }); }); - describe.skip('Todo Toggling', () => { + describe('Todo Toggling', () => { beforeEach(() => { page.mockLoad().as('loadRequest'); page.visit(); @@ -1131,7 +1131,7 @@ describe('', () => { }); }); - describe.skip('Toggle All Button', () => { + describe('Toggle All Button', () => { describe('if there are no todos', () => { it('should not be visible while loading todos', () => { page.mockLoad({ body: [] }).as('loadRequest'); diff --git a/package-lock.json b/package-lock.json index 19701e8788..0dbc74a8e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", @@ -1183,10 +1183,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.5.tgz", - "integrity": "sha512-mHRY2FkuoYCf5U0ahIukkaRo5LSZsxrTSgMJheFoyf3VXsTvfM9OfWcZIDIDB521kdPrScHHnRp+JRNjCfUO5A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", diff --git a/package.json b/package.json index b6062525ab..6d0f20adcc 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", diff --git a/src/App.tsx b/src/App.tsx index e06bf551eb..e6f2a04c60 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,13 @@ /* eslint-disable jsx-a11y/control-has-associated-label */ import React, { useEffect, useRef, useState } from 'react'; import { UserWarning } from './UserWarning'; -import { addTodos, deleteTodos, getTodos, USER_ID } from './api/todos'; +import { + addTodos, + changeTodos, + deleteTodos, + getTodos, + USER_ID, +} from './api/todos'; import { Todo } from './types/Todo'; import { TodoList } from './Components/TodoList/TodoList'; import { Footer } from './Components/Footer/Footer'; @@ -15,7 +21,7 @@ export const App: React.FC = () => { const [todos, setTodos] = useState([]); const [sortType, setSortType] = useState(SortType.all); const [tempTodo, setTempTodo] = useState(null); - const [deletePostsId, setDeletePostsId] = useState([]); + const [changePostsId, setChangePostsId] = useState([]); const inputRef = useRef(null); const [errorMessage, setErrorMessage] = useState(''); @@ -98,7 +104,7 @@ export const App: React.FC = () => { function deleteDataFromServer(postId: number) { setErrorMessage(''); - setDeletePostsId(currentIds => [...currentIds, postId]); + setChangePostsId(currentIds => [...currentIds, postId]); if (!postId) { setErrorMessage('Title should not be empty to delete'); @@ -124,7 +130,7 @@ export const App: React.FC = () => { }, 3000); }) .finally(() => { - setDeletePostsId(idOfPosts => idOfPosts.filter(id => id !== postId)); + setChangePostsId(idOfPosts => idOfPosts.filter(id => id !== postId)); inputRef.current?.focus(); }); } @@ -139,6 +145,62 @@ export const App: React.FC = () => { } } + function changeDataToServer(changeId: number) { + setErrorMessage(''); + setChangePostsId(currentIds => [...currentIds, changeId]); + const searcData = todos.find(todo => todo.id === changeId) || null; + + if (!searcData) { + setErrorMessage('Unable to update a todo'); + + setTimeout(() => { + setErrorMessage(''); + }, 3000); + + return; + } + + const changePost = { + ...searcData, + completed: !searcData.completed, + }; + + changeTodos(changePost) + .then(updatedTodo => + setTodos(currentPosts => { + return currentPosts.map(post => + post.id === updatedTodo.id ? updatedTodo : post, + ); + }), + ) + .catch(() => { + setErrorMessage('Unable to update a todo'); + + setTimeout(() => { + setErrorMessage(''); + }, 3000); + }) + .finally(() => { + setChangePostsId(currentIds => + currentIds.filter(currentId => currentId !== changeId), + ); + }); + } + + function changeAllToServer() { + let targetTodos = todos.filter(todo => !todo.completed); + + if (targetTodos.length === 0) { + targetTodos = todos; + } + + const changesId = targetTodos.map(todo => todo.id); + + for (const id of changesId) { + changeDataToServer(id); + } + } + const activeTodoCount = todos.filter(todo => !todo.completed).length; const hasCompletedTodos = todos.length > activeTodoCount; @@ -153,9 +215,11 @@ export const App: React.FC = () => {
{todos.length > 0 && ( @@ -163,15 +227,16 @@ export const App: React.FC = () => {
@@ -182,8 +247,6 @@ export const App: React.FC = () => { error={errorMessage} setError={catchError => setErrorMessage(catchError)} /> - {/* - Unable to update a todo */}
); }; diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx index e1e945a8b1..76701735b9 100644 --- a/src/Components/Header/Header.tsx +++ b/src/Components/Header/Header.tsx @@ -1,13 +1,22 @@ import classNames from 'classnames'; import { useState } from 'react'; +import { Todo } from '../../types/Todo'; interface HeaderProps { + todos: Todo[]; active: number; onChange: (value: string) => Promise; inputRef: React.RefObject; + changeAll: () => void; } -export const Header = ({ active, onChange, inputRef }: HeaderProps) => { +export const Header = ({ + todos, + active, + onChange, + inputRef, + changeAll, +}: HeaderProps) => { const [listValue, setListValue] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); @@ -36,11 +45,16 @@ export const Header = ({ active, onChange, inputRef }: HeaderProps) => { return (
{/* this button should have `active` class only if all todos are completed */} - + + + )}
Date: Tue, 14 Jul 2026 19:01:17 +0300 Subject: [PATCH 4/4] file managment --- src/Components/ErrorNotification/ErrorNotification.tsx | 6 +----- src/Components/Footer/Footer.tsx | 9 +-------- src/Components/Header/Header.tsx | 10 +--------- src/Components/TodoList/TodoList.tsx | 10 +--------- src/types/ErrorProps.ts | 4 ++++ src/types/FooterProps.ts | 9 +++++++++ src/types/HeaderProps.ts | 9 +++++++++ src/types/TodoListProps.ts | 10 ++++++++++ 8 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 src/types/ErrorProps.ts create mode 100644 src/types/FooterProps.ts create mode 100644 src/types/HeaderProps.ts create mode 100644 src/types/TodoListProps.ts diff --git a/src/Components/ErrorNotification/ErrorNotification.tsx b/src/Components/ErrorNotification/ErrorNotification.tsx index 0d1c3fa089..c900661df6 100644 --- a/src/Components/ErrorNotification/ErrorNotification.tsx +++ b/src/Components/ErrorNotification/ErrorNotification.tsx @@ -1,9 +1,5 @@ import classNames from 'classnames'; - -interface ErrorProps { - error: string; - setError: (cathcError: string) => void; -} +import { ErrorProps } from '../../types/ErrorProps'; export const ErrorNotification = ({ error, setError }: ErrorProps) => { return ( diff --git a/src/Components/Footer/Footer.tsx b/src/Components/Footer/Footer.tsx index 2bf39adc0f..71d31f888c 100644 --- a/src/Components/Footer/Footer.tsx +++ b/src/Components/Footer/Footer.tsx @@ -1,13 +1,6 @@ import classNames from 'classnames'; import { SortType } from '../../types/SortType'; - -interface FooterProps { - activeTodosCount: number; - currentSortType: SortType; - hasCompletedTodos: boolean; - onSortChange: (value: SortType) => void; - deletedAllCompleted: () => void; -} +import { FooterProps } from '../../types/FooterProps'; export const Footer = ({ activeTodosCount, diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx index 76701735b9..b3d6e18b13 100644 --- a/src/Components/Header/Header.tsx +++ b/src/Components/Header/Header.tsx @@ -1,14 +1,6 @@ import classNames from 'classnames'; import { useState } from 'react'; -import { Todo } from '../../types/Todo'; - -interface HeaderProps { - todos: Todo[]; - active: number; - onChange: (value: string) => Promise; - inputRef: React.RefObject; - changeAll: () => void; -} +import { HeaderProps } from '../../types/HeaderProps'; export const Header = ({ todos, diff --git a/src/Components/TodoList/TodoList.tsx b/src/Components/TodoList/TodoList.tsx index b1b4e0e8d7..88c0086419 100644 --- a/src/Components/TodoList/TodoList.tsx +++ b/src/Components/TodoList/TodoList.tsx @@ -1,15 +1,7 @@ import classNames from 'classnames'; import { Todo } from '../../types/Todo'; import React, { useRef, useState } from 'react'; - -interface TodoListProps { - filteredTodos: Todo[]; - tempTodo: Todo | null; - deleteId: number[]; - deleteData: (id: number) => Promise; - changeStatusData: (id: number) => void; - renameData: (id: number, newTitle: string) => Promise; -} +import { TodoListProps } from '../../types/TodoListProps'; export const TodoList = ({ filteredTodos, diff --git a/src/types/ErrorProps.ts b/src/types/ErrorProps.ts new file mode 100644 index 0000000000..34e0651b19 --- /dev/null +++ b/src/types/ErrorProps.ts @@ -0,0 +1,4 @@ +export interface ErrorProps { + error: string; + setError: (cathcError: string) => void; +} diff --git a/src/types/FooterProps.ts b/src/types/FooterProps.ts new file mode 100644 index 0000000000..dd010dd3c1 --- /dev/null +++ b/src/types/FooterProps.ts @@ -0,0 +1,9 @@ +import { SortType } from "./SortType"; + +export interface FooterProps { + activeTodosCount: number; + currentSortType: SortType; + hasCompletedTodos: boolean; + onSortChange: (value: SortType) => void; + deletedAllCompleted: () => void; +} diff --git a/src/types/HeaderProps.ts b/src/types/HeaderProps.ts new file mode 100644 index 0000000000..5d06df1cd8 --- /dev/null +++ b/src/types/HeaderProps.ts @@ -0,0 +1,9 @@ +import { Todo } from "./Todo"; + +export interface HeaderProps { + todos: Todo[]; + active: number; + onChange: (value: string) => Promise; + inputRef: React.RefObject; + changeAll: () => void; +} diff --git a/src/types/TodoListProps.ts b/src/types/TodoListProps.ts new file mode 100644 index 0000000000..649cd402da --- /dev/null +++ b/src/types/TodoListProps.ts @@ -0,0 +1,10 @@ +import { Todo } from "./Todo"; + +export interface TodoListProps { + filteredTodos: Todo[]; + tempTodo: Todo | null; + deleteId: number[]; + deleteData: (id: number) => Promise; + changeStatusData: (id: number) => void; + renameData: (id: number, newTitle: string) => Promise; +}