Task detail page finalized and added user loading skeleton
This commit is contained in:
1 parent
2204ffeece
commit
e5e565430c
7 files changed
+126
-23
No files matched your search
@@ -24,7 +24,7 @@ const TaskComponent: React.FC<TaskComponentProps> = ({ task }) => {
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
className="task-list-item"
|
||||
className={`task-list-item ${task.finished && "finished"}`}
|
||||
onClick={() => navigate(`/task/${task.id}`)}
|
||||
>
|
||||
<Grid container spacing={2} alignItems="center" width={"100%"}>
|
||||
@@ -38,14 +38,11 @@ const TaskComponent: React.FC<TaskComponentProps> = ({ task }) => {
|
||||
Assigned to: {assignedToUser}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<Grid size={4}>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Due: {task.due}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid size={1}>
|
||||
<input type="checkbox" checked={task.finished} disabled={true} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,101 @@
|
||||
import React from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { TaskContext } from "../contexts/taskContext";
|
||||
import { useContext } from "react";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Divider,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { UserContext } from "../contexts/userContext";
|
||||
import type { User } from "../utils/types";
|
||||
|
||||
const TaskDetail: React.FC = () => {
|
||||
return <div></div>;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { taskId } = useParams();
|
||||
|
||||
const { tasks, setTasks } = useContext(TaskContext);
|
||||
const { users, loading } = useContext(UserContext);
|
||||
|
||||
const numerizedTaskId = Number(taskId);
|
||||
|
||||
const taskInQuestion = tasks.find((task) => task.id === numerizedTaskId);
|
||||
|
||||
if (!taskInQuestion) return <div>Task not found</div>;
|
||||
|
||||
let userInQuestion: User | undefined;
|
||||
if (users && !loading) {
|
||||
userInQuestion = users.find(
|
||||
(user) => user.id === taskInQuestion?.assignedTo,
|
||||
);
|
||||
}
|
||||
|
||||
function markFinished() {
|
||||
setTasks((prevTasks) =>
|
||||
prevTasks.map((task) =>
|
||||
// @ts-expect-error The function is called after the check. It's safe to assume taskInQuestion is not undefined
|
||||
task.id === taskInQuestion.id
|
||||
? { ...task, finished: !task.finished }
|
||||
: task,
|
||||
),
|
||||
);
|
||||
navigate(-1);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title={`Task #${taskInQuestion.id}`} />
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Typography variant="subtitle1" color="text.primary">
|
||||
{taskInQuestion.text}
|
||||
</Typography>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Typography variant="body2" color="text.secondary" align="left">
|
||||
<b>Assigned to:</b>{" "}
|
||||
{userInQuestion
|
||||
? `${userInQuestion.name} <${userInQuestion.email}>`
|
||||
: "Unknown user"}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" align="left">
|
||||
<b>Due date:</b> {taskInQuestion.due}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" align="left">
|
||||
<b>Created at:</b>{" "}
|
||||
{taskInQuestion.createdAt.current.toLocaleString()}
|
||||
</Typography>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Button variant="outlined" onClick={() => navigate(-1)}>
|
||||
Go back
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={markFinished}
|
||||
>
|
||||
{!taskInQuestion.finished ? "Mark finished" : "Undo"}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskDetail;
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
CardHeader,
|
||||
Skeleton,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
@@ -37,6 +38,7 @@ const TaskForm: React.FC = () => {
|
||||
const creationDate = useRef<Date>(new Date());
|
||||
|
||||
function addTask(data: FormValues) {
|
||||
creationDate.current = new Date();
|
||||
const currentIdMaxId = Math.max(...tasks.map((task) => task.id)) + 1;
|
||||
const newId = Math.max(currentIdMaxId, 1);
|
||||
|
||||
@@ -53,8 +55,7 @@ const TaskForm: React.FC = () => {
|
||||
reset();
|
||||
}
|
||||
|
||||
// TODO make a skeleton
|
||||
if (loading) return <div>Loading...</div>;
|
||||
if (loading) return <Skeleton className="task-form-skeleton" />;
|
||||
|
||||
// TODO Better error display
|
||||
if (users == null) return <div>{error}</div>;
|
||||
|
||||
@@ -6,6 +6,15 @@ import { Card, List } from "@mui/material";
|
||||
const TaskList: React.FC = () => {
|
||||
const { tasks } = useContext(TaskContext);
|
||||
|
||||
// show unfinished tasks later and those with closer due date higher
|
||||
const sortedTasks = tasks.sort((a, b) => {
|
||||
if (a.finished == b.finished) {
|
||||
if (a.due === b.due) return 0;
|
||||
return a.due < b.due ? -1 : 1;
|
||||
}
|
||||
return a.finished ? 1 : -1;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="task-list">
|
||||
<Card>
|
||||
@@ -13,7 +22,7 @@ const TaskList: React.FC = () => {
|
||||
<p>No tasks yet...</p>
|
||||
) : (
|
||||
<List>
|
||||
{tasks.map((task, index) => (
|
||||
{sortedTasks.map((task, index) => (
|
||||
<TaskComponent key={index} task={task} />
|
||||
))}
|
||||
</List>
|
||||
|
||||
@@ -23,3 +23,14 @@
|
||||
.task-list-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.task-list-item.finished {
|
||||
text-decoration: line-through;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.task-form-skeleton {
|
||||
width: 50vw;
|
||||
max-width: 1000px;
|
||||
height: 100px;
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import "@/css/MainPage.css";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import TaskForm from "../components/TaskForm";
|
||||
import TaskList from "../components/TaskList";
|
||||
import "@/css/MainPage.css";
|
||||
|
||||
const MainPage: React.FC = () => {
|
||||
return (
|
||||
|
||||
+2
-13
@@ -1,19 +1,8 @@
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { TaskContext } from "../contexts/taskContext";
|
||||
import { useContext } from "react";
|
||||
import TaskDetail from "../components/TaskDetail";
|
||||
|
||||
const TaskPage: React.FC = () => {
|
||||
const { taskId } = useParams();
|
||||
const numerizedTaskId = Number(taskId);
|
||||
|
||||
const { tasks } = useContext(TaskContext);
|
||||
|
||||
const taskInQuestion = tasks.find((task) => task.id === numerizedTaskId);
|
||||
|
||||
if (!taskInQuestion) return <div>Task not found</div>;
|
||||
|
||||
return <div>Task {taskId}</div>;
|
||||
return <TaskDetail />;
|
||||
};
|
||||
|
||||
export default TaskPage;
|
||||
Reference in new issue
Block a user