Added MUI to Main Page
This commit is contained in:
1 parent
c4d7ad5b5d
commit
2204ffeece
16 files changed
+319
-107
No files matched your search
+4
-38
@@ -1,42 +1,8 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
body {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
+20
-14
@@ -1,23 +1,29 @@
|
||||
import "./App.css";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
import MainPage from "./pages/MainPage";
|
||||
import TaskPage from "./pages/TaskPage";
|
||||
import NotFound from "./pages/NotFound";
|
||||
import { UserContextProvider } from "./contexts/UserContextProvider";
|
||||
|
||||
import "./App.css";
|
||||
|
||||
import { TaskContextProvider } from "./contexts/TaskContextProvider";
|
||||
import { UserContextProvider } from "./contexts/UserContextProvider";
|
||||
import MainPage from "./pages/MainPage";
|
||||
import NotFound from "./pages/NotFound";
|
||||
import TaskPage from "./pages/TaskPage";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<UserContextProvider>
|
||||
<TaskContextProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<MainPage />} />
|
||||
<Route path="/task/:taskId" element={<TaskPage />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</TaskContextProvider>
|
||||
</UserContextProvider>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<UserContextProvider>
|
||||
<TaskContextProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<MainPage />} />
|
||||
<Route path="/task/:taskId" element={<TaskPage />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</TaskContextProvider>
|
||||
</UserContextProvider>
|
||||
</LocalizationProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
+26
-5
@@ -3,6 +3,7 @@ import type { Task } from "../utils/types";
|
||||
import { UserContext } from "../contexts/userContext";
|
||||
import { useContext } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Grid, ListItem, Typography } from "@mui/material";
|
||||
|
||||
type TaskComponentProps = {
|
||||
task: Task;
|
||||
@@ -22,11 +23,31 @@ const TaskComponent: React.FC<TaskComponentProps> = ({ task }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div onClick={() => navigate(`/task/${task.id}`)}>
|
||||
<span>{task.text}</span>
|
||||
<span>Assigned to: {assignedToUser}</span>
|
||||
<input type="checkbox" checked={task.finished} disabled={true} />
|
||||
</div>
|
||||
<ListItem
|
||||
className="task-list-item"
|
||||
onClick={() => navigate(`/task/${task.id}`)}
|
||||
>
|
||||
<Grid container spacing={2} alignItems="center" width={"100%"}>
|
||||
<Grid size={4}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{task.text}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid size={4}>
|
||||
<Typography variant="body2" color="text.secondary" align="left">
|
||||
Assigned to: {assignedToUser}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+86
-14
@@ -1,21 +1,34 @@
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import type { Task } from "../utils/types";
|
||||
import { useRef } from "react";
|
||||
import { useContext } from "react";
|
||||
import { UserContext } from "../contexts/userContext";
|
||||
import { TaskContext } from "../contexts/taskContext";
|
||||
import {
|
||||
Autocomplete,
|
||||
Button,
|
||||
Card,
|
||||
CardHeader,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import { validateDueDate } from "../utils/validation";
|
||||
import type { Dayjs } from "dayjs";
|
||||
|
||||
type FormValues = {
|
||||
text: string;
|
||||
assignedTo: number;
|
||||
dueDate: Dayjs;
|
||||
};
|
||||
|
||||
const TaskForm: React.FC = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
// formState: { errors: formErrors }
|
||||
control,
|
||||
reset,
|
||||
formState: { errors: formErrors },
|
||||
} = useForm<FormValues>();
|
||||
|
||||
const { users, loading, error } = useContext(UserContext);
|
||||
@@ -26,17 +39,18 @@ const TaskForm: React.FC = () => {
|
||||
function addTask(data: FormValues) {
|
||||
const currentIdMaxId = Math.max(...tasks.map((task) => task.id)) + 1;
|
||||
const newId = Math.max(currentIdMaxId, 1);
|
||||
console.log("NEW ID: ", newId);
|
||||
|
||||
const newTask: Task = {
|
||||
id: newId,
|
||||
assignedTo: Number(data.assignedTo),
|
||||
due: data.dueDate.format("YYYY-MM-DD"),
|
||||
text: data.text,
|
||||
createdAt: creationDate,
|
||||
finished: false,
|
||||
};
|
||||
|
||||
setTasks((prev) => [...prev, newTask]);
|
||||
reset();
|
||||
}
|
||||
|
||||
// TODO make a skeleton
|
||||
@@ -46,17 +60,75 @@ const TaskForm: React.FC = () => {
|
||||
if (users == null) return <div>{error}</div>;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(addTask)}>
|
||||
<input {...register("text", { required: true, minLength: 2 })} />
|
||||
<select {...register("assignedTo", { required: true })}>
|
||||
{users.map((user) => (
|
||||
<option key={user.id} value={user.id}>
|
||||
{user.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
<Card>
|
||||
<CardHeader title="Create new task" />
|
||||
<form className="task-form" onSubmit={handleSubmit(addTask)}>
|
||||
<TextField
|
||||
id="outlined-basic"
|
||||
label="Task text"
|
||||
variant="outlined"
|
||||
error={!!formErrors.text}
|
||||
helperText={formErrors.text?.message}
|
||||
{...register("text", {
|
||||
required: "Task text is required",
|
||||
minLength: {
|
||||
value: 2,
|
||||
message: "Must be at least 2 characters",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="assignedTo"
|
||||
control={control}
|
||||
rules={{ required: "Assignee is required" }}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
disablePortal
|
||||
options={users.map((user) => ({
|
||||
label: user.name,
|
||||
id: user.id,
|
||||
}))}
|
||||
getOptionLabel={(option) => option.label}
|
||||
onChange={(_, data) => field.onChange(data?.id || "")}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Assign To"
|
||||
error={!!formErrors.assignedTo}
|
||||
helperText={formErrors.assignedTo?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="dueDate"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Due date is required",
|
||||
validate: validateDueDate,
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<DatePicker
|
||||
label="Due Date"
|
||||
onChange={(date) => field.onChange(date)}
|
||||
slotProps={{
|
||||
textField: {
|
||||
fullWidth: true,
|
||||
error: !!formErrors.dueDate,
|
||||
helperText: formErrors.dueDate?.message,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" variant="outlined">
|
||||
Add
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import React, { useContext } from "react";
|
||||
import TaskComponent from "./Task";
|
||||
import { TaskContext } from "../contexts/taskContext";
|
||||
import { Card, List } from "@mui/material";
|
||||
|
||||
const TaskList: React.FC = () => {
|
||||
const { tasks } = useContext(TaskContext);
|
||||
|
||||
if (tasks.length === 0) return <div>No tasks yet...</div>;
|
||||
|
||||
return tasks.map((task, index) => (
|
||||
<div key={index}>
|
||||
<TaskComponent task={task} />
|
||||
return (
|
||||
<div className="task-list">
|
||||
<Card>
|
||||
{tasks.length === 0 ? (
|
||||
<p>No tasks yet...</p>
|
||||
) : (
|
||||
<List>
|
||||
{tasks.map((task, index) => (
|
||||
<TaskComponent key={index} task={task} />
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskList;
|
||||
@@ -8,6 +8,8 @@ export const UserContextProvider = ({
|
||||
}: Readonly<{ children: React.ReactNode }>) => {
|
||||
const userProps = useUsers();
|
||||
|
||||
if(userProps.users == null && !userProps.loading) return <div>Failed to load users</div>;
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={userProps}>
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
.task-form {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
width: 50vw;
|
||||
max-width: 1000px;
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.task-form > * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.task-list-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.task-list-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
@@ -23,8 +23,8 @@ export function useUsers() {
|
||||
const data = parseUserData(await response.json());
|
||||
setUsers(data);
|
||||
setError(null);
|
||||
} catch(error) {
|
||||
console.error(error)
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setError("An error occurred during data fetching.");
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import TaskForm from "../components/TaskForm";
|
||||
import TaskList from "../components/TaskList";
|
||||
import "@/css/MainPage.css";
|
||||
|
||||
const MainPage: React.FC = () => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const NotFound: React.FC = () => {
|
||||
return <div>Not found</div>;
|
||||
return <div>Page not found</div>;
|
||||
};
|
||||
|
||||
export default NotFound;
|
||||
@@ -10,6 +10,7 @@ export type Task = {
|
||||
finished: boolean;
|
||||
assignedTo: number; // User ID
|
||||
createdAt: React.RefObject<Date>;
|
||||
due: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Dayjs } from "dayjs";
|
||||
|
||||
export function validateDueDate(value: Dayjs): string | boolean {
|
||||
const selected = new Date(value.format("YYYY-MM-DD"));
|
||||
const now = new Date();
|
||||
now.setHours(0, 0, 0, 0);
|
||||
|
||||
if (selected < now) return "Due date cannot be in the past";
|
||||
|
||||
return true;
|
||||
}
|
||||
Reference in new issue
Block a user