add task check and delete
This commit is contained in:
67
client/src/pages/Projects/components/Project.tsx
Normal file
67
client/src/pages/Projects/components/Project.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
54
client/src/pages/Projects/components/TasksList.tsx
Normal file
54
client/src/pages/Projects/components/TasksList.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
60
client/src/pages/Projects/components/TasksListItem.tsx
Normal file
60
client/src/pages/Projects/components/TasksListItem.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user