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 (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
className="task-list-item"
|
className={`task-list-item ${task.finished && "finished"}`}
|
||||||
onClick={() => navigate(`/task/${task.id}`)}
|
onClick={() => navigate(`/task/${task.id}`)}
|
||||||
>
|
>
|
||||||
<Grid container spacing={2} alignItems="center" width={"100%"}>
|
<Grid container spacing={2} alignItems="center" width={"100%"}>
|
||||||
@@ -38,14 +38,11 @@ const TaskComponent: React.FC<TaskComponentProps> = ({ task }) => {
|
|||||||
Assigned to: {assignedToUser}
|
Assigned to: {assignedToUser}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={3}>
|
<Grid size={4}>
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography variant="body1" color="text.secondary">
|
||||||
Due: {task.due}
|
Due: {task.due}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={1}>
|
|
||||||
<input type="checkbox" checked={task.finished} disabled={true} />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,101 @@
|
|||||||
import React from "react";
|
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 = () => {
|
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;
|
export default TaskDetail;
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
|
Skeleton,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||||
@@ -37,6 +38,7 @@ const TaskForm: React.FC = () => {
|
|||||||
const creationDate = useRef<Date>(new Date());
|
const creationDate = useRef<Date>(new Date());
|
||||||
|
|
||||||
function addTask(data: FormValues) {
|
function addTask(data: FormValues) {
|
||||||
|
creationDate.current = new Date();
|
||||||
const currentIdMaxId = Math.max(...tasks.map((task) => task.id)) + 1;
|
const currentIdMaxId = Math.max(...tasks.map((task) => task.id)) + 1;
|
||||||
const newId = Math.max(currentIdMaxId, 1);
|
const newId = Math.max(currentIdMaxId, 1);
|
||||||
|
|
||||||
@@ -53,8 +55,7 @@ const TaskForm: React.FC = () => {
|
|||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO make a skeleton
|
if (loading) return <Skeleton className="task-form-skeleton" />;
|
||||||
if (loading) return <div>Loading...</div>;
|
|
||||||
|
|
||||||
// TODO Better error display
|
// TODO Better error display
|
||||||
if (users == null) return <div>{error}</div>;
|
if (users == null) return <div>{error}</div>;
|
||||||
|
|||||||
@@ -6,6 +6,15 @@ import { Card, List } from "@mui/material";
|
|||||||
const TaskList: React.FC = () => {
|
const TaskList: React.FC = () => {
|
||||||
const { tasks } = useContext(TaskContext);
|
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 (
|
return (
|
||||||
<div className="task-list">
|
<div className="task-list">
|
||||||
<Card>
|
<Card>
|
||||||
@@ -13,7 +22,7 @@ const TaskList: React.FC = () => {
|
|||||||
<p>No tasks yet...</p>
|
<p>No tasks yet...</p>
|
||||||
) : (
|
) : (
|
||||||
<List>
|
<List>
|
||||||
{tasks.map((task, index) => (
|
{sortedTasks.map((task, index) => (
|
||||||
<TaskComponent key={index} task={task} />
|
<TaskComponent key={index} task={task} />
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
|||||||
@@ -23,3 +23,14 @@
|
|||||||
.task-list-item:hover {
|
.task-list-item:hover {
|
||||||
background-color: #f0f0f0;
|
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 React from "react";
|
||||||
|
|
||||||
import TaskForm from "../components/TaskForm";
|
import TaskForm from "../components/TaskForm";
|
||||||
import TaskList from "../components/TaskList";
|
import TaskList from "../components/TaskList";
|
||||||
import "@/css/MainPage.css";
|
|
||||||
|
|
||||||
const MainPage: React.FC = () => {
|
const MainPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
+2
-13
@@ -1,19 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import TaskDetail from "../components/TaskDetail";
|
||||||
import { TaskContext } from "../contexts/taskContext";
|
|
||||||
import { useContext } from "react";
|
|
||||||
|
|
||||||
const TaskPage: React.FC = () => {
|
const TaskPage: React.FC = () => {
|
||||||
const { taskId } = useParams();
|
return <TaskDetail />;
|
||||||
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>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TaskPage;
|
export default TaskPage;
|
||||||
Reference in new issue
Block a user