From 5758a207d54bd946dcb8924c996b99788bc0a5bd Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 10:33:14 +0800 Subject: [PATCH 01/16] fix: Update tailwind config to use color --- src/routes/Login.tsx | 2 +- src/routes/Signup.tsx | 2 +- tailwind.config.js | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/routes/Login.tsx b/src/routes/Login.tsx index b7b0b60..5d424ad 100644 --- a/src/routes/Login.tsx +++ b/src/routes/Login.tsx @@ -106,7 +106,7 @@ function Login(): React.ReactElement { - +
+
+
+
+ +
+ + +
+ +
+ + + +
+ +
+
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ +
+
    +
  • +
    + + + + + +
    +
  • +
+
+ + +
+
); } diff --git a/tailwind.config.js b/tailwind.config.js index 1cc8d86..9bb7927 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -12,6 +12,7 @@ module.exports = { }, backgroundImage: { logo: "url('/src/images/logo.svg')", + linear: 'linear-gradient(173deg, #FFD370 5.12%, #FFD370 53.33%, #FFD370 53.44%, #FFF 53.45%, #FFF 94.32%)', }, }, }, From 714de82b4425fa174e39c528248c1128b08ffe0b Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 11:50:53 +0800 Subject: [PATCH 04/16] feat: Add logout api --- src/helpers/api.tsx | 29 +++++++++++++++++++++-- src/routes/Login.tsx | 2 ++ src/routes/PublicLayout.tsx | 9 ++++---- src/routes/Todo.tsx | 46 +++++++++++++++++++++++++++++++++++-- 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/helpers/api.tsx b/src/helpers/api.tsx index 44f8476..c07e32b 100644 --- a/src/helpers/api.tsx +++ b/src/helpers/api.tsx @@ -51,17 +51,42 @@ const login = async (data: object): Promise => { const check = async (cookieValue: string): Promise => { try { - req.defaults.headers.common['Authorization'] = cookieValue; + // if (cookieValue) { + // req.defaults.headers.common['Authorization'] = cookieValue; + // } const res: AxiosResponse = await req.get('/users/checkout'); if (res?.status !== 200) { throw new Error(); } + return res?.data; + } catch (error: any) { + if (error?.status === 400) { + return; + } + + handleError(error); + } +}; + +const logout = async (): Promise => { + try { + const res: AxiosResponse = await req.post('/users/sign_out'); + if (res?.status !== 200) { + throw new Error(); + } + + return res?.data; + } catch (error: unknown) { + handleError(error); + } +}; + return res?.data; } catch (error: unknown) { handleError(error); } }; -export const api = { signup, login, check }; +export const api = { req, signup, login, check, logout }; diff --git a/src/routes/Login.tsx b/src/routes/Login.tsx index 5d424ad..206af55 100644 --- a/src/routes/Login.tsx +++ b/src/routes/Login.tsx @@ -45,6 +45,8 @@ function Login(): React.ReactElement { // end of !res?.status if (res?.status) { + api.req.defaults.headers.common['Authorization'] = res?.token; + Toast.fire({ icon: 'success', title: '登入成功', diff --git a/src/routes/PublicLayout.tsx b/src/routes/PublicLayout.tsx index f9c543d..ebb6acc 100644 --- a/src/routes/PublicLayout.tsx +++ b/src/routes/PublicLayout.tsx @@ -18,10 +18,11 @@ function PublicLayout(): React.ReactElement { if (token) { api.check(token).then((res: any) => { if (!res?.status) { - Toast.fire({ - icon: 'warning', - title: '請重新操作', - }); + return; + // Toast.fire({ + // icon: 'warning', + // title: '請重新操作', + // }); } // end of !res?.status diff --git a/src/routes/Todo.tsx b/src/routes/Todo.tsx index aed1494..0f68e82 100644 --- a/src/routes/Todo.tsx +++ b/src/routes/Todo.tsx @@ -1,9 +1,51 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; + +import { useAuth } from '../contexts/AuthContext'; +import { api } from '../helpers/api'; +import { Toast } from '../components/Toast'; import Logo from '../components/Logo'; function Todo(): React.ReactElement { - // const [count, setCount] = useState(0); + const navigate = useNavigate(); + const { token } = useAuth(); + const handleLogout = () => { + api.logout().then((res: any) => { + if (!res?.status) { + Toast.fire({ + icon: 'warning', + title: '請重新操作', + }); + } + // end of !res?.status + + if (res?.status) { + const msg = res.message || '登出成功'; + + api.req.defaults.headers.common['Authorization'] = ''; + + Toast.fire({ + icon: 'success', + title: msg, + didClose: () => { + setTimeout(() => { + navigate('/login'); + }, 400); + }, + }); + } + // end of res?.status + }); + // end of api + }; + // end of handleLogout + useEffect(() => { + if (token) { + api.req.defaults.headers.common['Authorization'] = token; + } + }, []); + return (
From 9faf53a0448b46ba99e0b34b8e7550d120a8acb2 Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 11:51:26 +0800 Subject: [PATCH 05/16] feat: Add useInput hook --- src/hooks/useInput.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/hooks/useInput.tsx diff --git a/src/hooks/useInput.tsx b/src/hooks/useInput.tsx new file mode 100644 index 0000000..5cdac3a --- /dev/null +++ b/src/hooks/useInput.tsx @@ -0,0 +1,12 @@ +import { useState } from 'react'; + +export const useInput = (initValue: string) => { + const [value, setValue] = useState(initValue); + + const handleChange = (event: any) => { + setValue(event.target.value); + }; + + return { value, onChange: handleChange, clear: () => setValue('') }; +}; +// end of useInput() From c4217256c35815c4d0040cad3a1fb217e84880fb Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 12:03:14 +0800 Subject: [PATCH 06/16] feat: Add getTodo and postTodo api --- src/helpers/api.tsx | 22 +++++- src/index.css | 2 +- src/routes/Todo.tsx | 178 +++++++++++++++++++++++++++++++------------- 3 files changed, 148 insertions(+), 54 deletions(-) diff --git a/src/helpers/api.tsx b/src/helpers/api.tsx index c07e32b..7fcfd7a 100644 --- a/src/helpers/api.tsx +++ b/src/helpers/api.tsx @@ -83,10 +83,30 @@ const logout = async (): Promise => { } }; +const getTodo = async (): Promise => { + try { + const res: AxiosResponse = await req.get('/todos'); + if (res?.status !== 200) { + throw new Error(); + } + + return res?.data; + } catch (error: unknown) { + handleError(error); + } +}; + +const postTodo = async (data: object): Promise => { + try { + const res: AxiosResponse = await req.post('/todos', data); + if (res?.status !== 201) { + throw new Error(); + } + return res?.data; } catch (error: unknown) { handleError(error); } }; -export const api = { req, signup, login, check, logout }; +export const api = { req, signup, login, check, logout, getTodo, postTodo }; diff --git a/src/index.css b/src/index.css index 3e9330b..d60e2ac 100644 --- a/src/index.css +++ b/src/index.css @@ -3,7 +3,7 @@ @tailwind utilities; * { - outline: 1px solid #f73; + /* outline: 1px solid #f73; */ } input:checked+div { diff --git a/src/routes/Todo.tsx b/src/routes/Todo.tsx index 0f68e82..54272b9 100644 --- a/src/routes/Todo.tsx +++ b/src/routes/Todo.tsx @@ -2,14 +2,26 @@ import React, { useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; +import { useInput } from '../hooks/useInput'; import { api } from '../helpers/api'; import { Toast } from '../components/Toast'; import Logo from '../components/Logo'; +interface ITodoData { + content: string; + createTime: number; + id: string; + status: boolean; +} + function Todo(): React.ReactElement { const navigate = useNavigate(); const { token } = useAuth(); + + const [todoData, setTodoData] = useState([]); + const newInputTodo = useInput(''); + const handleLogout = () => { api.logout().then((res: any) => { if (!res?.status) { @@ -40,12 +52,58 @@ function Todo(): React.ReactElement { // end of api }; // end of handleLogout + + const getTodos = () => { + api.getTodo().then((res: any) => { + if (res?.status) { + setTodoData([...res?.data]); + } + }); + // end of api + }; + // end of getTodos() + + const handleAddTodo = () => { + const data = { + content: newInputTodo.value.trim(), + }; + api.postTodo(data).then((res) => { + if (!res?.status) { + Toast.fire({ + icon: 'warning', + title: '請重新操作', + }); + } + // end of !res?.status + + if (res?.status) { + const msg = res.message || '新增成功'; + + Toast.fire({ + icon: 'success', + title: msg, + }); + + newInputTodo.clear(); + getTodos(); + } + // end of res?.status + }); + // end of api + }; + // end of handleAddTodo + useEffect(() => { if (token) { api.req.defaults.headers.common['Authorization'] = token; } }, []); + useEffect(() => { + if (!todoData.length) { + getTodos(); + } + }, []); return (
@@ -55,7 +113,7 @@ function Todo(): React.ReactElement {
- @@ -66,13 +124,24 @@ function Todo(): React.ReactElement { name="content" id="inputAddToto" placeholder="新增待辦事項" - className="w-[83%] font-medium text-dark placeholder:text-light" + className="w-[83%] font-medium text-dark outline-none placeholder:text-light" + value={newInputTodo.value} + onChange={newInputTodo.onChange} + onKeyDown={(e) => { + if (e.key === 'Enter') { + return handleAddTodo(); + } + if (e.key === 'Escape') { + return newInputTodo.clear(); + } + }} /> -
- + +
+ + + + + + + + + + +
+ {/* end of custom checkbox */} + + + + + + + + ); + })} From d57707e44e733edc38287c27c30e765fbfb7931e Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 12:12:01 +0800 Subject: [PATCH 07/16] feat: Add delete api --- src/helpers/api.tsx | 24 +++++++++++++++++++++++- src/routes/Todo.tsx | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/helpers/api.tsx b/src/helpers/api.tsx index 7fcfd7a..c2e2d94 100644 --- a/src/helpers/api.tsx +++ b/src/helpers/api.tsx @@ -109,4 +109,26 @@ const postTodo = async (data: object): Promise => { } }; -export const api = { req, signup, login, check, logout, getTodo, postTodo }; +const deleteTodo = async (todoId: string): Promise => { + try { + const res: AxiosResponse = await req.delete(`/todos/${todoId}`); + if (res?.status !== 200) { + throw new Error(); + } + + return res?.data; + } catch (error: unknown) { + handleError(error); + } +}; + +export const api = { + req, + signup, + login, + check, + logout, + getTodo, + postTodo, + deleteTodo, +}; diff --git a/src/routes/Todo.tsx b/src/routes/Todo.tsx index 54272b9..23c45de 100644 --- a/src/routes/Todo.tsx +++ b/src/routes/Todo.tsx @@ -93,6 +93,32 @@ function Todo(): React.ReactElement { }; // end of handleAddTodo + const handleRemoveTodo = (todo: ITodoData) => { + api.deleteTodo(todo.id).then((res) => { + if (!res?.status) { + Toast.fire({ + icon: 'warning', + title: '請重新操作', + }); + } + // end of !res?.status + + if (res?.status) { + const msg = res.message || '新增成功'; + + Toast.fire({ + icon: 'success', + title: msg, + }); + + getTodos(); + } + // end of res?.status + }); + // end of api + }; + // end of handleRemoveTodo + useEffect(() => { if (token) { api.req.defaults.headers.common['Authorization'] = token; @@ -230,7 +256,13 @@ function Todo(): React.ReactElement { value={todo.content} /> - + ); From 7889c3432e6c7d65ac4e9b43e6c8930eb2094588 Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 13:07:07 +0800 Subject: [PATCH 08/16] feat: Add patch and put api --- src/helpers/api.tsx | 28 +++++++++++ src/hooks/useInput.tsx | 2 +- src/index.css | 4 +- src/routes/Todo.tsx | 111 ++++++++++++++++++++++++++++++++++++----- 4 files changed, 130 insertions(+), 15 deletions(-) diff --git a/src/helpers/api.tsx b/src/helpers/api.tsx index c2e2d94..23fc511 100644 --- a/src/helpers/api.tsx +++ b/src/helpers/api.tsx @@ -122,6 +122,32 @@ const deleteTodo = async (todoId: string): Promise => { } }; +const patchTodo = async (todoId: string): Promise => { + try { + const res: AxiosResponse = await req.patch(`/todos/${todoId}/toggle`); + if (res?.status !== 200) { + throw new Error(); + } + + return res?.data; + } catch (error: unknown) { + handleError(error); + } +}; + +const putTodo = async (todoId: string, data: object): Promise => { + try { + const res: AxiosResponse = await req.put(`/todos/${todoId}`, data); + if (res?.status !== 200) { + throw new Error(); + } + + return res?.data; + } catch (error: unknown) { + handleError(error); + } +}; + export const api = { req, signup, @@ -131,4 +157,6 @@ export const api = { getTodo, postTodo, deleteTodo, + patchTodo, + putTodo, }; diff --git a/src/hooks/useInput.tsx b/src/hooks/useInput.tsx index 5cdac3a..3eff953 100644 --- a/src/hooks/useInput.tsx +++ b/src/hooks/useInput.tsx @@ -7,6 +7,6 @@ export const useInput = (initValue: string) => { setValue(event.target.value); }; - return { value, onChange: handleChange, clear: () => setValue('') }; + return { value, setValue, onChange: handleChange, clear: () => setValue('') }; }; // end of useInput() diff --git a/src/index.css b/src/index.css index d60e2ac..2850632 100644 --- a/src/index.css +++ b/src/index.css @@ -6,10 +6,10 @@ /* outline: 1px solid #f73; */ } -input:checked+div { +input:checked+.custom-check { @apply border-0; } -input:checked+div svg { +input:checked+.custom-check svg { @apply block; } \ No newline at end of file diff --git a/src/routes/Todo.tsx b/src/routes/Todo.tsx index 23c45de..369bfec 100644 --- a/src/routes/Todo.tsx +++ b/src/routes/Todo.tsx @@ -21,6 +21,9 @@ function Todo(): React.ReactElement { const [todoData, setTodoData] = useState([]); const newInputTodo = useInput(''); + const editInputTodo = useInput(''); + + const [isEditId, setIsEditId] = useState(''); const handleLogout = () => { api.logout().then((res: any) => { @@ -104,7 +107,7 @@ function Todo(): React.ReactElement { // end of !res?.status if (res?.status) { - const msg = res.message || '新增成功'; + const msg = res.message || '刪除成功'; Toast.fire({ icon: 'success', @@ -119,6 +122,63 @@ function Todo(): React.ReactElement { }; // end of handleRemoveTodo + const handleToggleTodo = (todo: ITodoData) => { + api.patchTodo(todo.id).then((res) => { + if (!res?.status) { + Toast.fire({ + icon: 'warning', + title: '請重新操作', + }); + } + // end of !res?.status + + if (res?.status) { + const msg = res.message || '編輯成功'; + + Toast.fire({ + icon: 'success', + title: msg, + }); + + getTodos(); + } + // end of res?.status + }); + // end of api + }; + // end of handleToggleTodo + + const handleEditTodo = () => { + const data = { + content: editInputTodo.value, + }; + + api.putTodo(isEditId, data).then((res) => { + if (!res?.status) { + Toast.fire({ + icon: 'warning', + title: '請重新操作', + }); + } + // end of !res?.status + + if (res?.status) { + const msg = res.message || '編輯成功'; + + Toast.fire({ + icon: 'success', + title: msg, + }); + + getTodos(); + } + // end of res?.status + + setIsEditId(''); + }); + // end of api + }; + useEffect(() => { if (token) { api.req.defaults.headers.common['Authorization'] = token; @@ -218,11 +278,13 @@ function Todo(): React.ReactElement { name="checkbox" id="todoStatus" aria-label="STATUS" - value="yes" - className="absolute h-5 w-5 opacity-0" + className="absolute h-5 w-5 cursor-pointer opacity-0" + value={`${todo.status}`} + checked={todo.status} + onChange={() => handleToggleTodo(todo)} /> -
+
- + {isEditId === todo.id ? ( + + {todo.content} + + + )}
  • -
  • -
  • -
    +
      - {todoData && - todoData.map((todo) => { + {filterData && + filterData.map((todo) => { return (
    • From 11f3773057a446eb02ef1d15ebfe34527c08dd16 Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 14:19:43 +0800 Subject: [PATCH 10/16] feat: Add clear done's api --- src/components/Toast.tsx | 2 +- src/helpers/api.tsx | 5 ++- src/routes/PublicLayout.tsx | 2 +- src/routes/Todo.tsx | 62 +++++++++++++++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index a89a8a6..5e15af2 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -4,7 +4,7 @@ export const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, - timer: 3000, + timer: 1500, timerProgressBar: true, didOpen: (toast) => { toast.addEventListener('mouseenter', Swal.stopTimer); diff --git a/src/helpers/api.tsx b/src/helpers/api.tsx index 23fc511..815df35 100644 --- a/src/helpers/api.tsx +++ b/src/helpers/api.tsx @@ -51,11 +51,10 @@ const login = async (data: object): Promise => { const check = async (cookieValue: string): Promise => { try { - // if (cookieValue) { - // req.defaults.headers.common['Authorization'] = cookieValue; - // } + req.defaults.headers.common['Authorization'] = cookieValue; const res: AxiosResponse = await req.get('/users/checkout'); + if (res?.status !== 200) { throw new Error(); } diff --git a/src/routes/PublicLayout.tsx b/src/routes/PublicLayout.tsx index ebb6acc..968df20 100644 --- a/src/routes/PublicLayout.tsx +++ b/src/routes/PublicLayout.tsx @@ -33,7 +33,7 @@ function PublicLayout(): React.ReactElement { didClose: () => { setTimeout(() => { navigate('/todo'); - }, 400); + }, 100); }, }); } diff --git a/src/routes/Todo.tsx b/src/routes/Todo.tsx index 8f84cd6..e9913c3 100644 --- a/src/routes/Todo.tsx +++ b/src/routes/Todo.tsx @@ -26,6 +26,8 @@ function Todo(): React.ReactElement { const [isEditId, setIsEditId] = useState(''); const [isClickTab, setIsClickTab] = useState('ALL'); + const [haveTodoLength, setHaveTodoLength] = useState(0); + const [haveClearLength, setHaveClearLength] = useState(0); const handleLogout = () => { api.logout().then((res: any) => { @@ -184,6 +186,42 @@ function Todo(): React.ReactElement { }; // end of handleEditTodo + const handleClearDoneTodo = () => { + const doneIdList = [...todoData] + .filter((item) => { + return item.status; + }) + .map((todo) => { + return todo.id; + }); + + // console.log(doneIdList); + + const deletePromises = doneIdList.map((id) => api.deleteTodo(id)); + + Promise.all(deletePromises) + .then((results) => { + results.forEach((result) => { + if (!result?.status) { + throw new Error(); + } + const msg = result.message || '編輯成功'; + + Toast.fire({ + icon: 'success', + title: msg, + }); + }); + }) + .catch((error) => { + console.error('Error:::', error); + }) + .finally(() => { + getTodos(); + }); + }; + // end of handleClearDone + useEffect(() => { if (token) { api.req.defaults.headers.common['Authorization'] = token; @@ -212,6 +250,18 @@ function Todo(): React.ReactElement { }) .reverse(), ); + + setHaveTodoLength( + [...todoData].filter((item) => { + return !item.status; + }).length, + ); + + setHaveClearLength( + [...todoData].filter((item) => { + return item.status; + }).length, + ); }, [isClickTab, todoData]); return ( @@ -402,12 +452,18 @@ function Todo(): React.ReactElement {
      - -
      From e961f28d571275fb8e48c9a8725918c202875c69 Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 14:35:55 +0800 Subject: [PATCH 11/16] refactor: Update addInput to file --- src/components/TodoAddInput.tsx | 56 +++++++++++++++++++++++++++++++++ src/routes/Todo.tsx | 48 +++++----------------------- 2 files changed, 64 insertions(+), 40 deletions(-) create mode 100644 src/components/TodoAddInput.tsx diff --git a/src/components/TodoAddInput.tsx b/src/components/TodoAddInput.tsx new file mode 100644 index 0000000..55bc81a --- /dev/null +++ b/src/components/TodoAddInput.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import { useInput } from '../hooks/useInput'; + +interface ITodoAddInputProps { + handleAddTodo: (newInput: string) => void; +} +// end of interface + +function TodoAddInput(props: ITodoAddInputProps): React.ReactElement { + const newInputTodo = useInput(''); + const { handleAddTodo } = props; + + return ( +
      + { + if (e.key === 'Enter') { + handleAddTodo(newInputTodo.value.trim()); + return newInputTodo.clear(); + } + if (e.key === 'Escape') { + return newInputTodo.clear(); + } + }} + /> + + +
      + ); +} +// end of TodoAddInput + +export default TodoAddInput; diff --git a/src/routes/Todo.tsx b/src/routes/Todo.tsx index e9913c3..ae1181a 100644 --- a/src/routes/Todo.tsx +++ b/src/routes/Todo.tsx @@ -6,6 +6,8 @@ import { useInput } from '../hooks/useInput'; import { api } from '../helpers/api'; import { Toast } from '../components/Toast'; +import TodoAddInput from '../components/TodoAddInput'; + import Logo from '../components/Logo'; interface ITodoData { @@ -21,7 +23,7 @@ function Todo(): React.ReactElement { const [todoData, setTodoData] = useState([]); const [filterData, setFilterData] = useState([]); - const newInputTodo = useInput(''); + // const newInputTodo = useInput(''); const editInputTodo = useInput(''); const [isEditId, setIsEditId] = useState(''); @@ -70,9 +72,10 @@ function Todo(): React.ReactElement { }; // end of getTodos() - const handleAddTodo = () => { + const handleAddTodo = (newInput: string) => { const data = { - content: newInputTodo.value.trim(), + content: newInput, + // content: newInputTodo.value.trim(), }; api.postTodo(data).then((res) => { @@ -92,7 +95,7 @@ function Todo(): React.ReactElement { title: msg, }); - newInputTodo.clear(); + // newInputTodo.clear(); getTodos(); setIsClickTab('TODO'); } @@ -277,42 +280,7 @@ function Todo(): React.ReactElement { -
      - { - if (e.key === 'Enter') { - return handleAddTodo(); - } - if (e.key === 'Escape') { - return newInputTodo.clear(); - } - }} - /> - - -
      +
      From 346cc473c2ee0bdefb458888889f8b79ff125d1a Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 14:47:08 +0800 Subject: [PATCH 12/16] refactor: Update tabs to file --- src/components/TodoTabs.tsx | 61 +++++++++++++++++++++++++++++++++++++ src/routes/Todo.tsx | 45 ++------------------------- 2 files changed, 63 insertions(+), 43 deletions(-) create mode 100644 src/components/TodoTabs.tsx diff --git a/src/components/TodoTabs.tsx b/src/components/TodoTabs.tsx new file mode 100644 index 0000000..568e873 --- /dev/null +++ b/src/components/TodoTabs.tsx @@ -0,0 +1,61 @@ +import React from 'react'; + +interface ITodoTabsProps { + isClickTab: string; + setIsClickTab: (isClickTab: string) => void; +} +// end of interface + +function TodoTabs(props: ITodoTabsProps): React.ReactElement { + const { isClickTab, setIsClickTab } = props; + + return ( +
      +
        +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      +
      + ); +} + +// end of TodoTabs + +export default TodoTabs; diff --git a/src/routes/Todo.tsx b/src/routes/Todo.tsx index ae1181a..38a8812 100644 --- a/src/routes/Todo.tsx +++ b/src/routes/Todo.tsx @@ -7,6 +7,7 @@ import { api } from '../helpers/api'; import { Toast } from '../components/Toast'; import TodoAddInput from '../components/TodoAddInput'; +import TodoTabs from '../components/TodoTabs'; import Logo from '../components/Logo'; @@ -283,49 +284,7 @@ function Todo(): React.ReactElement {
      -
      -
        -
      • - -
      • -
      • - -
      • -
      • - -
      • -
      -
      +
        From 58df5000fd749aa670129c402946380d6587f1e5 Mon Sep 17 00:00:00 2001 From: Aya <90342033+Aya-X@users.noreply.github.com> Date: Sun, 10 Sep 2023 15:14:11 +0800 Subject: [PATCH 13/16] refactor: Update TodoCardBody to file --- src/components/TodoCardBody.tsx | 230 ++++++++++++++++++++++++++++++++ src/routes/Todo.tsx | 209 ++--------------------------- 2 files changed, 240 insertions(+), 199 deletions(-) create mode 100644 src/components/TodoCardBody.tsx diff --git a/src/components/TodoCardBody.tsx b/src/components/TodoCardBody.tsx new file mode 100644 index 0000000..d57ca3d --- /dev/null +++ b/src/components/TodoCardBody.tsx @@ -0,0 +1,230 @@ +import React, { useEffect, useState } from 'react'; + +import { api } from '../helpers/api'; +import { Toast } from './Toast'; + +import { useInput } from '../hooks/useInput'; + +interface ITodoData { + content: string; + createTime: number; + id: string; + status: boolean; +} + +interface ITodoCardBodyProps { + todoData: ITodoData[]; + getTodos: () => void; + isClickTab: string; + setIsClickTab: (isClickTab: string) => void; +} +// end of interface + +function TodoCardBody(props: ITodoCardBodyProps): React.ReactElement { + const { todoData, getTodos, isClickTab, setIsClickTab } = props; + + const [filterData, setFilterData] = useState([]); + + const [isEditId, setIsEditId] = useState(''); + const editInputTodo = useInput(''); + + const handleRemoveTodo = (todo: ITodoData) => { + api.deleteTodo(todo.id).then((res) => { + if (!res?.status) { + Toast.fire({ + icon: 'warning', + title: '請重新操作', + }); + } + // end of !res?.status + + if (res?.status) { + const msg = res.message || '刪除成功'; + + Toast.fire({ + icon: 'success', + title: msg, + }); + + getTodos(); + } + // end of res?.status + }); + // end of api + }; + // end of handleRemoveTodo + + const handleToggleTodo = (todo: ITodoData) => { + api.patchTodo(todo.id).then((res) => { + if (!res?.status) { + Toast.fire({ + icon: 'warning', + title: '請重新操作', + }); + } + // end of !res?.status + + if (res?.status) { + const msg = res.message || '編輯成功'; + + Toast.fire({ + icon: 'success', + title: msg, + }); + + getTodos(); + } + // end of res?.status + }); + // end of api + }; + // end of handleToggleTodo + + const handleEditTodo = () => { + const data = { + content: editInputTodo.value, + }; + + api.putTodo(isEditId, data).then((res) => { + if (!res?.status) { + Toast.fire({ + icon: 'warning', + title: '請重新操作', + }); + } + // end of !res?.status + + if (res?.status) { + const msg = res.message || '編輯成功'; + + Toast.fire({ + icon: 'success', + title: msg, + }); + + getTodos(); + } + // end of res?.status + + setIsEditId(''); + }); + // end of api + }; + // end of handleEditTodo + + useEffect(() => { + setFilterData( + [...todoData] + .filter((item) => { + if (isClickTab === 'ALL') { + return true; + } + if (isClickTab === 'TODO') { + return !item.status; + } + if (isClickTab === 'DONE') { + return item.status; + } + }) + .reverse(), + ); + }, [isClickTab, todoData]); + + return ( +
        +
          + {filterData && + filterData.map((todo) => { + return ( +
        • +
          + + + {isEditId === todo.id ? ( + { + if (e.key === 'Enter' || e.key === 'Escape') { + handleEditTodo(); + } + }} + /> + ) : ( + + )} + + +
          +
        • + ); + })} +
        +
        + ); +} + +// end of TodoCardBody + +export default TodoCardBody; diff --git a/src/routes/Todo.tsx b/src/routes/Todo.tsx index 38a8812..bad88e9 100644 --- a/src/routes/Todo.tsx +++ b/src/routes/Todo.tsx @@ -1,13 +1,13 @@ import React, { useEffect, useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; -import { useInput } from '../hooks/useInput'; import { api } from '../helpers/api'; import { Toast } from '../components/Toast'; import TodoAddInput from '../components/TodoAddInput'; import TodoTabs from '../components/TodoTabs'; +import TodoCardBody from '../components/TodoCardBody'; import Logo from '../components/Logo'; @@ -23,11 +23,7 @@ function Todo(): React.ReactElement { const { token } = useAuth(); const [todoData, setTodoData] = useState([]); - const [filterData, setFilterData] = useState([]); - // const newInputTodo = useInput(''); - const editInputTodo = useInput(''); - const [isEditId, setIsEditId] = useState(''); const [isClickTab, setIsClickTab] = useState('ALL'); const [haveTodoLength, setHaveTodoLength] = useState(0); const [haveClearLength, setHaveClearLength] = useState(0); @@ -106,90 +102,6 @@ function Todo(): React.ReactElement { }; // end of handleAddTodo - const handleRemoveTodo = (todo: ITodoData) => { - api.deleteTodo(todo.id).then((res) => { - if (!res?.status) { - Toast.fire({ - icon: 'warning', - title: '請重新操作', - }); - } - // end of !res?.status - - if (res?.status) { - const msg = res.message || '刪除成功'; - - Toast.fire({ - icon: 'success', - title: msg, - }); - - getTodos(); - } - // end of res?.status - }); - // end of api - }; - // end of handleRemoveTodo - - const handleToggleTodo = (todo: ITodoData) => { - api.patchTodo(todo.id).then((res) => { - if (!res?.status) { - Toast.fire({ - icon: 'warning', - title: '請重新操作', - }); - } - // end of !res?.status - - if (res?.status) { - const msg = res.message || '編輯成功'; - - Toast.fire({ - icon: 'success', - title: msg, - }); - - getTodos(); - } - // end of res?.status - }); - // end of api - }; - // end of handleToggleTodo - - const handleEditTodo = () => { - const data = { - content: editInputTodo.value, - }; - - api.putTodo(isEditId, data).then((res) => { - if (!res?.status) { - Toast.fire({ - icon: 'warning', - title: '請重新操作', - }); - } - // end of !res?.status - - if (res?.status) { - const msg = res.message || '編輯成功'; - - Toast.fire({ - icon: 'success', - title: msg, - }); - - getTodos(); - } - // end of res?.status - - setIsEditId(''); - }); - // end of api - }; - // end of handleEditTodo - const handleClearDoneTodo = () => { const doneIdList = [...todoData] .filter((item) => { @@ -239,22 +151,6 @@ function Todo(): React.ReactElement { }, []); useEffect(() => { - setFilterData( - [...todoData] - .filter((item) => { - if (isClickTab === 'ALL') { - return true; - } - if (isClickTab === 'TODO') { - return !item.status; - } - if (isClickTab === 'DONE') { - return item.status; - } - }) - .reverse(), - ); - setHaveTodoLength( [...todoData].filter((item) => { return !item.status; @@ -271,7 +167,7 @@ function Todo(): React.ReactElement { return (
        -
        +
        +
        -
        -
          - {filterData && - filterData.map((todo) => { - return ( -
        • -
          - - - {isEditId === todo.id ? ( - { - if (e.key === 'Enter' || e.key === 'Escape') { - handleEditTodo(); - } - }} - /> - ) : ( - - )} - - -
          -
        • - ); - })} -
        -
        +