From 916f2b340142c217d2f984d09cfa38df5c77b2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Sat, 9 Jul 2022 21:31:12 -0300 Subject: [PATCH] add task check and delete --- client/package.json | 1 + client/src/App.tsx | 12 ++-- client/src/pages/Projects/Projects.tsx | 27 +++++++- .../src/pages/Projects/components/Project.tsx | 67 +++++++++++++++++++ .../pages/Projects/components/TasksList.tsx | 54 +++++++++++++++ .../Projects/components/TasksListItem.tsx | 60 +++++++++++++++++ client/src/providers/AuthProvider.tsx | 2 +- client/src/utils/swrFetcher.ts | 18 +++++ client/yarn.lock | 5 ++ 9 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 client/src/pages/Projects/components/Project.tsx create mode 100644 client/src/pages/Projects/components/TasksList.tsx create mode 100644 client/src/pages/Projects/components/TasksListItem.tsx create mode 100644 client/src/utils/swrFetcher.ts diff --git a/client/package.json b/client/package.json index d71cd2e..1f7e8d9 100644 --- a/client/package.json +++ b/client/package.json @@ -21,6 +21,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.33.1", "react-scripts": "5.0.1", + "swr": "^1.3.0", "typescript": "^4.4.2", "web-vitals": "^2.1.0" }, diff --git a/client/src/App.tsx b/client/src/App.tsx index c4a3e12..3bcd647 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -2,7 +2,7 @@ import "@fontsource/roboto/300.css"; import "@fontsource/roboto/400.css"; import "@fontsource/roboto/500.css"; import "@fontsource/roboto/700.css"; -import { Container } from "@mui/material"; +import { Box, Container } from "@mui/material"; import { CookiesProvider } from "react-cookie"; import { LoginDialog } from "./components/LoginDialog"; import { Topbar } from "./components/Topbar"; @@ -18,10 +18,12 @@ function App() { - } - unauthenticatedContent={} - /> + + } + unauthenticatedContent={} + /> + diff --git a/client/src/pages/Projects/Projects.tsx b/client/src/pages/Projects/Projects.tsx index 5cf6d2b..7d3abad 100644 --- a/client/src/pages/Projects/Projects.tsx +++ b/client/src/pages/Projects/Projects.tsx @@ -1,5 +1,26 @@ +import { Box } from "@mui/material"; +import useSWR from "swr"; +import { useAuth } from "../../hooks/useAuth"; +import { createSWRFetcher } from "../../utils/swrFetcher"; +import { Project } from "./components/Project"; + +type APIProjectList = { + data: { + id: number; + name: string; + }[]; +}; + export const Projects = () => { + const { token } = useAuth(); + const fetcher = createSWRFetcher(token); + const { data } = useSWR("projects", fetcher); + return ( - <> - ) -} \ No newline at end of file + + {data?.data.map((project) => ( + + ))} + + ); +}; diff --git a/client/src/pages/Projects/components/Project.tsx b/client/src/pages/Projects/components/Project.tsx new file mode 100644 index 0000000..84598da --- /dev/null +++ b/client/src/pages/Projects/components/Project.tsx @@ -0,0 +1,67 @@ +import { Add } from "@mui/icons-material"; +import MoreVertIcon from "@mui/icons-material/MoreVert"; +import { + Card, + CardActions, + CardContent, + CardHeader, + IconButton, +} from "@mui/material"; +import useSWR from "swr"; +import { useAuth } from "../../../hooks/useAuth"; +import { createSWRFetcher } from "../../../utils/swrFetcher"; +import { TaskListProps, TasksList } from "./TasksList"; + +export type APIProjectTasksList = { + data: TaskListProps["tasks"]; +}; + +export type ProjectProps = { + id: number; + name: string; +}; + +export const Project = (props: ProjectProps) => { + const { token } = useAuth(); + const fetcher = createSWRFetcher(token); + + const { data, mutate } = useSWR( + `projects/${props.id}/tasks`, + fetcher + ); + + const uncompletedTasks = data?.data.filter((task) => !task.finishedAt) ?? []; + const completedTasks = data?.data.filter((task) => task.finishedAt) ?? []; + + return ( + + + + + } + title={props.name} + /> + + + + + + + + + + + ); +}; diff --git a/client/src/pages/Projects/components/TasksList.tsx b/client/src/pages/Projects/components/TasksList.tsx new file mode 100644 index 0000000..3002711 --- /dev/null +++ b/client/src/pages/Projects/components/TasksList.tsx @@ -0,0 +1,54 @@ +import { List, ListSubheader } from "@mui/material"; +import { KeyedMutator } from "swr"; +import { useAuth } from "../../../hooks/useAuth"; +import { APIProjectTasksList } from "./Project"; +import { Task, TasksListItem } from "./TasksListItem"; + +export type TaskListProps = { + projectId: number; + title: string; + tasks: Task[]; + mutate: KeyedMutator; +}; + +export const TasksList = ({ + projectId, + title, + tasks, + mutate, +}: TaskListProps) => { + const { apiClient } = useAuth(); + const handleCheck = (taskId: number) => { + apiClient(`projects/${projectId}/tasks/${taskId}/finish`).then((res) => { + mutate(); + }); + }; + + const handleDelete = (taskId: number) => { + apiClient(`projects/${projectId}/tasks/${taskId}`, { + method: "DELETE", + }).then((res) => { + mutate(); + }); + }; + + return ( + + {title} + + } + > + {tasks.map((task) => ( + + ))} + + ); +}; diff --git a/client/src/pages/Projects/components/TasksListItem.tsx b/client/src/pages/Projects/components/TasksListItem.tsx new file mode 100644 index 0000000..917ac71 --- /dev/null +++ b/client/src/pages/Projects/components/TasksListItem.tsx @@ -0,0 +1,60 @@ +import DeleteIcon from "@mui/icons-material/Delete"; +import { + Checkbox, + IconButton, + ListItem, + ListItemAvatar, + ListItemText +} from "@mui/material"; +import { useState } from "react"; + +export type Task = { + id: number; + description: string; + createdAt: Date; + finishedAt?: Date; +}; + +export type TasksListItemProps = { + task: Task; + onCheck: (taskId: number) => void; + onDelete: (taskId: number) => void; +}; + +export const TasksListItem = ({ + task, + onCheck, + onDelete, +}: TasksListItemProps) => { + const finished = !!task.finishedAt; + const [isLoading, setIsLoading] = useState(false); + + const handleCheck = () => { + setIsLoading(true); + onCheck(task.id); + }; + + const handleDelete = () => { + setIsLoading(true); + onDelete(task.id); + }; + + const blockInteration = finished || isLoading; + + return ( + + + + ) + } + > + + + + + + ); +}; diff --git a/client/src/providers/AuthProvider.tsx b/client/src/providers/AuthProvider.tsx index d69a6b9..ebad2e8 100644 --- a/client/src/providers/AuthProvider.tsx +++ b/client/src/providers/AuthProvider.tsx @@ -46,7 +46,7 @@ export const AuthProvider = ({ children, ...rest }: AuthProviderProps) => { password, }), }).then(async (res) => { - setToken(await res.json()); + setToken((await res.json()).token); setIsLoginDialogOpen(false); }); }, diff --git a/client/src/utils/swrFetcher.ts b/client/src/utils/swrFetcher.ts new file mode 100644 index 0000000..5601f2c --- /dev/null +++ b/client/src/utils/swrFetcher.ts @@ -0,0 +1,18 @@ +const host = "http://localhost:5000/"; + +export const createSWRFetcher = + (token: string | null) => async (input: RequestInfo | URL, init?: RequestInit) => { + const { headers, ...rest } = init ?? {}; + + const customInt: RequestInit = { + headers: { + "Content-Type": "application/json", + "X-Access-Token": token ?? "", + ...headers, + }, + ...rest, + }; + + const res = await fetch(host + input, customInt); + return await res.json(); + }; diff --git a/client/yarn.lock b/client/yarn.lock index 4cfc0ca..fc0a5f8 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -8484,6 +8484,11 @@ svgo@^2.7.0: picocolors "^1.0.0" stable "^0.1.8" +swr@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" + integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw== + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"