Commit 1d3b7017 authored by Нелли Ибрагимова's avatar Нелли Ибрагимова

Merge branch 'development' of…

Merge branch 'development' of ssh://git.attractor-school.com:30022/apollo64/crm-team-one into task-80-my-task-new-task
parents b20ea305 39724752
......@@ -12,6 +12,7 @@
"@types/bcrypt": "^5.0.0",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.14",
"@types/nodemailer": "^6.4.6",
"bcrypt": "^5.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
......@@ -20,6 +21,7 @@
"mongoose": "^6.7.0",
"multer": "^1.4.5-lts.1",
"nanoid": "^3.3.4",
"nodemailer": "^6.8.0",
"path": "^0.12.7",
"pg": "^8.8.0",
"reflect-metadata": "^0.1.13",
......@@ -1712,6 +1714,14 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.8.tgz",
"integrity": "sha512-uGwPWlE0Hj972KkHtCDVwZ8O39GmyjfMane1Z3GUBGGnkZ2USDq7SxLpVIiIHpweY9DS0QTDH0Nw7RNBsAAZ5A=="
},
"node_modules/@types/nodemailer": {
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.6.tgz",
"integrity": "sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
......@@ -4488,6 +4498,14 @@
"webidl-conversions": "^3.0.0"
}
},
"node_modules/nodemailer": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz",
"integrity": "sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nodemon": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
......@@ -7663,6 +7681,14 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.8.tgz",
"integrity": "sha512-uGwPWlE0Hj972KkHtCDVwZ8O39GmyjfMane1Z3GUBGGnkZ2USDq7SxLpVIiIHpweY9DS0QTDH0Nw7RNBsAAZ5A=="
},
"@types/nodemailer": {
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.6.tgz",
"integrity": "sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==",
"requires": {
"@types/node": "*"
}
},
"@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
......@@ -9721,6 +9747,11 @@
}
}
},
"nodemailer": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz",
"integrity": "sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ=="
},
"nodemon": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
......
......@@ -29,6 +29,7 @@
"@types/bcrypt": "^5.0.0",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.14",
"@types/nodemailer": "^6.4.6",
"bcrypt": "^5.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
......@@ -37,6 +38,7 @@
"mongoose": "^6.7.0",
"multer": "^1.4.5-lts.1",
"nanoid": "^3.3.4",
"nodemailer": "^6.8.0",
"path": "^0.12.7",
"pg": "^8.8.0",
"reflect-metadata": "^0.1.13",
......
......@@ -4,6 +4,7 @@ import {Task} from './models/Task';
import { Project } from "./models/Project";
import { Member } from "./models/Member";
import { DateTimeTask } from "./models/DateTimeTask";
import { PasswordRecovery } from "./models/PasswordRecovery";
export const myDataSource = new DataSource({
type: "postgres",
......@@ -12,7 +13,7 @@ export const myDataSource = new DataSource({
username: "pluser",
password: "pluser",
database: "planner",
entities: [User,Task,Project,Member,DateTimeTask],
entities: [User,Task,Project,Member,DateTimeTask,PasswordRecovery],
logging: true,
synchronize: true, // in build switch to false
migrationsRun: false
......
......@@ -2,6 +2,8 @@ import express, { NextFunction, Request, Response, Router } from "express";
import { myDataSource } from "./app-data-source";
import { Task } from "./models/Task";
import { User } from "./models/User";
import nodemailer from 'nodemailer';
const dataSource = myDataSource;
......@@ -24,33 +26,39 @@ export const auth = async(req: Request,res: Response, next:NextFunction):Promise
export const authAuthorOrExecutorOfTask = async(req: Request,res: Response, next:NextFunction):Promise<void | express.Response<Response>>=>{
const token = req.get('Authorization');
const {taskId} = req.body
if(!token) return res.status(401).send({Message:'token not exists'})
req.body={...req.body,executorStatus:false}
req.body={...req.body,authorStatus:false}
const executor = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.leftJoinAndSelect("user.tasks", "task")
.where("user.token = :token", { token: token })
.getOne();
console.log('executor', executor)
if (executor) {
req.body={...req.body,executorStatus:true}
const task = await dataSource
.getRepository(Task)
.findOne({
relations:{
executor:true,
author:true,
},
where:[
{
id:taskId,
executor:{
token:token
}
const author = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.leftJoinAndSelect("user.createdTasks", "task")
.where("user.token = :token", { token: token })
.getOne();
console.log('author', author)
if (author) {
},
{
id:taskId,
author:{
token:token
}
},
]})
if (!task) return res.status(404).send({message:'task with possible user involved is not found'})
if (task?.author?.token === token ) {
req.body={...req.body,authorStatus:true}
} else if(task?.executor?.token === token) {
req.body={...req.body,executorStatus:true}
} else {
}
if(!author && !executor)return res.status(401).send({Message:'user is not authorized'})
next()
};
......@@ -70,3 +78,20 @@ export const taskFinderById = async (taskId:string):Promise<null | Task>=>{
})
return task
}
export let transporter = nodemailer.createTransport({
service:'Yandex',
// host: "smtp.yandex.ru",
// port: 465,
// secure: true, // true for 465, false for other ports
auth: {
user: "planner45@yandex.ru", // generated ethereal user
pass: "newPlannerProject123" // generated ethereal password
}
})
export const FRONTEND_URL = 'localhost:3000'
\ No newline at end of file
import {
Column,
Entity,
PrimaryGeneratedColumn,
BaseEntity,
OneToOne,
CreateDateColumn,
JoinColumn,
} from 'typeorm';
import { User } from './User';
interface IPasswordRecovery{
createdAt:Date;
token: string;
user: User;
enabled:boolean;
}
@Entity({name: 'PasswordRecovery'})
export class PasswordRecovery extends BaseEntity implements IPasswordRecovery{
@PrimaryGeneratedColumn('uuid')
id!: string;
@CreateDateColumn({ name: 'created_at', type: Date, default: new Date() })
createdAt!: Date;
@Column({ name: 'token', type: 'varchar',length:100, unique: true, nullable:true })
token!: string;
@OneToOne(()=>User,{nullable: false, eager:true})
@JoinColumn()
user!:User;
@Column({ name: 'enabled', type:'boolean', default:true})
enabled!: boolean;
}
\ No newline at end of file
......@@ -7,7 +7,8 @@ import {
ManyToOne,
OneToOne,
JoinTable,
OneToMany
OneToMany,
JoinColumn
} from 'typeorm';
import {User} from './User';
import {Project} from './Project';
......@@ -28,9 +29,9 @@ import { DateTimeTask } from './DateTimeTask';
priority: priorityType | null;
archive:boolean,
author: User;
executor:User;
project:Project|null;
dateTimeTasks:DateTimeTask[]|null;
executor:User;
}
@Entity({ name: 'Task' })
......@@ -49,7 +50,7 @@ import { DateTimeTask } from './DateTimeTask';
dateTimeDeadLine!: Date;
@Column({ name: 'dateTimeFactDeadLine', type: Date,nullable: true })
dateTimeFactDeadLine!: Date;
@Column({ name: 'archive', type: 'varchar', length:50,nullable: false, default:false })
@Column({ name: 'archive', type: 'boolean',nullable: false, default:false })
archive!: boolean
@Column({
......@@ -73,8 +74,7 @@ import { DateTimeTask } from './DateTimeTask';
@ManyToOne(() => User, (user: { tasks: Task[]; }) => user.tasks,{eager : true})
author!: User;
@ManyToOne(() =>User, (user: { tasks: Task[]}) => user.tasks,{eager : true,nullable: true, onUpdate:'CASCADE'})
@JoinTable()
@ManyToOne(() =>User, (user: { tasks: Task[]}) => user.tasks,{nullable: true,cascade: true, onUpdate:'CASCADE', eager : true,})
executor!: User;
@ManyToOne(()=>Project,(project:{tasks: Task[]}) => project.tasks,{eager : true,nullable: true,onUpdate:'CASCADE'})
......
......@@ -5,9 +5,7 @@ import {
CreateDateColumn,
BeforeInsert,
BaseEntity,
ManyToMany,
OneToMany,
JoinTable,
OneToOne,
} from 'typeorm';
import {IsEmail
......@@ -17,8 +15,9 @@ import bcrypt from 'bcrypt';
import {nanoid} from 'nanoid';
import {Task} from './Task';
import {Member} from './Member';
import { PasswordRecovery } from './PasswordRecovery';
const SALT_WORK_FACTOR= 10;
export const SALT_WORK_FACTOR= 10;
export enum UserRole {USER="user" ,DIRECTOR= "director",SUPERUSER="superuser"}
......@@ -38,7 +37,6 @@ interface IUser {
}
@Entity({ name: 'User' })
export class User extends BaseEntity implements IUser {
@PrimaryGeneratedColumn("uuid")
......@@ -76,20 +74,15 @@ export class User extends BaseEntity implements IUser {
@Exclude({ toPlainOnly: true })
password!: string;
@OneToMany(() => Task, (task: { user: User }) => task.user)
createdTasks!: Task[];
@OneToMany(() => Task, (task: { user: User }) =>task.user)
tasks!: Task[];
@OneToMany(() => Member, (member: { user: User }) => member.user)
members!: Member[];
// @ManyToMany(() => Project,(project: { user: User }) => project.user)
// @JoinTable()
// workerInProjects!: Project[];
@BeforeInsert()
......
......@@ -28,38 +28,51 @@ const taskFinderById = async (taskId:string):Promise<null | Task>=>{
}
/** make copy of task in calendar view */
router.post("/make-copy",authAuthorOrExecutorOfTask, async(req:Request, res:Response):Promise<Response>=>{
const {executorStatus,taskId,start, due} = req.body
if (executorStatus){
router.post("/make-copy", async(req:Request, res:Response):Promise<Response>=>{
const {taskId, dateTimeDue, dateTimeStart} = req.body
const newDateTimeTask = new DateTimeTask();
newDateTimeTask.dateTimeStart = start
newDateTimeTask.dateTimeDue = due
newDateTimeTask.dateTimeStart = dateTimeStart
newDateTimeTask.dateTimeDue = dateTimeDue
newDateTimeTask.task = taskId
await newDateTimeTask.save()
const task = taskFinderById(taskId)
return res.send({task})
}
return res.send({message :"Something wrong in make-copy router"})
} )
/** change date time of copy of task in calendar view */
router.put("change-copy", authAuthorOrExecutorOfTask, async(req:Request, res: Response):Promise<Response>=>{
const {executorStatus,dateTimeTaskId,taskId, start, due} = req.body
if (executorStatus){
router.put("/change-copy", authAuthorOrExecutorOfTask, async(req:Request, res: Response):Promise<Response>=>{
const {dateTimeTaskId, taskId, dateTimeStart, dateTimeDue, description, title, priority} = req.body
const dateTimeTask = await dataSource
.createQueryBuilder()
.select('dateTikeTask')
.select('dateTimeTask')
.from(DateTimeTask,'dateTimeTask')
.where("dateTimeTask.id = :dateTimeTaskId",{dateTimeTaskId})
.getOne()
if(!dateTimeTask) return res.send({message:"such dateTimeTask does not exists"})
dateTimeTask.dateTimeStart=start
dateTimeTask.dateTimeDue=due
dateTimeTask.dateTimeStart=dateTimeStart
dateTimeTask.dateTimeDue=dateTimeDue
await dateTimeTask.save()
const task = taskFinderById(taskId)
const task = await taskFinderById(taskId)
if (!task) return res.status(404).send({Message:'task not found'})
task.title = title;
task.description = description;
task.priority = priority;
await task.save()
return res.send({task})
}
return res.send({message :"Something wrong in make-copy router"})
})
/**delete copyTask by dateTimeTaskId */
router.delete('/:id', authAuthorOrExecutorOfTask, async(req:Request, res:Response):Promise<Response>=>{
const {id} = req.params
await myDataSource
.createQueryBuilder()
.delete()
.from(DateTimeTask)
.where("id = :id", { id })
.execute()
return res.send({message:"delete succesfully"})
}
)
export default router;
import express,{Router, Request, Response} from 'express';
import {User} from '../models/User';
import {myDataSource} from '../app-data-source';
import { nanoid } from 'nanoid';
import { PasswordRecovery } from '../models/PasswordRecovery';
import { transporter } from '../helpers';
import {FRONTEND_URL} from "../helpers";
import {SALT_WORK_FACTOR} from "../models/User";
import bcrypt from 'bcrypt';
const router:Router = express.Router();
const dataSource = myDataSource;
/**Make requiest to init recovery process */
router.post ('/', async (req:Request, res:Response):Promise<void |Response>=>{
const {email} = req.body
const user = await dataSource
.getRepository(User)
.findOne({
where:{
email:email
}
})
if (!user) return res.status(404).send({message:'user not found'})
const token = nanoid();
try{
const passwordRecovery = new PasswordRecovery()
passwordRecovery.user= user;
passwordRecovery.token=token;
await passwordRecovery.save()
const url = `${FRONTEND_URL}/reset-password/${token}`;
await transporter.sendMail({
from:"planner45@yandex.com",
to: `${email}`,
subject:"Запрос на восстановление пароля",
text:`Вы отправили запрос на восстановление пароля,
перейдите по ссылке плз:{url}`,
html:`Вы отправили запрос на восстановление пароля,
перейдите по ссылке плз: <br><a> href="${url}">${url}</a>`});
return res.send({message:'Email successffuly send'})
} catch (e){
console.log(e)
res.status(502).send({message:'mail got stuck in ', e })
}
})
/**reset token in password recovery */
router.get('/', async(req: Request, res: Response):Promise<Response|void>=>{
const token = req.query.token;
if(!token) return res.status(401).send({Message:'token not exists'})
const passwordRecovery = await dataSource
.createQueryBuilder()
.from(PasswordRecovery,'passwordRecovery')
.select('passwordRecovery')
.innerJoinAndSelect('passwordRecovery.user', 'user')
.where(' passwordRecovery.token=:token',{token})
.getOne()
if(!passwordRecovery || !passwordRecovery.enabled) return res.status(404).send({message:"Token is not valid"})
res.send(passwordRecovery)
passwordRecovery.enabled=false;
try{
await passwordRecovery.save();
} catch(e){
console.log(e)
}
})
/**change password */
router.patch('/:id/change-password', async (req: Request, res: Response):Promise<Response|void>=>{
const user = await dataSource
.getRepository(User)
.findOneBy({id:req.params.id})
if(!user) return res.status(404).send({Message:'user not found'})
const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
let newPassword:string = await bcrypt.hash(req.body.password, salt);
user.password = newPassword
try{
await user.save()
res.send({message:"Password saved"})
} catch (e){
res.status(502).send({message:"error in saving new psasword"})
}
})
export default router;
\ No newline at end of file
......@@ -25,7 +25,7 @@ router.get('/', async(req:Request, res:Response):Promise<Response> => {
/**create new task */
router.post('/', auth, async(req:Request, res:Response):Promise<Response>=>{
const {user,title,description,project,executor, dateTimeDeadLine,priority} = req.body;
const {user,title,description,project,executor,dateTimeStart,dateTimeDue, dateTimeDeadLine,priority} = req.body;
const newTask = new Task();
newTask.title = title;
newTask.description = description;
......@@ -35,6 +35,11 @@ router.post('/', auth, async(req:Request, res:Response):Promise<Response>=>{
newTask.executor= executor;
newTask.priority = priority;
await newTask.save();
const newDateTimeTask = new DateTimeTask();
newDateTimeTask.dateTimeStart = dateTimeStart
newDateTimeTask.dateTimeDue = dateTimeDue
newDateTimeTask.task = newTask
await newDateTimeTask.save()
return res.send({newTask});
})
......@@ -153,7 +158,7 @@ router.delete('/:taskId',async (req: Request, res: Response):Promise<Response>=>
/**change of task by task id */
router.put('/',authAuthorOrExecutorOfTask,async(req:Request, res:Response)=> {
const {authorStatus,executorStatus,taskId,title,description,note, archive,project,dateTimeTaskId,start,due,executor,accomplish,dateTimeDeadLine, dateTimeFactDeadLine,priority} = req.body;
const {authorStatus,executorStatus,taskId,title,description,note, archive,project,dateTimeTaskId,dateTimeStart,dateTimeDue,executor,accomplish,dateTimeDeadLine, dateTimeFactDeadLine,priority} = req.body;
const task = await taskFinderById(taskId)
if (!task) return res.status(404).send({Message:'task not found'})
let dateTimeTask = null;
......@@ -168,6 +173,12 @@ router.put('/',authAuthorOrExecutorOfTask,async(req:Request, res:Response)=> {
dateTimeTask = dateTimeTaskData
}
if (dateTimeTask!==null) {
dateTimeTask.dateTimeStart = dateTimeStart
dateTimeTask.dateTimeDue = dateTimeDue
await dateTimeTask.save()
}
if (authorStatus){
task.title = title
task.description= description
......@@ -177,13 +188,9 @@ router.put('/',authAuthorOrExecutorOfTask,async(req:Request, res:Response)=> {
task.executor=executor
task.priority= priority
}
if(executorStatus && dateTimeTask!==null){
dateTimeTask.dateTimeStart = start
dateTimeTask.dateTimeDue = due
await dateTimeTask.save()
if(executorStatus){
task.note = note
task.dateTimeFactDeadLine= dateTimeFactDeadLine
}
task.accomplish= accomplish
await task.save()
......
......@@ -5,6 +5,7 @@ import tasks from './routers/tasks';
import projects from './routers/projects';
import {myDataSource} from './app-data-source';
import copyTasks from './routers/copyTasks';
import passwordRecovery from './routers/passwordRecovery';
myDataSource
......@@ -25,6 +26,7 @@ app.use('/users',users)
app.use('/tasks',tasks)
app.use('/copy-tasks',copyTasks)
app.use('/projects',projects)
app.use('/password-recovery',passwordRecovery)
const run = async() => {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -8,7 +8,7 @@ const CalendarStandartCell = ({children, xs, hours, dayNumber, createTaskInCell
const [isThisCell, setIsThisCell] = useState(false)
const cellClass = {
position: 'relative',
height: linesInDay?.length ? `${40*linesInDay.length+35}px` : `${35+35}px`,
height: linesInDay?.length ? `${40*linesInDay.length+35}px` : `${35}px`,
borderRight: '1px solid black',
}
useEffect(()=>{
......
import { Card, CardActions, CardContent, Grid, IconButton } from "@mui/material";
import { Link } from "react-router-dom";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import { useDispatch, useSelector } from "react-redux";
const ProjectMembersItem = ({ displayName, id }) => {
const dispatch = useDispatch();
const { projects, project } = useSelector(state => state.projects);
return <>
<Grid item xs={12} sm={12} md={6} lg={4}>
<Card>
<CardContent>
<strong>
<br></br>
{displayName}
</strong>
<strong>
<br></br>
роль: {project?.project?.members[0]?.roleProject}
</strong>
<strong>
<br></br>
<button>delete</button>
</strong>
</CardContent>
</Card>
</Grid>
</>
};
export default ProjectMembersItem;
import {Grid} from "@mui/material";
import ProjectMembersItem from "../ProjectMembersItem/ProjectMembersItem";
const ProjectMembersList = ({users}) => {
console.log(users)
return (
<Grid item container direction="row" spacing={1}>
{users?.map(user => {
return <ProjectMembersItem
displayName={user.displayName}
id={user.id}
key={user.id}
/>
})}
</Grid>
);
};
export default ProjectMembersList;
\ No newline at end of file
......@@ -4,16 +4,27 @@ import { useSelector, useDispatch } from "react-redux";
import { useEffect } from "react";
import { fetchProject } from "../../store/actions/projectsActions";
import ProjectTasksBody from "../../components/ProjectTasks/ProjectTasksBody";
import { fetchUsers } from "../../store/actions/usersActions";
import ProjectMembersList from "../../components/ProjectMembersList/ProjectMembersList";
const FullProject = () => {
const { projects, project } = useSelector(state => state.projects);
const users = useSelector(state => state.users.users);
const dispatch = useDispatch();
const params = useParams()
const tasks = project.tasks;
console.log(projects);
console.log(tasks);
useEffect(() => {
dispatch(fetchUsers())
}, [dispatch]);
console.log(users)
useEffect(() => {
dispatch(fetchProject(params.id))
}, [params.id, dispatch]);
......@@ -34,7 +45,17 @@ const FullProject = () => {
</strong>
<strong>
<br></br>
{/* Админ проекта: {project?.project?.members} */}
Автор проекта: {project?.project?.members[0]?.user.displayName}
</strong>
<strong>
<br></br>
Роль в проекте: {project?.project?.members[0]?.roleProject}
</strong>
<strong>
<br></br>
Участники проекта:
<ProjectMembersList users={users} project={project}/>
</strong>
<strong>
<br></br>
......
......@@ -3,11 +3,12 @@ import { useDispatch, useSelector } from 'react-redux';
import MonthCalendarBody from '../../components/MonthCalendarBody/MonthCalendarBody';
import MonthCalendarHeader from '../../components/MonthCalendarHeader/MonthCalendarHeader';
import { dateToISOLikeButLocal, getCurrentMonthString, getDaysInMonth } from '../../helpers/CalendarHelpers';
import { addCalendarTask, deleteCalendarTask, editCalendarTask, fetchCalendarTasks} from '../../store/actions/tasksActions';
import { addCalendarTask, addCopyCalendarTask, deleteCalendarTask, editCalendarTask, fetchCalendarTasks} from '../../store/actions/tasksActions';
function MonthCalendar() {
const dispatch = useDispatch();
const { calendarTasks } = useSelector(state => state.tasks);
const user = useSelector(state => state.users?.user);
const [hourFormat, setHourFormat] = useState(false);
const [dateNow, setDateNow] = useState({month: '', year: ''})
......@@ -65,7 +66,6 @@ function MonthCalendar() {
return {...prevState, month: prevState.month + 1}
})
}, [])
console.log(currentTask)
const decrementMonth = useCallback(() => {
setDateNow((prevState)=>{
if (prevState.month - 1 === -1) {
......@@ -135,13 +135,15 @@ function MonthCalendar() {
due = dateToISOLikeButLocal(new Date(dateNow.year, dateNow.month, dayNumber, hour + hourDiff, 59))
}
const start = dateToISOLikeButLocal(new Date(dateNow.year, dateNow.month, dayNumber, hour, 0))
const newObj = {
const newTask = {
...currentTask,
dateTimeTaskId: currentTask.id,
dateTimeStart: start,
dateTimeDue: due
}
delete newObj.infoForCell
await dispatch(editCalendarTask(newObj))
delete newTask.id
delete newTask.infoForCell
await dispatch(editCalendarTask(newTask))
setCurrentTask({})
}
......@@ -151,15 +153,26 @@ function MonthCalendar() {
const day = currentTask.infoForCell.startDay
const due = dateToISOLikeButLocal(new Date(dateNow.year, dateNow.month, day, timeEndHour - 1, 59))
const start = dateToISOLikeButLocal(new Date(dateNow.year, dateNow.month, day, timeStartHour, 0))
if (currentTask.id) {
const newTask = {
...currentTask,
dateTimeStart: start,
dateTimeDue: due
dateTimeDue: due,
dateTimeTaskId: currentTask.id,
taskId: currentTask.mainTaskId
}
delete newTask.infoForCell
if (currentTask.id) {
delete newTask.id
await dispatch(editCalendarTask(newTask))
} else {
const newTask = {
...currentTask,
dateTimeStart: start,
dateTimeDue: due,
executor: user,
}
delete newTask.infoForCell
delete newTask.id
await dispatch(addCalendarTask(newTask))
}
}
......@@ -178,10 +191,11 @@ function MonthCalendar() {
...copyTask,
dateTimeStart: start,
dateTimeDue: due,
taskId: copyTask.mainTaskId
}
delete newTask.infoForCell
delete newTask.id
await dispatch(addCalendarTask(newTask))
await dispatch(addCopyCalendarTask(newTask))
setCopyTask(null)
}
......
......@@ -9,12 +9,11 @@ const NewProject = () => {
const dispatch = useDispatch();
const projects = useSelector(state => state.projects.projects);
const navigate = useNavigate();
let lastProject = projects.projects[projects.projects.length - 1];
const onSubmit = async (projectData) => {
await dispatch(createProject(projectData, navigate));
console.log(projectData)
console.log(lastProject)
};
useEffect(()=> {
......
......@@ -11,8 +11,10 @@ import NewProject from "../NewProject/NewProject";
const Projects = () => {
const dispatch = useDispatch();
const { projects, loading } = useSelector(state => state.projects.projects);
const {users} = useSelector(state => state.users);
console.log(projects)
console.log(loading)
console.log(users)
useEffect(() => {
dispatch(fetchProjects())
}, [dispatch]);
......
export const getDaysInMonth = (dateNow) => {
if (dateNow.month <= 11 && dateNow.month >= 0) {
const newDaysInMonth = []
const daysInMonthNumber = new Date(dateNow.year, dateNow.month + 1, 0).getDate()
for (let i = 1; i <= daysInMonthNumber; i++) {
......@@ -7,16 +8,27 @@ export const getDaysInMonth = (dateNow) => {
newDaysInMonth.push({dayNumber: i, dayOfWeek: getDayOfWeekString})
}
return newDaysInMonth
} else {
return null
}
}
export const getCurrentMonthString = (dateNow) => {
if (dateNow.month <= 11 && dateNow.month >= 0) {
return ["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь", "Декабрь"][dateNow.month];
} else {
return null
}
}
export const dateToISOLikeButLocal = (date) => {
if (date instanceof Date && !isNaN(date)) {
const offsetMs = date.getTimezoneOffset() * 60 * 1000;
const msLocal = date.getTime() - offsetMs;
const dateLocal = new Date(msLocal);
const iso = dateLocal.toISOString();
return iso;
} else {
return null
}
}
\ No newline at end of file
......@@ -7,3 +7,7 @@ export const LOGIN_USER_FAILURE = "LOGIN_USER_FAILURE";
export const LOGOUT_USER_SUCCESS = "LOGOUT_USER_SUCCESS";
export const LOGOUT_USER_FAILURE = "LOGOUT_USER_FAILURE";
export const FETCH_USERS_REQUEST = "FETCH_USERS_REQUEST";
export const FETCH_USERS_SUCCESS = "FETCH_USERS_SUCCESS";
export const FETCH_USERS_FAILURE = "FETCH_USERS_FAILURE";
\ No newline at end of file
......@@ -71,10 +71,23 @@ const addTaskFailure = (error) => {
};
export const addCalendarTask = (task) => {
return async (dispatch, getState) => {
return async (dispatch) => {
dispatch(addTaskRequest());
try {
await axios.post("/tasks", task);
const response = await axios.post("/tasks", task);
dispatch(addTaskSuccess())
dispatch(fetchCalendarTasks())
} catch (error) {
dispatch(addTaskFailure(error.response.data));
}
}
}
export const addCopyCalendarTask = (task) => {
return async (dispatch) => {
dispatch(addTaskRequest());
try {
const response = await axios.post("/copy-tasks/make-copy", task);
dispatch(addTaskSuccess())
dispatch(fetchCalendarTasks())
} catch (error) {
......@@ -84,7 +97,7 @@ export const addCalendarTask = (task) => {
}
export const addTask = (task) => {
return async (dispatch, getState) => {
return async (dispatch) => {
dispatch(addTaskRequest());
try {
await axios.post("/tasks", task);
......@@ -109,7 +122,7 @@ const editTaskFailure = (error) => {
};
export const editTask = (task) => {
return async (dispatch, getState) => {
return async (dispatch) => {
dispatch(editTaskRequest());
try {
await axios.put("/tasks", task);
......@@ -122,10 +135,10 @@ export const editTask = (task) => {
}
export const editCalendarTask = (task) => {
return async (dispatch, getState) => {
return async (dispatch) => {
dispatch(editTaskRequest());
try {
await axios.put("/tasks", task);
await axios.put("/copy-tasks/change-copy", task);
dispatch(editTaskSuccess())
dispatch(fetchCalendarTasks())
} catch (error) {
......@@ -147,7 +160,7 @@ const deleteTaskFailure = (error) => {
};
export const deleteTask = (taskId) => {
return async (dispatch, getState) => {
return async (dispatch) => {
dispatch(deleteTaskRequest());
try {
await axios.delete(`/tasks/${taskId}`);
......@@ -160,10 +173,10 @@ export const deleteTask = (taskId) => {
}
export const deleteCalendarTask = (taskId) => {
return async (dispatch, getState) => {
return async (dispatch) => {
dispatch(deleteTaskRequest());
try {
await axios.delete(`/tasks/${taskId}`);
await axios.delete(`/copy-tasks/${taskId}`);
dispatch(deleteTaskSuccess())
dispatch(fetchCalendarTasks())
} catch (error) {
......
import axios from "../../axiosPlanner";
import { LOGIN_USER_FAILURE, LOGIN_USER_SUCCESS, LOGOUT_USER_FAILURE, LOGOUT_USER_SUCCESS, REGISTER_USER_FAILURE, REGISTER_USER_REQUEST, REGISTER_USER_SUCCESS } from "../actionTypes/userActionTypes"
import { FETCH_USERS_FAILURE, FETCH_USERS_REQUEST, FETCH_USERS_SUCCESS, LOGIN_USER_FAILURE, LOGIN_USER_SUCCESS, LOGOUT_USER_FAILURE, LOGOUT_USER_SUCCESS, REGISTER_USER_FAILURE, REGISTER_USER_REQUEST, REGISTER_USER_SUCCESS } from "../actionTypes/userActionTypes"
import { showNotification } from "./commonActions";
const registerUserRequest = () => {
......@@ -14,6 +14,16 @@ const registerUserFailure = (error) => {
return {type: REGISTER_USER_FAILURE, error}
};
const fetchUsersRequest = () => {
return {type: FETCH_USERS_REQUEST}
};
const fetchUsersSuccess = (users) => {
return {type: FETCH_USERS_SUCCESS, users}
};
const fetchUsersFailure = () => {
return {type: FETCH_USERS_FAILURE}
};
export const registerUser = (userData, navigate) => {
return async (dispatch) => {
dispatch(registerUserRequest());
......@@ -91,3 +101,16 @@ export const logoutUser = (navigate) => {
}
}
}
export const fetchUsers = () => {
return async dispatch => {
dispatch(fetchUsersRequest());
try {
const response = await axios.get("/users");
dispatch(fetchUsersSuccess(response.data.users));
console.log(response.data.users)
} catch (e) {
dispatch(fetchUsersFailure(e));
}
}
};
\ No newline at end of file
......@@ -29,8 +29,22 @@ const tasksReduсer = (state = initialState, action) => {
case FETCH_CALENDAR_TASKS_REQUEST:
return {...state, loading: true};
case FETCH_CALENDAR_TASKS_SUCCESS:
const newArr = []
action.tasks.forEach((task)=>{
const newTasksWithoutInfoForCell = []
const newTasksWithInfoForCell = []
for (let task of action.tasks) {
for (let copy of task.dateTimeTasks) {
newTasksWithoutInfoForCell.push({
...copy,
mainTaskId: task.id,
executor: task.executor,
author: task.author,
priority: task.priority,
title: task.title,
description: task.description
})
}
}
newTasksWithoutInfoForCell.forEach((task)=>{
if (task.dateTimeStart && task.dateTimeDue) {
if (new Date(task.dateTimeDue).getTime() - new Date(task.dateTimeStart).getTime() < (24 * 3600000) &&
new Date(task.dateTimeDue).getTime() - new Date(task.dateTimeStart).getTime() > 0) {
......@@ -55,11 +69,11 @@ const tasksReduсer = (state = initialState, action) => {
endMinute: timeEndMinute,
}
}
newArr.push(newObj)
newTasksWithInfoForCell.push(newObj)
}
}
})
return {...state, loading: false, calendarTasks: newArr};
return {...state, loading: false, calendarTasks: newTasksWithInfoForCell};
case FETCH_ALL_TASKS_SUCCESS:
return {...state, loading: false, tasks: action.tasks};
case FETCH_CALENDAR_TASKS_FAILURE:
......
import { REGISTER_USER_REQUEST, REGISTER_USER_SUCCESS, REGISTER_USER_FAILURE, LOGIN_USER_SUCCESS, LOGIN_USER_FAILURE, LOGOUT_USER_SUCCESS } from "../actionTypes/userActionTypes";
import { REGISTER_USER_REQUEST, REGISTER_USER_SUCCESS, REGISTER_USER_FAILURE, LOGIN_USER_SUCCESS, LOGIN_USER_FAILURE, LOGOUT_USER_SUCCESS, FETCH_USERS_SUCCESS } from "../actionTypes/userActionTypes";
const initialState = {
user: null,
users: [],
registerError: null,
loginError: null,
loading: false
......@@ -24,6 +25,8 @@ const usersReducer = (state = initialState, action) => {
return {...state, loginError: action.error};
case LOGOUT_USER_SUCCESS:
return {...state, user: null};
case FETCH_USERS_SUCCESS:
return {...state, loading: false, users: action.users};
default:
return state;
}
......
import {getDaysInMonth, getCurrentMonthString, dateToISOLikeButLocal} from '../helpers/CalendarHelpers';
import {getAvailableTasks, getSortedTasks} from '../components/MonthCalendarBody/CalendarRowDay/Helpers'
describe('Получение дней в феврале 2022', () => {
test('Всего дней', () => {
expect(getDaysInMonth({year:2022, month:1}).length).toBe(28);
})
test('Первый день', () => {
expect(getDaysInMonth({year:2022, month:1})[0]).toEqual({dayNumber: 1, dayOfWeek: 'ВТ'});
})
test('Последний день', () => {
expect(getDaysInMonth({year:2022, month:1})[27]).toEqual({dayNumber: 28, dayOfWeek: 'ПН'});
})
test('Неккоретное значение выше нормы', () => {
expect(getDaysInMonth({year:2022, month:12})).toBe(null);
})
test('Неккоретное значение ниже нормы', () => {
expect(getDaysInMonth({year:2022, month:-1})).toBe(null);
})
})
describe('Получение дней в ноябре 2022', () => {
test('Всего дней', () => {
expect(getDaysInMonth({year:2022, month:10}).length).toBe(30);
})
test('Первый день', () => {
expect(getDaysInMonth({year:2022, month:10})[0]).toEqual({dayNumber: 1, dayOfWeek: 'ВТ'});
})
test('Последний день', () => {
expect(getDaysInMonth({year:2022, month:10})[29]).toEqual({dayNumber: 30, dayOfWeek: 'СР'});
})
})
describe('Получение месяца', () => {
test('Первый месяц', () => {
expect(getCurrentMonthString({month:0})).toBe("Январь");
})
test('Последний месяц', () => {
expect(getCurrentMonthString({month:11})).toBe("Декабрь");
})
test('Неккоретное значение выше нормы', () => {
expect(getCurrentMonthString({month:12})).toBe(null);
})
test('Неккоретное значение ниже нормы', () => {
expect(getCurrentMonthString({month:-1})).toBe(null);
})
})
describe('Получение ISO даты', () => {
test('Валидная дата', () => {
expect(dateToISOLikeButLocal(new Date(2021, 11, 28, 5, 59))).toBe("2021-12-28T05:59:00.000Z");
})
test('Не валидная дата', () => {
expect(dateToISOLikeButLocal(new Date(2021, 'sd', 28, 5, 59))).toBe(null);
})
})
describe('Получение допустимых задач для клетки', () => {
test('Валидные задачи', () => {
expect(getAvailableTasks([{infoForCell: {startYear: 2022, startMonth: 12, startDay: 12}}, {infoForCell: {startYear: 2022, startMonth: 12, startDay: 12}}], 2022, 11, 12).length).toBe(2);
})
test('Не валидные задачи', () => {
expect(getAvailableTasks([{infoForCell: {startYear: 2021, startMonth: 11, startDay: 12}}, {infoForCell: {startYear: 2022, startMonth: 12, startDay: 14}}], 2022, 11, 12).length).toBe(0);
})
test('Не все прошедшие проверку задачи', () => {
expect(getAvailableTasks([{infoForCell: {startYear: 2021, startMonth: 11, startDay: 12}}, {infoForCell: {startYear: 2022, startMonth: 12, startDay: 12}}], 2022, 11, 12).length).toBe(1);
})
})
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment