Added MUI to Main Page
This commit is contained in:
1 parent
c4d7ad5b5d
commit
2204ffeece
15 files changed
+282
-70
No files matched your search
@@ -13,6 +13,8 @@
|
|||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/material": "^7.1.1",
|
"@mui/material": "^7.1.1",
|
||||||
|
"@mui/x-date-pickers": "^8.5.3",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-hook-form": "^7.58.1",
|
"react-hook-form": "^7.58.1",
|
||||||
|
|||||||
Generated
+90
@@ -17,6 +17,12 @@ importers:
|
|||||||
'@mui/material':
|
'@mui/material':
|
||||||
specifier: ^7.1.1
|
specifier: ^7.1.1
|
||||||
version: 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@mui/x-date-pickers':
|
||||||
|
specifier: ^8.5.3
|
||||||
|
version: 8.5.3(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(dayjs@1.11.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
dayjs:
|
||||||
|
specifier: ^1.11.13
|
||||||
|
version: 1.11.13
|
||||||
react:
|
react:
|
||||||
specifier: ^19.1.0
|
specifier: ^19.1.0
|
||||||
version: 19.1.0
|
version: 19.1.0
|
||||||
@@ -471,6 +477,50 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@mui/x-date-pickers@8.5.3':
|
||||||
|
resolution: {integrity: sha512-rToJOgIfVN/mG8QPX5RSPcP7QdnNN4yk+37fBztVUnmR7mhiWdUNSHlazGpnRGRqDdr4lXRkJbTjtJLviWkhKg==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/react': ^11.9.0
|
||||||
|
'@emotion/styled': ^11.8.1
|
||||||
|
'@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||||
|
'@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||||
|
date-fns: ^2.25.0 || ^3.2.0 || ^4.0.0
|
||||||
|
date-fns-jalali: ^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0
|
||||||
|
dayjs: ^1.10.7
|
||||||
|
luxon: ^3.0.2
|
||||||
|
moment: ^2.29.4
|
||||||
|
moment-hijri: ^2.1.2 || ^3.0.0
|
||||||
|
moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@emotion/react':
|
||||||
|
optional: true
|
||||||
|
'@emotion/styled':
|
||||||
|
optional: true
|
||||||
|
date-fns:
|
||||||
|
optional: true
|
||||||
|
date-fns-jalali:
|
||||||
|
optional: true
|
||||||
|
dayjs:
|
||||||
|
optional: true
|
||||||
|
luxon:
|
||||||
|
optional: true
|
||||||
|
moment:
|
||||||
|
optional: true
|
||||||
|
moment-hijri:
|
||||||
|
optional: true
|
||||||
|
moment-jalaali:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@mui/x-internals@8.5.3':
|
||||||
|
resolution: {integrity: sha512-ImCg4E3DT3XoDIZO0pNCbB7iw14N+YCFY3J1V28POwCD7P2f3HSIz4jwzM006oYxI6bqeE6LMfpdPRDW6s6dQw==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -833,6 +883,9 @@ packages:
|
|||||||
csstype@3.1.3:
|
csstype@3.1.3:
|
||||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||||
|
|
||||||
|
dayjs@1.11.13:
|
||||||
|
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||||
|
|
||||||
debug@4.4.1:
|
debug@4.4.1:
|
||||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
@@ -1222,6 +1275,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
|
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
reselect@5.1.1:
|
||||||
|
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||||
|
|
||||||
resolve-from@4.0.0:
|
resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -1746,6 +1802,36 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.8
|
'@types/react': 19.1.8
|
||||||
|
|
||||||
|
'@mui/x-date-pickers@8.5.3(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(dayjs@1.11.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.6
|
||||||
|
'@mui/material': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@mui/system': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
|
||||||
|
'@mui/utils': 7.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||||
|
'@mui/x-internals': 8.5.3(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
|
||||||
|
'@types/react-transition-group': 4.4.12(@types/react@19.1.8)
|
||||||
|
clsx: 2.1.1
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@emotion/react': 11.14.0(@types/react@19.1.8)(react@19.1.0)
|
||||||
|
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
|
||||||
|
dayjs: 1.11.13
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
|
||||||
|
'@mui/x-internals@8.5.3(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.6
|
||||||
|
'@mui/system': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
|
||||||
|
'@mui/utils': 7.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
reselect: 5.1.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nodelib/fs.stat': 2.0.5
|
'@nodelib/fs.stat': 2.0.5
|
||||||
@@ -2073,6 +2159,8 @@ snapshots:
|
|||||||
|
|
||||||
csstype@3.1.3: {}
|
csstype@3.1.3: {}
|
||||||
|
|
||||||
|
dayjs@1.11.13: {}
|
||||||
|
|
||||||
debug@4.4.1:
|
debug@4.4.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
@@ -2453,6 +2541,8 @@ snapshots:
|
|||||||
|
|
||||||
react@19.1.0: {}
|
react@19.1.0: {}
|
||||||
|
|
||||||
|
reselect@5.1.1: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
resolve@1.22.10:
|
resolve@1.22.10:
|
||||||
|
|||||||
+2
-36
@@ -1,42 +1,8 @@
|
|||||||
#root {
|
#root {
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
body {
|
||||||
height: 6em;
|
background-color: #f0f0f0;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
+11
-5
@@ -1,14 +1,19 @@
|
|||||||
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 { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||||
import MainPage from "./pages/MainPage";
|
|
||||||
import TaskPage from "./pages/TaskPage";
|
import "./App.css";
|
||||||
import NotFound from "./pages/NotFound";
|
|
||||||
import { UserContextProvider } from "./contexts/UserContextProvider";
|
|
||||||
import { TaskContextProvider } from "./contexts/TaskContextProvider";
|
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() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
<UserContextProvider>
|
<UserContextProvider>
|
||||||
<TaskContextProvider>
|
<TaskContextProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
@@ -18,6 +23,7 @@ function App() {
|
|||||||
</Routes>
|
</Routes>
|
||||||
</TaskContextProvider>
|
</TaskContextProvider>
|
||||||
</UserContextProvider>
|
</UserContextProvider>
|
||||||
|
</LocalizationProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-4
@@ -3,6 +3,7 @@ import type { Task } from "../utils/types";
|
|||||||
import { UserContext } from "../contexts/userContext";
|
import { UserContext } from "../contexts/userContext";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Grid, ListItem, Typography } from "@mui/material";
|
||||||
|
|
||||||
type TaskComponentProps = {
|
type TaskComponentProps = {
|
||||||
task: Task;
|
task: Task;
|
||||||
@@ -22,11 +23,31 @@ const TaskComponent: React.FC<TaskComponentProps> = ({ task }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={() => navigate(`/task/${task.id}`)}>
|
<ListItem
|
||||||
<span>{task.text}</span>
|
className="task-list-item"
|
||||||
<span>Assigned to: {assignedToUser}</span>
|
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} />
|
<input type="checkbox" checked={task.finished} disabled={true} />
|
||||||
</div>
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ListItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+85
-13
@@ -1,21 +1,34 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import type { Task } from "../utils/types";
|
import type { Task } from "../utils/types";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { UserContext } from "../contexts/userContext";
|
import { UserContext } from "../contexts/userContext";
|
||||||
import { TaskContext } from "../contexts/taskContext";
|
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 = {
|
type FormValues = {
|
||||||
text: string;
|
text: string;
|
||||||
assignedTo: number;
|
assignedTo: number;
|
||||||
|
dueDate: Dayjs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskForm: React.FC = () => {
|
const TaskForm: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
// formState: { errors: formErrors }
|
control,
|
||||||
|
reset,
|
||||||
|
formState: { errors: formErrors },
|
||||||
} = useForm<FormValues>();
|
} = useForm<FormValues>();
|
||||||
|
|
||||||
const { users, loading, error } = useContext(UserContext);
|
const { users, loading, error } = useContext(UserContext);
|
||||||
@@ -26,17 +39,18 @@ const TaskForm: React.FC = () => {
|
|||||||
function addTask(data: FormValues) {
|
function addTask(data: FormValues) {
|
||||||
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);
|
||||||
console.log("NEW ID: ", newId);
|
|
||||||
|
|
||||||
const newTask: Task = {
|
const newTask: Task = {
|
||||||
id: newId,
|
id: newId,
|
||||||
assignedTo: Number(data.assignedTo),
|
assignedTo: Number(data.assignedTo),
|
||||||
|
due: data.dueDate.format("YYYY-MM-DD"),
|
||||||
text: data.text,
|
text: data.text,
|
||||||
createdAt: creationDate,
|
createdAt: creationDate,
|
||||||
finished: false,
|
finished: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
setTasks((prev) => [...prev, newTask]);
|
setTasks((prev) => [...prev, newTask]);
|
||||||
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO make a skeleton
|
// TODO make a skeleton
|
||||||
@@ -46,17 +60,75 @@ const TaskForm: React.FC = () => {
|
|||||||
if (users == null) return <div>{error}</div>;
|
if (users == null) return <div>{error}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(addTask)}>
|
<Card>
|
||||||
<input {...register("text", { required: true, minLength: 2 })} />
|
<CardHeader title="Create new task" />
|
||||||
<select {...register("assignedTo", { required: true })}>
|
<form className="task-form" onSubmit={handleSubmit(addTask)}>
|
||||||
{users.map((user) => (
|
<TextField
|
||||||
<option key={user.id} value={user.id}>
|
id="outlined-basic"
|
||||||
{user.name}
|
label="Task text"
|
||||||
</option>
|
variant="outlined"
|
||||||
))}
|
error={!!formErrors.text}
|
||||||
</select>
|
helperText={formErrors.text?.message}
|
||||||
<input type="submit" value="Submit" />
|
{...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>
|
</form>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import TaskComponent from "./Task";
|
import TaskComponent from "./Task";
|
||||||
import { TaskContext } from "../contexts/taskContext";
|
import { TaskContext } from "../contexts/taskContext";
|
||||||
|
import { Card, List } from "@mui/material";
|
||||||
|
|
||||||
const TaskList: React.FC = () => {
|
const TaskList: React.FC = () => {
|
||||||
const { tasks } = useContext(TaskContext);
|
const { tasks } = useContext(TaskContext);
|
||||||
|
|
||||||
if (tasks.length === 0) return <div>No tasks yet...</div>;
|
return (
|
||||||
|
<div className="task-list">
|
||||||
return tasks.map((task, index) => (
|
<Card>
|
||||||
<div key={index}>
|
{tasks.length === 0 ? (
|
||||||
<TaskComponent task={task} />
|
<p>No tasks yet...</p>
|
||||||
|
) : (
|
||||||
|
<List>
|
||||||
|
{tasks.map((task, index) => (
|
||||||
|
<TaskComponent key={index} task={task} />
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
));
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TaskList;
|
export default TaskList;
|
||||||
@@ -8,6 +8,8 @@ export const UserContextProvider = ({
|
|||||||
}: Readonly<{ children: React.ReactNode }>) => {
|
}: Readonly<{ children: React.ReactNode }>) => {
|
||||||
const userProps = useUsers();
|
const userProps = useUsers();
|
||||||
|
|
||||||
|
if(userProps.users == null && !userProps.loading) return <div>Failed to load users</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserContext.Provider value={userProps}>
|
<UserContext.Provider value={userProps}>
|
||||||
{children}
|
{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());
|
const data = parseUserData(await response.json());
|
||||||
setUsers(data);
|
setUsers(data);
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch(error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
setError("An error occurred during data fetching.");
|
setError("An error occurred during data fetching.");
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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 (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const NotFound: React.FC = () => {
|
const NotFound: React.FC = () => {
|
||||||
return <div>Not found</div>;
|
return <div>Page not found</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotFound;
|
export default NotFound;
|
||||||
@@ -10,6 +10,7 @@ export type Task = {
|
|||||||
finished: boolean;
|
finished: boolean;
|
||||||
assignedTo: number; // User ID
|
assignedTo: number; // User ID
|
||||||
createdAt: React.RefObject<Date>;
|
createdAt: React.RefObject<Date>;
|
||||||
|
due: string;
|
||||||
text: 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;
|
||||||
|
}
|
||||||
+9
-3
@@ -1,7 +1,13 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from "@vitejs/plugin-react-swc";
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
})
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in new issue
Block a user