├── 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 | Todo App 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 |
34 | 42 |
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 | --------------------------------------------------------------------------------