├── src
├── components
│ ├── TodoList.module.css
│ ├── TodoForm.module.css
│ ├── TaskFilters.module.css
│ ├── TodoList.tsx
│ ├── TaskFilters.tsx
│ ├── TodoForm.tsx
│ ├── TodoDetails.module.css
│ └── TodoDetails.tsx
├── react-app-env.d.ts
├── models
│ └── todo.ts
├── index.css
├── App.tsx
├── index.tsx
├── App.css
├── api
│ └── todoApi.ts
└── store
│ └── store-todo.tsx
├── Todo App.png
├── public
├── favicon.ico
├── robots.txt
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── manifest.json
└── index.html
├── .gitignore
├── README.md
├── tsconfig.json
└── package.json
/src/components/TodoList.module.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/Todo App.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quocbao19982009/Todo-App/HEAD/Todo App.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quocbao19982009/Todo-App/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quocbao19982009/Todo-App/HEAD/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quocbao19982009/Todo-App/HEAD/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quocbao19982009/Todo-App/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quocbao19982009/Todo-App/HEAD/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quocbao19982009/Todo-App/HEAD/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/src/models/todo.ts:
--------------------------------------------------------------------------------
1 | interface TodoModel {
2 | text: string;
3 | id: string;
4 | complete: boolean;
5 | createdAt?: string;
6 | code?: string;
7 | }
8 |
9 | export enum filter {
10 | all = "all",
11 | active = "active",
12 | completed = "completed",
13 | }
14 |
15 | export default TodoModel;
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Todo App
2 |
3 |
4 |
5 | ## General Information
6 |
7 | This is a Todo Application that use Firebase API, React with TypeScript. The app can Add, Delete and Edit todo. Todo can be sorted by Complete or Active.
8 |
9 | Live: https://todo-app-16d97.web.app/
10 |
11 | ## Technologies
12 |
13 | - React
14 | - TypeScript
15 | - Fetch API
16 | - Firebase Realtime Data
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext, useEffect } from "react";
2 | import "./App.css";
3 | import TodoForm from "./components/TodoForm";
4 | import TodoList from "./components/TodoList";
5 | import TodoModel from "./models/todo";
6 | import { TodoContext } from "./store/store-todo";
7 |
8 | function App() {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | >
19 | );
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import TodoContextProvider from "./store/store-todo";
6 | ReactDOM.render(
7 |
8 |
9 |
10 |
11 | ,
12 | document.getElementById("root")
13 | );
14 |
15 | // If you want to start measuring performance in your app, pass a function
16 | // to log results (for example: reportWebVitals(console.log))
17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
18 |
--------------------------------------------------------------------------------
/src/components/TodoForm.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding-left: 15px;
3 | padding-right: 15px;
4 | width: 100%;
5 | }
6 |
7 | .form {
8 | margin: 40px 0 10px;
9 | }
10 |
11 | .form_input {
12 | outline: none;
13 | border: 0;
14 | border-bottom: 1px dotted #666;
15 | border-radius: 0;
16 | padding: 0 0 5px 0;
17 | width: 100%;
18 | height: 50px;
19 | font-family: inherit;
20 | font-size: 1.5rem;
21 | font-weight: 300;
22 | color: #fff;
23 | background: transparent;
24 | -webkit-font-smoothing: antialiased;
25 | }
26 |
27 | @media (min-width: 33.75em) {
28 | .form {
29 | margin: 80px 0 20px;
30 | }
31 | .form_input {
32 | height: 61px;
33 | font-size: 2rem;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/TaskFilters.module.css:
--------------------------------------------------------------------------------
1 | .task-filters {
2 | margin-bottom: 45px;
3 | padding-left: 1px;
4 | font-size: 1rem;
5 | line-height: 24px;
6 | list-style-type: none;
7 | }
8 | .task-filters:after {
9 | clear: both;
10 | content: "";
11 | display: table;
12 | }
13 | @media screen and (min-width: 33.75em) {
14 | .task-filters {
15 | margin-bottom: 55px;
16 | }
17 | }
18 | .task-filters li {
19 | float: left;
20 | }
21 | .task-filters li:not(:first-child) {
22 | margin-left: 12px;
23 | }
24 | .task-filters li:not(:first-child):before {
25 | padding-right: 12px;
26 | content: "/";
27 | font-weight: 300;
28 | }
29 | .task-filters a {
30 | color: #999;
31 | text-decoration: none;
32 | }
33 | .task-filters a.active {
34 | color: #fff;
35 | }
36 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | html {
8 | min-height: 100%;
9 | -webkit-text-size-adjust: 100%;
10 | -ms-text-size-adjust: 100%;
11 | -moz-osx-font-smoothing: grayscale;
12 | -webkit-font-smoothing: antialiased;
13 | font-size: 16px;
14 | cursor: default;
15 | background-color: #222;
16 | }
17 |
18 | body {
19 | font-family: "aktiv-grotesk-std", Helvetica Neue, Arial, sans-serif;
20 | font-size: 1.125rem;
21 | line-height: 1.33333;
22 | color: #999;
23 | }
24 |
25 | main {
26 | max-width: 810px;
27 | width: 100%;
28 | margin: 0 auto;
29 | }
30 |
31 | .container {
32 | margin: 0 auto;
33 | width: 100%;
34 | max-width: 810px;
35 | padding-left: 15px;
36 | padding-right: 15px;
37 | }
38 |
39 | footer {
40 | position: fixed;
41 | padding: 10px 10px 0px 10px;
42 | text-align: center;
43 | bottom: 0;
44 | width: 100%;
45 | /* Height of the footer*/
46 | height: 40px;
47 | }
48 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
22 |
23 | Todo App
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.1",
7 | "@testing-library/react": "^12.1.2",
8 | "@testing-library/user-event": "^13.5.0",
9 | "@types/jest": "^27.0.3",
10 | "@types/node": "^16.11.17",
11 | "@types/react": "^17.0.38",
12 | "@types/react-dom": "^17.0.11",
13 | "react": "^17.0.2",
14 | "react-dom": "^17.0.2",
15 | "react-scripts": "5.0.0",
16 | "typescript": "^4.5.4",
17 | "web-vitals": "^2.1.2"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from "react";
2 | import { TodoContext } from "../store/store-todo";
3 | import classes from "./TodoList.module.css";
4 | import TodoDetails from "./TodoDetails";
5 | import TodoModel, { filter } from "../models/todo";
6 |
7 | const TodoList = () => {
8 | const todoCtx = useContext(TodoContext);
9 |
10 | const todoList = todoCtx.todoList;
11 | const getTodo = todoCtx.getTodo;
12 | const filterOrder = todoCtx.filter;
13 |
14 | useEffect(() => {
15 | getTodo();
16 | }, []);
17 |
18 | const allTodos =
19 | filterOrder === filter.all &&
20 | todoList.map((todo) => );
21 |
22 | const completedTodos =
23 | filterOrder === filter.completed &&
24 | todoList
25 | .filter((todo) => todo.complete === true)
26 | .map((todo) => );
27 |
28 | const activeTodos =
29 | filterOrder === filter.active &&
30 | todoList
31 | .filter((todo) => todo.complete === false)
32 | .map((todo) => );
33 |
34 | return (
35 |
36 | {allTodos}
37 |
38 | {completedTodos}
39 |
40 | {activeTodos}
41 |
42 | );
43 | };
44 |
45 | export default TodoList;
46 |
--------------------------------------------------------------------------------
/src/components/TaskFilters.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import classes from "./TaskFilters.module.css";
3 | import { TodoContext } from "../store/store-todo";
4 | import { filter } from "../models/todo";
5 |
6 | const TaskFilters = () => {
7 | const todoCtx = useContext(TodoContext);
8 | const changeFilter = todoCtx.changeFilter;
9 | const filterOrder = todoCtx.filter;
10 | console.log(todoCtx);
11 |
12 | const changeFilterOrder = (filter: filter) => {
13 | changeFilter(filter);
14 | };
15 |
16 | return (
17 |
43 | );
44 | };
45 |
46 | export default TaskFilters;
47 |
--------------------------------------------------------------------------------
/src/components/TodoForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useContext } from "react";
2 | import TodoModel from "../models/todo";
3 | import { TodoContext } from "../store/store-todo";
4 | import classes from "./TodoForm.module.css";
5 | import TaskFilters from "./TaskFilters";
6 |
7 | const TodoForm = () => {
8 | const [loading, setLoading] = useState(false);
9 | // Do i need loading here?
10 |
11 | const inputRef = useRef(null);
12 |
13 | const todoCtx = useContext(TodoContext);
14 | const addTodo = todoCtx.addTodo;
15 |
16 | const submitHandler = (e: React.FormEvent) => {
17 | e.preventDefault();
18 | const newTodo: TodoModel = {
19 | text: inputRef.current!.value,
20 | id: new Date().getTime().toString(),
21 | complete: false,
22 | };
23 |
24 | if (newTodo.text.trim() === "") {
25 | return;
26 | }
27 | addTodo(newTodo);
28 |
29 | inputRef.current!.value = "";
30 | };
31 | return (
32 |
33 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default TodoForm;
49 |
--------------------------------------------------------------------------------
/src/components/TodoDetails.module.css:
--------------------------------------------------------------------------------
1 | .todo_item {
2 | display: flex;
3 | outline: none;
4 | border-bottom: 1px dotted #666;
5 | height: 60px;
6 | overflow: hidden;
7 | color: #fff;
8 | font-size: 1.125rem;
9 | font-weight: 300;
10 | }
11 |
12 | @media (min-width: 33.75rem) {
13 | .todo_item {
14 | font-size: 1.5rem;
15 | }
16 | }
17 |
18 | .cell:first-child,
19 | .cell:last-child {
20 | display: flex;
21 | flex: 0 0 auto;
22 | align-items: center;
23 | }
24 |
25 | .cell:first-child {
26 | padding-right: 20px;
27 | }
28 | .cell:last-child {
29 | gap: 8px;
30 | }
31 |
32 | .cell:nth-child(2) {
33 | display: flex;
34 | flex: 1 1 0%;
35 | padding-right: 30px;
36 | overflow: hidden;
37 | align-items: center;
38 | }
39 |
40 | .title {
41 | max-width: 100%;
42 | line-height: 60px;
43 | overflow: hidden;
44 | outline: none;
45 | text-overflow: ellipsis;
46 | white-space: nowrap;
47 | }
48 |
49 | .icon {
50 | display: inline-block;
51 | text-align: center;
52 | text-decoration: none;
53 | outline: none;
54 | border: 0;
55 | padding: 0;
56 | border-radius: 40px;
57 | padding: 8px;
58 | width: 40px;
59 | height: 40px;
60 | font-size: 1.35rem;
61 | background-color: #2a2a2a;
62 | color: #555;
63 | padding: 8px;
64 | }
65 |
66 | .icon:hover {
67 | color: #999;
68 | cursor: pointer;
69 | }
70 |
71 | .todo-item_completed .title {
72 | color: #666;
73 | text-decoration: line-through #85bf6b;
74 | }
75 |
76 | .todo-item_completed .checkIcon {
77 | color: #85bf6b;
78 | }
79 |
80 | .hide {
81 | display: none !important;
82 | }
83 |
84 | form {
85 | width: 100%;
86 | }
87 |
88 | .input {
89 | outline: none;
90 | border: 0;
91 | padding: 0;
92 | width: 100%;
93 | height: 60px;
94 | color: inherit;
95 | font: inherit;
96 | background: transparent;
97 | }
98 |
--------------------------------------------------------------------------------
/src/components/TodoDetails.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import TodoModel from "../models/todo";
3 | import { TodoContext } from "../store/store-todo";
4 | import classes from "./TodoDetails.module.css";
5 |
6 | interface TodoDetailsProps {
7 | todo: TodoModel;
8 | }
9 | const TodoDetails = ({ todo }: TodoDetailsProps) => {
10 | const [todoText, setTodoText] = useState(todo.text);
11 | const [editing, setEditing] = useState(false);
12 |
13 | const todoCtx = useContext(TodoContext);
14 | const removeTodo = todoCtx.removeTodo;
15 | const checkTodo = todoCtx.checkTodo;
16 | const updateTodo = todoCtx.updateTodo;
17 |
18 | const removeTodoHanlder = () => {
19 | removeTodo(todo.code!);
20 | };
21 |
22 | const checkTodoHandler = () => {
23 | checkTodo(todo.code!);
24 | };
25 |
26 | const saveEditTodoHandler = () => {
27 | updateTodo(todo.code!, todoText);
28 | setEditing(false);
29 | };
30 |
31 | const onEnterPressHandler = (event: React.KeyboardEvent) => {
32 | if (event.key === "Enter") {
33 | saveEditTodoHandler();
34 | setEditing(false);
35 | return;
36 | }
37 | };
38 | const todo_completed = todo.complete ? classes["todo-item_completed"] : "";
39 |
40 | const todo_editing = editing ? classes["todo-item_editing"] : "";
41 |
42 | const hide = editing ? classes.hide : "";
43 |
44 | return (
45 |
46 |
47 |
53 |
54 |
55 | {!editing &&
{todoText}
}
56 | {editing && (
57 |
setTodoText(e.target.value)}
63 | >
64 | )}
65 |
66 |
67 |
73 |
79 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default TodoDetails;
91 |
92 | // Adding edit function, need to figure out a way to turn of Editng and Change todo TExt in the same time
93 |
--------------------------------------------------------------------------------
/src/api/todoApi.ts:
--------------------------------------------------------------------------------
1 | import TodoModel from "../models/todo";
2 |
3 | export const addTodosAPI = async (todo: TodoModel) => {
4 | try {
5 | const response = await fetch(
6 | "https://todo-app-16d97-default-rtdb.europe-west1.firebasedatabase.app/todoList.json",
7 | {
8 | method: "POST",
9 | body: JSON.stringify({ ...todo }),
10 | headers: {
11 | "Content-Type": "application/json",
12 | },
13 | }
14 | );
15 |
16 | if (!response.ok) {
17 | throw new Error("Sending Todo Fail");
18 | }
19 |
20 | const data = await response.json();
21 | return data;
22 | } catch (e) {
23 | throw new Error("Sending Todo Fail");
24 | }
25 | };
26 |
27 | export const removeTodoAPI = async (code: string) => {
28 | try {
29 | const response = await fetch(
30 | `https://todo-app-16d97-default-rtdb.europe-west1.firebasedatabase.app/todoList/${code}.json`,
31 | {
32 | method: "DELETE",
33 | }
34 | );
35 | if (!response.ok) {
36 | throw new Error("Delete Todo Fail");
37 | }
38 | } catch (error) {
39 | throw new Error("Cannot Delete Todos");
40 | }
41 | };
42 |
43 | export const editTodoAPI = async (code: string, updateText: string) => {
44 | try {
45 | const response = await fetch(
46 | `https://todo-app-16d97-default-rtdb.europe-west1.firebasedatabase.app/todoList/${code}.json`,
47 | {
48 | method: "PATCH",
49 | body: JSON.stringify({ text: updateText }),
50 | }
51 | );
52 |
53 | if (!response.ok) {
54 | throw new Error("Updating Todo Fail");
55 | }
56 | const data = await response.json();
57 |
58 | console.log(response);
59 | } catch (error) {
60 | throw new Error("Updating Todo Fail");
61 | }
62 | };
63 |
64 | export const checkTodoAPI = async (code: string, updateComplete: boolean) => {
65 | try {
66 | const response = await fetch(
67 | `https://todo-app-16d97-default-rtdb.europe-west1.firebasedatabase.app/todoList/${code}.json`,
68 | {
69 | method: "PATCH",
70 | body: JSON.stringify({ complete: updateComplete }),
71 | }
72 | );
73 |
74 | if (!response.ok) {
75 | throw new Error("Updating Todo Fail");
76 | }
77 | } catch (error) {
78 | throw new Error("Updating Todo Fail");
79 | }
80 | };
81 |
82 | export const getTodosAPI = async () => {
83 | try {
84 | const response = await fetch(
85 | "https://todo-app-16d97-default-rtdb.europe-west1.firebasedatabase.app/todoList.json"
86 | );
87 |
88 | if (!response.ok) {
89 | throw new Error("Cannot get Todos, please check source");
90 | }
91 |
92 | const data = await response.json();
93 |
94 | const loadedTodos: TodoModel[] = [];
95 |
96 | for (const key in data) {
97 | loadedTodos.push({
98 | code: key,
99 | id: data[key].id,
100 | text: data[key].text,
101 | createdAt: data[key].createdAt,
102 | complete: data[key].complete,
103 | });
104 | }
105 |
106 | return loadedTodos;
107 | } catch (error) {
108 | throw new Error("Cannot get Todos");
109 | }
110 | };
111 |
--------------------------------------------------------------------------------
/src/store/store-todo.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, createContext } from "react";
2 | import TodoList from "../components/TodoList";
3 | import {
4 | getTodosAPI,
5 | addTodosAPI,
6 | removeTodoAPI,
7 | editTodoAPI,
8 | checkTodoAPI,
9 | } from "../api/todoApi";
10 |
11 | import TodoModel, { filter } from "../models/todo";
12 |
13 | interface TodoContextInterface {
14 | todoList: TodoModel[];
15 | filter: filter;
16 | changeFilter: (filterOrder: filter) => void;
17 | getTodo: () => void;
18 | addTodo: (todo: TodoModel) => void;
19 | removeTodo: (id: string) => void;
20 | checkTodo: (id: string) => void;
21 | updateTodo: (id: string, textInput: string) => void;
22 | }
23 |
24 | export const TodoContext = createContext({
25 | todoList: [],
26 | filter: filter.all,
27 | changeFilter: (filterOrder: filter) => {},
28 | getTodo: () => {},
29 | addTodo: (todo: TodoModel) => {},
30 | removeTodo: (id: string) => {},
31 | checkTodo: (id: string) => {},
32 | updateTodo: (id: string, textInput: string) => {},
33 | });
34 |
35 | const TodoContextProvider: React.FC = (props) => {
36 | const [todos, setTodos] = useState([]);
37 | const [filterOrder, setFilterOrder] = useState(filter.all);
38 |
39 | const changeFilterHandler = (filterOrder: filter) => {
40 | setFilterOrder(filterOrder);
41 | };
42 |
43 | const getTodoHandler = async () => {
44 | const loadedTodos = await getTodosAPI();
45 | setTodos(loadedTodos);
46 | };
47 |
48 | const addTodoHandler = async (todo: TodoModel) => {
49 | let code;
50 | code = await addTodosAPI({ ...todo, createdAt: new Date().toISOString() });
51 | const newTodo: TodoModel = {
52 | code: code.name,
53 | ...todo,
54 | createdAt: new Date().toISOString(),
55 | };
56 | setTodos((prevTodos) => {
57 | return prevTodos.concat(newTodo);
58 | });
59 | };
60 |
61 | const removeTodoHanlder = async (code: string) => {
62 | await removeTodoAPI(code);
63 | setTodos((prevTodos) => {
64 | return prevTodos.filter((todo) => todo.code !== code);
65 | });
66 | };
67 |
68 | const checkTodoHandler = async (code: string) => {
69 | const targetTodoIndex = todos.findIndex((todo) => todo.code === code);
70 | const targetTodo = todos[targetTodoIndex];
71 | const updateTodo = { ...targetTodo, complete: !targetTodo.complete };
72 | let updateTodos = [...todos];
73 | updateTodos[targetTodoIndex] = updateTodo;
74 | setTodos(updateTodos);
75 | await checkTodoAPI(code, !targetTodo.complete);
76 | };
77 |
78 | const updatingTodoHandler = async (code: string, textInput: string) => {
79 | const targetTodoIndex = todos.findIndex((todo) => todo.code === code);
80 | const targetTodo = todos[targetTodoIndex];
81 | const updateTodo: TodoModel = { ...targetTodo, text: textInput };
82 | let updateTodos = [...todos];
83 | updateTodos[targetTodoIndex] = updateTodo;
84 | setTodos(updateTodos);
85 | await editTodoAPI(code, textInput);
86 | };
87 |
88 | const todoContextValue: TodoContextInterface = {
89 | todoList: todos,
90 | filter: filterOrder,
91 | changeFilter: changeFilterHandler,
92 | getTodo: getTodoHandler,
93 | addTodo: addTodoHandler,
94 | removeTodo: removeTodoHanlder,
95 | checkTodo: checkTodoHandler,
96 | updateTodo: updatingTodoHandler,
97 | };
98 |
99 | return (
100 |
101 | {props.children}
102 |
103 | );
104 | };
105 |
106 | export default TodoContextProvider;
107 |
--------------------------------------------------------------------------------