add task check and delete

This commit is contained in:
João Geonizeli
2022-07-09 21:31:12 -03:00
parent 02955c8603
commit 916f2b3401
9 changed files with 237 additions and 9 deletions

View File

@@ -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"
},

View File

@@ -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() {
<Topbar />
<LoginDialog />
<Container>
<Box marginTop={3}>
<UnautorizedBlock
protectedContent={<Projects />}
unauthenticatedContent={<NewAccount />}
/>
</Box>
</Container>
</AuthProvider>
</CookiesProvider>

View File

@@ -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<APIProjectList>("projects", fetcher);
return (
<></>
)
}
<Box sx={{ display: "grid", gridAutoColumns: 'repeat(auto-fit, minmax(250px, 1fr))' }}>
{data?.data.map((project) => (
<Project key={project.id} {...project} />
))}
</Box>
);
};

View File

@@ -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<APIProjectTasksList>(
`projects/${props.id}/tasks`,
fetcher
);
const uncompletedTasks = data?.data.filter((task) => !task.finishedAt) ?? [];
const completedTasks = data?.data.filter((task) => task.finishedAt) ?? [];
return (
<Card sx={{ margin: 4 }}>
<CardHeader
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={props.name}
/>
<CardContent>
<TasksList
projectId={props.id}
mutate={mutate}
title="To Do"
tasks={uncompletedTasks}
/>
<TasksList
projectId={props.id}
mutate={mutate}
title="Done"
tasks={completedTasks}
/>
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add task">
<Add />
</IconButton>
</CardActions>
</Card>
);
};

View File

@@ -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<APIProjectTasksList>;
};
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 (
<List
dense
subheader={
<ListSubheader component="div" id="nested-list-subheader">
{title}
</ListSubheader>
}
>
{tasks.map((task) => (
<TasksListItem
key={task.id}
task={task}
onCheck={handleCheck}
onDelete={handleDelete}
/>
))}
</List>
);
};

View File

@@ -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 (
<ListItem
secondaryAction={
finished ? null : (
<IconButton onClick={handleDelete} disabled={blockInteration} edge="end" aria-label="delete">
<DeleteIcon />
</IconButton>
)
}
>
<ListItemAvatar>
<Checkbox onChange={handleCheck} disabled={blockInteration} checked={blockInteration} />
</ListItemAvatar>
<ListItemText primary="Single-line item" />
</ListItem>
);
};

View File

@@ -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);
});
},

View File

@@ -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();
};

View File

@@ -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"