Commit dde54eb9 authored by Ibadullina Inabat's avatar Ibadullina Inabat

Merge branch 'development' of…

Merge branch 'development' of ssh://git.attractor-school.com:30022/apollo64/crm-team-one into task-99-fix/add_members_correct
parents 06b02f32 4a12c42e
......@@ -3,6 +3,7 @@ import { myDataSource } from "./app-data-source";
import { Task } from "./models/Task";
import { User } from "./models/User";
import nodemailer from 'nodemailer';
import { Member } from "./models/Member";
const dataSource = myDataSource;
......@@ -103,6 +104,29 @@ export const authAuthorOrExecutorOfDateTimeTask = async(req: Request,res: Respon
}
/**check if user is admin of the project, receives userId and projectId*/
export const authAdminProject = async(req: Request,res: Response, next:NextFunction):Promise<void | express.Response<Response>>=>{
const token = req.get('Authorization');
const {projectId} = req.body;
const adminOfProject = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.leftJoinAndSelect("user.members","member")
.leftJoinAndSelect('member.project', 'project' )
.where("user.token = :token", { token })
.andWhere('project.id=:projectId',{projectId})
.andWhere('member.roleProject=:roleProject',{roleProject:'admin'})
.getOne()
if (!adminOfProject){
return res.send({message:'User is not authorized'})
}
req.body ={...req.body,adminStatus:true}
next()
}
/** return user if is in the given project, recieves userId adn projectId */
/**task finder by id, return one task */
......@@ -122,6 +146,30 @@ export const taskFinderById = async (taskId:string):Promise<null | Task>=>{
return task
}
/**member finder by userId and projectId, return one task */
export const memberFinderById = async (userId:string, projectId:string)=>{
const member = await dataSource
.getRepository(Member)
.findOne({
relations:{
user:true,
project:true,
},
where:{
user:{
id : userId
},
project:{
id:projectId
}
}
})
return member
}
export let transporter = nodemailer.createTransport({
......
......@@ -32,10 +32,10 @@ import {
@CreateDateColumn({ name: 'createdAt', type: Date, default: new Date() })
createdAt!: Date;
@ManyToOne(() => User, (user: { members: Member[]; }) => user.members,{cascade: true, onUpdate:'CASCADE',eager:true})
@ManyToOne(() => User, (user: { members: Member[]; }) => user.members,{cascade: true, onUpdate:'CASCADE', onDelete: 'CASCADE',eager:true})
user!: User;
@ManyToOne(() => Project, (project: { members: Member[]; }) => project.members,{cascade: true, onUpdate:'CASCADE',nullable:true})
@ManyToOne(() => Project, (project: { members: Member[]; }) => project.members,{cascade: true, onUpdate:'CASCADE', onDelete: 'CASCADE',nullable:true})
project!: Project;
@Column({
......
......@@ -46,7 +46,7 @@ import {
@OneToMany(() => Task, (task: { project: Project; })=>task.project,{nullable:true})
tasks!:Task[];
@OneToMany(() => Member, (member: { project: Project; })=>member.project)
@OneToMany(() => Member, (member: { project: Project; })=>member.project, {onDelete: 'CASCADE'})
members!:Member[];
}
\ No newline at end of file
......@@ -80,7 +80,7 @@ export class User extends BaseEntity implements IUser {
@OneToMany(() => Task, (task: { user: User }) =>task.user)
tasks!: Task[];
@OneToMany(() => Member, (member: { user: User }) => member.user)
@OneToMany(() => Member, (member: { user: User }) => member.user, {onDelete: 'CASCADE'})
members!: Member[];
......
import express,{Router, Request, Response} from 'express';
import {Project} from '../models/Project';
import {myDataSource} from '../app-data-source';
import { User } from '../models/User';
import { Member, MemberRole } from '../models/Member';
import { userInfo } from 'os';
import { Task } from '../models/Task';
import { auth } from '../helpers';
import { auth, authAdminProject } from '../helpers';
const router:Router = express.Router();
const dataSource = myDataSource;
......@@ -15,6 +12,7 @@ router.get('/',async (req:Request, res:Response): Promise<Response>=> {
const projects:Project[] = await dataSource.manager.find(Project)
return res.send({projects})
})
/**get projects were user is member, by user token */
router.get('/my',auth, async (req:Request, res:Response): Promise<Response>=> {
const user = req.body.user
......@@ -46,6 +44,9 @@ router.get('/my',auth, async (req:Request, res:Response): Promise<Response>=> {
return res.send({projects})
})
/**create new project */
router.post('/',auth, async (req:Request, res:Response): Promise<Response> => {
if (!req.body) return res.status(400).send({Message:'problem in incoming req.body'})
......@@ -91,7 +92,7 @@ router.get("/:id",async (req:Request, res:Response): Promise<Response> => {
})
/** Delete project by project ID*/
router.delete('/:projectId',async (req: Request, res: Response):Promise<Response>=>{
router.delete('/:projectId',authAdminProject,async (req: Request, res: Response):Promise<Response>=>{
const projectId = req.params.projectId;
await myDataSource
.createQueryBuilder()
......@@ -117,11 +118,14 @@ router.get('/user/:userId', async (req : Request, res : Response): Promise<Respo
})
/** Add user to specific project */
router.post('/add-user/', async (req: Request, res: Response):Promise<Response>=>{
router.post('/add-user/', authAdminProject, async (req: Request, res: Response):Promise<Response>=>{
const {userId, projectId, roleProject} = req.body;
console.log("req body" + req.body)
const newMember:Member = new Member();
try{
newMember.user= userId;
......@@ -136,38 +140,50 @@ router.post('/add-user/', async (req: Request, res: Response):Promise<Response>=
})
/** Remove user from specific project */
router.post('/remove-user', async (req: Request, res: Response):Promise<Response>=> {
const token = req.get('Authorization');
const {userId, projectId} = req.body;
const adminOfProject = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.leftJoinAndSelect("user.members","member")
.leftJoinAndSelect('member.project', 'project' )
.where("user.token = :token", { token })
.andWhere('project.id=:projectId',{projectId})
.andWhere('member.roleProject=:roleProject',{roleProject:'admin'})
.getOne()
if (!adminOfProject){
return res.send({message:'User is not authorized'})
}
/** Remove user from specific project by userId */
router.delete('/remove-user/:userId', authAdminProject,async (req: Request, res: Response):Promise<Response>=> {
const {projectId} = req.body;
const {userId }=req.params;
try{
await dataSource
.createQueryBuilder()
.delete()
.from(Member)
.where("user = :userId", { userId })
.where("user= :userId", { userId })
.andWhere("project=:projectId",{projectId})
.execute()
return res.send({message:"User removed from project successfully" })
} catch(e){
return res.send({message:'Failed to remove user from project'})
}
})
/**change rights of user inside of project by admin, recieve userId, new roleProject */
router.put('/change-project-role/:userId',authAdminProject, async (req: Request, res: Response):Promise<Response|void> =>{
const {userId}= req.params
const {projectId, newRoleProject} =req.body
const member = await dataSource
.createQueryBuilder()
.select("member")
.from(Member, "member")
.leftJoinAndSelect("member.user","user")
.leftJoinAndSelect('member.project', 'project' )
.where("user.id = :userId", { userId })
.andWhere("project.id=:projectId",{projectId})
.getOne()
if(!member) return res.status(404).send({Message:'user and project are not relevant'})
try{
member.roleProject = newRoleProject
await member.save()
} catch(e){
return res.send({message:"failed to change role"})
}
return res.send({message:"User's new role ", newRoleProject})
})
export default router;
......@@ -35,6 +35,22 @@ const users = await dataSource
return res.send({users})
})
//** return all users of DB */
router.get('/all-fields/', async (req : Request, res : Response):Promise<object> => {
const users = await dataSource
.getRepository(User)
.find({
relations:{
members:{
project:true,
user:true
},
}
})
return res.send({users})
})
/**create new user*/
router.post('/', upload.single("avatar"), async (req : Request, res : Response):Promise<object> => {
const {name,surname,password,email, role} = req.body;
......@@ -100,6 +116,7 @@ router.delete('/sessions', async(req: Request, res: Response):Promise<void | obj
if(!user) return res.send({successMsg});
user.token = nanoid();
await user.save();
return res.send(successMsg)
})
......
......@@ -10,7 +10,7 @@ import { useParams } from "react-router-dom";
import PersonAddIcon from '@mui/icons-material/PersonAdd';
const style = {
position: 'absolute',
position: 'relative',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
......
import {Button, Menu, MenuItem} from "@mui/material";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {NavLink, useNavigate} from "react-router-dom";
import { logoutUser } from "../../../store/actions/usersActions";
import {Button} from "@mui/material";
import {NavLink} from "react-router-dom";
import { superuserMenuButtons } from "../../../constants";
import ProfileBlock from "../ProfileBlock/ProfileBlock";
const AdminMenu = () => {
const dispatch = useDispatch();
const navigate = useNavigate()
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const user = useSelector(state => state.users.user)
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const logout = () => {
dispatch(logoutUser(navigate));
handleClose()
}
return <>
{superuserMenuButtons.map((button, i)=>{
return(
<Button
key={i}
component={NavLink}
to="/projects"
color="inherit"
size="large"
>
Проекты
</Button>
<Button
component={NavLink}
to="/week"
color="inherit"
size="large"
>
Неделя
</Button>
<Button
component={NavLink}
to="/month"
color="inherit"
size="large"
>
Месяц
</Button>
<Button
component={NavLink}
to="/my-tasks"
to={button.path}
color="inherit"
size="large"
>
Мои задачи
{button.text}
</Button>
<Button
component={NavLink}
to="/workers-tasks"
color="inherit"
size="large"
>
Задачи сотрудников
</Button>
<Button
component={NavLink}
to="/sign-up"
color="inherit"
size="large"
>
Создать сотрудника
</Button>
<Button
color="inherit"
onClick={handleClick}
>
Hello, {user?.displayName}
</Button>
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
<MenuItem component={NavLink} to="/profile/test" color="inherit" onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={logout}>Logout</MenuItem>
</Menu>
)
})}
<ProfileBlock/>
</>
};
......
import {Button} from "@mui/material";
import {NavLink} from "react-router-dom";
import { anonymoysMenuButtons } from "../../../constants";
const AnonymousMenu = () => {
return <>
{anonymoysMenuButtons.map((button, i)=>{
return(
<Button
key={i}
component={NavLink}
to={button.path}
color="inherit"
to="/sign-in"
size="large"
>
Вход
{button.text}
</Button>
)
})}
</>
};
......
import {Button, Menu, MenuItem} from "@mui/material";
import { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import {NavLink, useNavigate} from "react-router-dom";
import { fetchUsersAllFields, fetchUsers, logoutUser } from "../../../store/actions/usersActions";
import SwitchUserModal from "../../SwitchUserModal/SwitchUserModal";
const ProfileBlock = () => {
const dispatch = useDispatch();
const navigate = useNavigate()
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const user = useSelector(state => state.users.user)
const [openSwitchUser, setOpenSwitchUser] = useState(false);
const handleCloseSwitchUser = () => setOpenSwitchUser(false);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleOpenSwitchUser = () => setOpenSwitchUser(true);
const logout = () => {
dispatch(logoutUser(navigate));
handleClose()
}
const switchUser=()=>{
dispatch(fetchUsersAllFields());
// dispatch(fetchUsers())
handleClose()
handleOpenSwitchUser()
}
return <>
<Button
color="inherit"
onClick={handleClick}
id='test_greetings'
>
Hello, {user?.displayName}
</Button>
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
<MenuItem component={NavLink} to="/profile/test" color="inherit" onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={()=>{switchUser()}}>Поменять юзера</MenuItem>
<MenuItem onClick={()=>{logout()}}>Logout</MenuItem>
</Menu>
<SwitchUserModal
open={openSwitchUser}
handleClose={handleCloseSwitchUser}
/>
</>
};
export default ProfileBlock;
\ No newline at end of file
import {Button, Menu, MenuItem} from "@mui/material";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {NavLink, useNavigate} from "react-router-dom";
import { logoutUser } from "../../../store/actions/usersActions";
import {Button} from "@mui/material";
import {NavLink} from "react-router-dom";
import { workerMenuButtons } from "../../../constants";
import ProfileBlock from "../ProfileBlock/ProfileBlock";
const WorkerMenu = () => {
const dispatch = useDispatch();
const navigate = useNavigate()
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const user = useSelector(state => state.users.user)
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const logout = () => {
dispatch(logoutUser(navigate));
handleClose()
}
return <>
{workerMenuButtons.map((button, i)=>{
return(
<Button
key={i}
component={NavLink}
to="/projects"
color="inherit"
size="large"
>
Проекты
</Button>
<Button
component={NavLink}
to="/week"
color="inherit"
size="large"
>
Неделя
</Button>
<Button
component={NavLink}
to="/month"
color="inherit"
size="large"
>
Месяц
</Button>
<Button
component={NavLink}
to="/my-tasks"
to={button.path}
color="inherit"
size="large"
>
Мои задачи
</Button>
<Button
color="inherit"
onClick={handleClick}
>
Hello, {user?.displayName}
{button.text}
</Button>
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
)
})}
>
<MenuItem component={NavLink} to="/profile/test" color="inherit" onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={logout}>Logout</MenuItem>
</Menu>
<ProfileBlock/>
</>
};
......
......@@ -5,7 +5,7 @@ const CalendarRow = ({children, week}) => {
<Grid
container
align='center'
sx={{borderBottom: week ? null : '1px solid black', borderRight: '1px solid black', borderLeft: '1px solid black'}}
sx={{borderBottom: week ? null : '1px solid black', borderLeft: '1px solid black'}}
>
{children}
</Grid>
......
......@@ -34,14 +34,15 @@ const EmptyBox = ({hourNumber, handleOpen, dayNumber, xs, dragTaskHandler, modal
onDragOver={(e)=>{dragOverHandler(e)}}
onDrop={(e)=>{dropHandler(e)}}
onClick={(e)=>{onClickHandler(e, dayNumber, hourNumber)}}
className='test_empty_box'
item xs={xs} sx={{
height: '35px',
height: '40px',
backgroundColor: 'rgb(0,0,0,0)',
zIndex: '6',
cursor: copyTask ? 'pointer' : 'default'
}}>
{isThisCell ?
<DefaultTask/> : null}
<DefaultTask/> : ' '}
</Grid>)
};
......
......@@ -14,6 +14,7 @@ function MonthAndYearInfo({currentMonthString, year, incrementMonth, decrementMo
alignItems: 'center',
gap: '10px'
}}
id='test_month_info'
>
<ArrowDecrementButton
onClick={decrementMonth}
......
......@@ -56,7 +56,7 @@ function MonthCalendarModalContent({title, onChangeCurrentTaskHandler, descripti
/>
</div>
<div style={{display: 'flex', gap: '20px', margin: '10px 0'}}>
<Button onClick={sendNewTaskHandler}>Сохранить</Button>
<Button id='test_button_save_task'onClick={sendNewTaskHandler}>Сохранить</Button>
<Button onClick={deleteTaskHandler}>Удалить</Button>
</div>
</>);
......
import {Box, Button, Grid, Modal} from "@mui/material";
import {useState} from "react";
import { useSelector } from "react-redux";
import FormElement from "../UI/Form/FormElement/FormElement";
import FormElement from "../../UI/Form/FormElement/FormElement";
import {Typography} from "@mui/material";
const style = {
......
......@@ -10,11 +10,11 @@ import {
Paper
} from "@mui/material";
import { useState } from "react";
import TaskModal from "../../components/MyTasksCompoments/TaskModal/TaskModal";
import TaskModal from "../../../components/MyTasksCompoments/TaskModal/TaskModal";
import moment from "moment";
import CustomTableCell from "../../components/MyTasksCompoments/CustomTableCell";
import MaterialUIPickers from "../../components/MyTasksCompoments/DateTimePicker/DateTimePicker";
import BasicSelect from "../../components/UI/Select/Select";
import CustomTableCell from "../../../components/MyTasksCompoments/CustomTableCell";
import MaterialUIPickers from "../../../components/MyTasksCompoments/DateTimePicker/DateTimePicker";
import BasicSelect from "../../../components/UI/Select/Select";
import ProjectTasksHeader from "./ProjectTasksHeader";
export default function ProjectTasksBody({ tasks }) {
......
import React,{useEffect, useState} from 'react';
import { Button, Grid ,Typography,Autocomplete, TextField} from "@mui/material";
import SwitchUserTable from '../SwitchUserTable/SwitchUserTable';
const SwitchUserForm=({submitFormHandler, users})=> {
const [currentUser, setCurrentUser] = useState(null);
const [projectsRoles, setProjectsRole] = useState([])
const onChangeHandle =(user)=>{
setCurrentUser(user)
setProjectsRole([])
}
useEffect(()=>{
if(currentUser?.members.length>0) {
for (let member of currentUser.members ) {
const newProjectRole = {
"projectName":member.project.title,
"roleName":member.roleProject
}
setProjectsRole([...projectsRoles, newProjectRole ])
}
}
},[ currentUser])
return (
<>
{/* <Box sx={style}> */}
{users?
<form onSubmit={(e)=>submitFormHandler(e,currentUser)}>
<Grid container direction="column" spacing={2}>
<Typography variant="h5" style={{margin: "5px", textAlign: "center"}} >Выберите юзера</Typography>
<Autocomplete
options={users}
onChange={(event,user)=>onChangeHandle(user) }
getOptionLabel={(option) => option.displayName}
renderInput={(params) => <TextField
style={{margin: "5px"}}
label={"Юзер"}
{...params} />}
/>
{projectsRoles.length>0?
<Grid item>
<SwitchUserTable projectsRoles={projectsRoles}/>
</Grid>
:<></>}
<Grid container direction="row" spacing={2}>
<Grid item>
<Button
type="submit"
color="primary"
variant="contained"
sx={{margin:1}}
>
ОК
</Button>
</Grid>
{/* <Grid item>
<Button
onClick={()=>console.log('cancel form')}
color="primary"
variant="contained"
>
Отмена
</Button>
</Grid> */}
</Grid>
</Grid>
</form>
: <></>}
{/* </Box> */}
</>
)
}
export default SwitchUserForm;
import React from 'react';
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
import { useDispatch, useSelector } from "react-redux";
import { logoutUser, loginUser } from '../../store/actions/usersActions';
import SwitchUserForm from './SwitchUserForm/SwitchUserForm';
import { useNavigate } from "react-router-dom";
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 'fit-content',
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
const SwitchUserModal= ({open, handleClose})=> {
const dispatch= useDispatch();
const usersAllFields = useSelector(state => state.users.usersAllFields)
const navigate= useNavigate();
const submitFormHandler=(e, currentUser)=>{
dispatch(logoutUser(navigate))
e.preventDefault();
dispatch(loginUser({
"email": currentUser.email,
"password": "123"
}, navigate))
}
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<SwitchUserForm
submitFormHandler = {submitFormHandler}
users = {usersAllFields}
/>
</Box>
</Modal>
</div>
);
}
export default SwitchUserModal;
import * as React from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
const SwitchUserTable=({projectsRoles})=>{
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 350 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell
sx ={{
'backgroundColor':'black',
'color': 'white'
}}
>Проекты</TableCell>
<TableCell
sx ={{
'backgroundColor':'black',
'color': 'white'
}} align="right"
>Роли</TableCell>
</TableRow>
</TableHead>
<TableBody>
{projectsRoles.map((projectRole,index) => (
<TableRow
key={index}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{projectRole.projectName}
</TableCell>
<TableCell align="right">{projectRole.roleName}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)
}
export default SwitchUserTable ;
......@@ -56,6 +56,7 @@ export default function ModalTask({modal, handleClose, children}) {
return (
<>
<Modal
id='test_modal_task'
open={modal.open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
......
......@@ -25,6 +25,7 @@ const UserForm = ({ state, onChange, onSubmit, getFieldError, buttonText, resetP
sx={{ mt: "15px" }}
type="submit"
fullWidth
id='test_login'
variant="contained"
color="primary"
>
......
import { Grid } from "@mui/material";
import CalendarRow from "../../../MonthCalendar/MonthCalendarBody/CalendarRow/CalendarRow";
import CalendarStandartCell from "../../../MonthCalendar/MonthCalendarBody/CalendarStandartCell.js/CalendarStandartCell";
function CalendarRowDayWeek({week, hoursInDay}) {
return ( <>
<Grid item xs={11.005}>
<CalendarRow week={true}>
{week?.map((weekDay, i)=>{
return (
<Grid item key={i} xs={12/week.length}>
{hoursInDay?.map((hour, i)=>{
return (
<CalendarStandartCell key={i} week={true}>
</CalendarStandartCell>)
})}
</Grid>)
})}
</CalendarRow>
</Grid>
</> );
}
export default CalendarRowDayWeek;
\ No newline at end of file
......@@ -3,9 +3,10 @@ import { Box } from "@mui/system";
import CalendarRow from "../../MonthCalendar/MonthCalendarBody/CalendarRow/CalendarRow";
import CalendarSmallCell from "../../MonthCalendar/MonthCalendarBody/CalendarSmallCell/CalendarSmallCell";
import CalendarStandartCell from "../../MonthCalendar/MonthCalendarBody/CalendarStandartCell.js/CalendarStandartCell";
import CalendarRowDayWeek from "./CalendarRowDayWeek/CalendarRowDayWeek";
import { getCurrentWeekDayString } from "./Helpers";
function WeekCalendarBody({week, hoursInDay, hourFormat, setHourFormat}) {
function WeekCalendarBody({week, hoursInDay, hourFormat, setHourFormat,}) {
return (
<>
......@@ -42,21 +43,11 @@ function WeekCalendarBody({week, hoursInDay, hourFormat, setHourFormat}) {
})}
</Grid>
<Grid item xs={11.005}>
<CalendarRow week={true}>
{week?.map((weekDay, i)=>{
return (
<Grid item key={i} xs={12/week.length}>
{hoursInDay?.map((hour, i)=>{
return (
<CalendarStandartCell key={i} week={true}>
<CalendarRowDayWeek
week={week}
hoursInDay={hoursInDay}
/>
</CalendarStandartCell>)
})}
</Grid>)
})}
</CalendarRow>
</Grid>
</CalendarRow>
</Grid>
</Box>
......
export const apiUrl = "http://localhost:8000";
export const uploadsUrl = `${apiUrl}/uploads`;
export const workerMenuButtons = [
{text: 'Проекты', path: '/projects'},
{text: 'Неделя', path: '/week'},
{text: 'Месяц', path: '/month'},
{text: 'Мои задачи', path: '/my-tasks'}
]
export const superuserMenuButtons = [
{text: 'Проекты', path: '/projects'},
{text: 'Неделя', path: '/week'},
{text: 'Месяц', path: '/month'},
{text: 'Мои задачи', path: '/my-tasks'},
{text: 'Задачи Сотрудников', path: '/workers-tasks'},
{text: 'Создать Сотрудника', path: '/sign-up'}
]
export const anonymoysMenuButtons = [
{text: 'Вход', path: '/sign-in'},
]
\ No newline at end of file
......@@ -3,9 +3,9 @@ import { useParams } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { useEffect } from "react";
import { fetchProject } from "../../store/actions/projectsActions";
import ProjectTasksBody from "../../components/ProjectTasks/ProjectTasksBody";
import ProjectTasksBody from "../../components/ProjectComponents/ProjectTasks/ProjectTasksBody";
import { fetchUsers } from "../../store/actions/usersActions";
import ProjectMembersList from "../../components/ProjectMembersList/ProjectMembersList";
import ProjectMembersList from "../../components/ProjectComponents/ProjectMembersList/ProjectMembersList";
import NewMember from "../NewMember/NewMember";
const FullProject = () => {
......
......@@ -158,18 +158,17 @@ function MonthCalendar() {
...currentTask,
dateTimeStart: start,
dateTimeDue: due,
dateTimeTaskId: currentTask.id,
taskId: currentTask.mainTaskId
}
delete newTask.infoForCell
delete newTask.id
await dispatch(editCalendarTask(newTask))
await dispatch(editCalendarTask(newTask, currentTask.id))
} else {
const newTask = {
...currentTask,
dateTimeStart: start,
dateTimeDue: due,
executor: user,
dateTimeDeadLine: due,
}
delete newTask.infoForCell
delete newTask.id
......
......@@ -2,7 +2,7 @@ import {useNavigate} from "react-router-dom";
import {useDispatch, useSelector} from "react-redux";
import { useEffect } from "react";
import ProjectForm from "../../components/ProjectForm/ProjectForm";
import ProjectForm from "../../components/ProjectComponents/ProjectForm/ProjectForm";
import { createMember, createProject, fetchMembers, fetchProjects } from "../../store/actions/projectsActions";
import MemberForm from "../../components/MemberForm/MemberForm";
......
......@@ -2,7 +2,7 @@ import {useNavigate} from "react-router-dom";
import {useDispatch, useSelector} from "react-redux";
import { useEffect } from "react";
import ProjectForm from "../../components/ProjectForm/ProjectForm";
import ProjectForm from "../../components/ProjectComponents/ProjectForm/ProjectForm";
import { createProject, fetchProjects } from "../../store/actions/projectsActions";
const NewProject = () => {
......
......@@ -5,7 +5,7 @@ import { useDispatch, useSelector } from "react-redux";
import Loader from "../../components/UI/Loader/Loader";
import HasAccess from "../../components/UI/HasAccess/HasAccess";
import { fetchProjects } from "../../store/actions/projectsActions";
import ProjectsList from "../../components/ProjectsList/ProjectsList";
import ProjectsList from "../../components/ProjectComponents/ProjectsList/ProjectsList";
import NewProject from "../NewProject/NewProject";
const Projects = () => {
......
......@@ -58,10 +58,17 @@ export const getWeekFromCurrentDate = (year, month, curDay) => {
export const getWeekInfoString = (week, date) => {
if (week[0] > week[6]) {
if (date.currentDay < week[0]) {
if (date.month === 0) {
return getCurrentMonthString(11) + ' - ' + getCurrentMonthString(0) + ' ' + date.year
}
return getCurrentMonthString(date.month - 1) + ' - ' + getCurrentMonthString(date.month) + ' ' + date.year
} else {
if (date.month === 11) {
return getCurrentMonthString(date.month) + ' - ' + getCurrentMonthString(0) + ' ' + date.year
}
return getCurrentMonthString(date.month) + ' - ' + getCurrentMonthString(date.month + 1) + ' ' + date.year
}
} else {
return getCurrentMonthString(date.month) + ' ' + date.year
}
......
......@@ -11,3 +11,7 @@ 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";
export const FETCH_USERS_ALL_FIELDS_REQUEST = "FETCH_USERS_ALL_FIELDS_REQUEST";
export const FETCH_USERS_ALL_FIELDS_SUCCESS = "FETCH_USERS_ALL_FIELDS_SUCCESS";
export const FETCH_USERS_ALL_FIELDS_FAILURE = "FETCH_USERS_ALL_FIELDS_FAILURE";
\ No newline at end of file
......@@ -77,7 +77,7 @@ export const addCalendarTask = (task) => {
return async (dispatch) => {
dispatch(addTaskRequest());
try {
const response = await axios.post("/tasks", task);
await axios.post("/tasks", task);
dispatch(addTaskSuccess())
dispatch(fetchCalendarTasks())
} catch (error) {
......@@ -90,7 +90,7 @@ export const addCopyCalendarTask = (task) => {
return async (dispatch) => {
dispatch(addTaskRequest());
try {
const response = await axios.post("/copy-tasks/make-copy", task);
await axios.post("/copy-tasks/make-copy", task);
dispatch(addTaskSuccess())
dispatch(fetchCalendarTasks())
} catch (error) {
......@@ -137,11 +137,11 @@ export const editTask = (task) => {
}
}
export const editCalendarTask = (task) => {
export const editCalendarTask = (task, taskId) => {
return async (dispatch) => {
dispatch(editTaskRequest());
try {
await axios.put("/copy-tasks/change-copy", task);
await axios.put(`/copy-tasks/change-copy/${taskId}`, task);
dispatch(editTaskSuccess())
dispatch(fetchCalendarTasks())
} catch (error) {
......
import axios from "../../axiosPlanner";
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 { 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,FETCH_USERS_ALL_FIELDS_REQUEST,
FETCH_USERS_ALL_FIELDS_SUCCESS,
FETCH_USERS_ALL_FIELDS_FAILURE, } from "../actionTypes/userActionTypes"
import { showNotification } from "./commonActions";
const registerUserRequest = () => {
......@@ -24,6 +26,16 @@ const fetchUsersFailure = () => {
return {type: FETCH_USERS_FAILURE}
};
const fetchUsersAllFieldsRequest = () => {
return {type: FETCH_USERS_ALL_FIELDS_REQUEST}
};
const fetchUsersAllFieldsSuccess = (users) => {
return {type: FETCH_USERS_ALL_FIELDS_SUCCESS, users}
};
const fetchUsersAllFieldsFailure = () => {
return {type: FETCH_USERS_ALL_FIELDS_FAILURE}
};
export const registerUser = (userData, navigate) => {
return async (dispatch) => {
dispatch(registerUserRequest());
......@@ -71,7 +83,7 @@ export const loginUser = (userData, navigate) => {
export const forgottenPassword = (userData, navigate) => {
return async (dispatch) => {
try {
console.log(userData)
console.log( 'forgottenPassword userData ',userData)
const response = await axios.post("users/requestPasswordReset", userData);
// if (userData.email === response.data.email) {
......@@ -85,13 +97,9 @@ export const forgottenPassword = (userData, navigate) => {
}
export const logoutUser = (navigate) => {
return async (dispatch, getState) => {
return async (dispatch) => {
try {
await axios.delete("/users/sessions", {
headers: {
'Authorization': getState().users.user?.token
}
});
await axios.delete("/users/sessions");
dispatch(logoutUserSuccess());
navigate("/");
dispatch(showNotification("Вы успешно вышли"));
......@@ -114,3 +122,17 @@ export const fetchUsers = () => {
}
}
};
export const fetchUsersAllFields = () => {
return async dispatch => {
dispatch(fetchUsersAllFieldsRequest());
try {
const response = await axios.get("/users/all-fields/");
dispatch(fetchUsersAllFieldsSuccess(response.data.users));
} catch (e) {
dispatch(fetchUsersAllFieldsFailure(e));
}
}
};
......@@ -12,7 +12,6 @@ import {
DELETE_TASK_REQUEST,
DELETE_TASK_FAILURE,
FETCH_ALL_TASKS_SUCCESS,
EDIT_CALENDAR_TASK,
FETCH_TASKS_BY_PROJECT_REQUEST,
FETCH_TASKS_BY_PROJECT_SUCCESS,
FETCH_TASKS_BY_PROJECT_FAILURE,
......
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";
import { REGISTER_USER_REQUEST, REGISTER_USER_SUCCESS, REGISTER_USER_FAILURE, LOGIN_USER_SUCCESS, LOGIN_USER_FAILURE, LOGOUT_USER_SUCCESS, FETCH_USERS_SUCCESS,FETCH_USERS_ALL_FIELDS_SUCCESS } from "../actionTypes/userActionTypes";
const initialState = {
user: null,
users: [],
usersAllFields:[],
registerError: null,
loginError: null,
loading: false
......@@ -27,6 +28,8 @@ const usersReducer = (state = initialState, action) => {
return {...state, user: null};
case FETCH_USERS_SUCCESS:
return {...state, loading: false, users: action.users};
case FETCH_USERS_ALL_FIELDS_SUCCESS:
return {...state, loading: false, usersAllFields: action.users};
default:
return state;
}
......
import {getDaysInMonth, getCurrentMonthString, dateToISOLikeButLocal} from '../helpers/CalendarHelpers';
import {getAvailableTasks, getSortedTasks} from '../components/MonthCalendarBody/CalendarRowDay/Helpers'
import {getAvailableTasks, getSortedTasks} from '../components/MonthCalendar/MonthCalendarBody/CalendarRowDay/Helpers'
describe('Получение дней в феврале 2022', () => {
test('Всего дней', () => {
......@@ -33,16 +33,16 @@ describe('Получение дней в ноябре 2022', () => {
describe('Получение месяца', () => {
test('Первый месяц', () => {
expect(getCurrentMonthString({month:0})).toBe("Январь");
expect(getCurrentMonthString(0)).toBe("Январь");
})
test('Последний месяц', () => {
expect(getCurrentMonthString({month:11})).toBe("Декабрь");
expect(getCurrentMonthString(11)).toBe("Декабрь");
})
test('Неккоретное значение выше нормы', () => {
expect(getCurrentMonthString({month:12})).toBe(null);
expect(getCurrentMonthString(12)).toBe(null);
})
test('Неккоретное значение ниже нормы', () => {
expect(getCurrentMonthString({month:-1})).toBe(null);
expect(getCurrentMonthString(-1)).toBe(null);
})
})
......@@ -66,3 +66,60 @@ describe('Получение допустимых задач для клетки
expect(getAvailableTasks([{infoForCell: {startYear: 2021, startMonth: 11, startDay: 12}}, {infoForCell: {startYear: 2022, startMonth: 12, startDay: 12}}], 2022, 11, 12).length).toBe(1);
})
})
describe('Получение сортированных задач', () => {
test('Сортировка прошла успешно Вариант№1', () => {
const tasks = [
{
infoForCell: {
startHour: 12,
endHour: 14
}
},
{
infoForCell: {
startHour: 13,
endHour: 14
}
}]
expect(getSortedTasks(tasks)).toEqual(tasks)
})
test('Сортировка прошла успешно Вариант№2', () => {
const tasks = [
{
infoForCell: {
startHour: 12,
endHour: 14
}
},
{
infoForCell: {
startHour: 10,
endHour: 14
}
}]
expect(getSortedTasks(tasks)).toEqual(tasks.reverse())
})
test('Сортировка прошла успешно Вариант№3', () => {
const tasks = [
{
infoForCell: {
startHour: 12,
endHour: 14
}
},
{
infoForCell: {
startHour: 10,
endHour: 14
}
},
{
infoForCell: {
startHour: 11,
endHour: 12
}
}]
expect(getSortedTasks(tasks)).toEqual([tasks[1], tasks[0], tasks[2]])
})
})
\ No newline at end of file
import { getCurrentWeekDayString } from '../components/WeekCalendar/WeekCalendarBody/Helpers';
import {getWeekFromCurrentDate, getWeekInfoString} from '../helpers/CalendarHelpers';
describe('Получение недели в 7дневном формате', () => {
test('Получение недели 10 декабря 2022', () => {
expect(getWeekFromCurrentDate(2022, 11, 10)).toEqual([5,6,7,8,9,10,11]);
})
test('Получение недели 1 декабря 2022', () => {
expect(getWeekFromCurrentDate(2022, 11, 1)).toEqual([28,29,30,1,2,3,4]);
})
test('Получение недели 1 октября 2023', () => {
expect(getWeekFromCurrentDate(2023, 9, 1)).toEqual([25,26,27,28,29,30,1]);
})
})
describe('Получение информации о неделе', () => {
test('Получение информации о недели 10 декабря 2022', () => {
const date = {month: 11, year: 2022, currentDay: 10}
const week = [5,6,7,8,9,10,11]
expect(getWeekInfoString(week, date)).toBe("Декабрь 2022");
})
test('Получение информации о недели 1 декабря 2022', () => {
const date = {month: 11, year: 2022, currentDay: 1}
const week = [28,29,30,1,2,3,4]
expect(getWeekInfoString(week, date)).toBe("Ноябрь - Декабрь 2022");
})
test('Получение информации о недели 30 ноября 2022', () => {
const date = {month: 10, year: 2022, currentDay: 30}
const week = [28,29,30,1,2,3,4]
expect(getWeekInfoString(week, date)).toBe("Ноябрь - Декабрь 2022");
})
test('Получение информации о недели 31 декабря 2022', () => {
const date = {month: 11, year: 2022, currentDay: 31}
const week = [26,27,28,29,30,31,1]
expect(getWeekInfoString(week, date)).toBe("Декабрь - Январь 2022");
})
test('Получение информации о недели 1 января 2023', () => {
const date = {month: 0, year: 2023, currentDay: 1}
const week = [26,27,28,29,30,31,1]
expect(getWeekInfoString(week, date)).toBe("Декабрь - Январь 2023");
})
test('Получение информации о недели 31 января 2023', () => {
const date = {month: 0, year: 2023, currentDay: 31}
const week = [30,31,1,2,3,4,5]
expect(getWeekInfoString(week, date)).toBe("Январь - Февраль 2023");
})
})
describe('Получение дня недели', () => {
test('Понедельник', () => {
expect(getCurrentWeekDayString(0)).toBe('ПН');
})
test('Четверг', () => {
expect(getCurrentWeekDayString(3)).toBe('ЧТ');
})
test('Воскресенье', () => {
expect(getCurrentWeekDayString(6)).toBe('ВС');
})
test('Неккоретное значение выше нормы', () => {
expect(getCurrentWeekDayString(7)).toBe(null);
})
test('Неккоретное значение ниже нормы', () => {
expect(getCurrentWeekDayString(-1)).toBe(null);
})
})
node_modules
.idea
output
exports.config = {
output: './output',
helpers: {
Puppeteer: {
url: 'http://localhost:3000',
show: true,
windowSize: '1200x900'
}
},
include: {
I: './steps_file.js'
},
mocha: {},
bootstrap: null,
timeout: null,
teardown: null,
hooks: [],
gherkin: {
features: './features/*.feature',
steps: [
'./step_definitions/steps.js',
'./step_definitions/create_task_steps.js']
},
plugins: {
screenshotOnFail: {
enabled: true
},
tryTo: {
enabled: true
},
retryFailedStep: {
enabled: false
},
retryTo: {
enabled: true
},
eachElement: {
enabled: true
},
pauseOnFail: {}
},
stepTimeout: 0,
stepTimeoutOverride: [{
pattern: 'wait.*',
timeout: 0
},
{
pattern: 'amOnPage',
timeout: 0
}
],
tests: './step_definitions/*_steps.js',
name: 'planner-tests',
translation: 'ru-RU'
}
\ No newline at end of file
#language: ru
Функция: Создание задачи
Сценарий: Успешное создание задачи
Допустим я нахожусь на странице "/"
То я нажимаю на кнопку '#test_month_header'
И я вижу элемент "#test_month_info"
Если я нажимаю на элемент ".test_empty_box"
И я вижу элемент "#test_modal_task"
Если я ввожу в поле "title" текст "test tasklasdlasdl"
И я нажимаю на кнопку '#test_button_save_task'
Тогда я вижу элемент с текстом "Задачаtest tasklasdlasdl"
\ No newline at end of file
{
"compilerOptions": {
"allowJs": true
}
}
\ No newline at end of file
This diff is collapsed.
{
"name": "planner-tests",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npx codeceptjs run --steps"
},
"author": "",
"license": "ISC",
"dependencies": {
"codeceptjs": "^3.3.7",
"puppeteer": "^19.3.0"
}
}
const { I } = inject();
Given("я нахожусь на странице {string}", (page) => {
I.login('a@a.a0', '123')
I.amOnPage(page)
});
Then('я нажимаю на кнопку {string}', (buttonId) => {
I.click(buttonId)
});
When('я вижу элемент {string}', (infoString) => {
I.waitForElement(infoString, 5)
});
When('я нажимаю на элемент {string}', (className) => {
I.click(className)
});
When('я ввожу в поле {string} текст {string}', (field, title) => {
I.fillField(field, title)
});
When('я нажимаю на кнопку {string}', (buttonId) => {
I.click(buttonId)
I.wait(2)
});
Then('я вижу элемент с текстом {string}', (text) => {
I.see(text)
I.wait(2)
});
/// <reference types='codeceptjs' />
type steps_file = typeof import('./steps_file.js');
declare namespace CodeceptJS {
interface SupportObject { I: I, current: any, Я: Я }
interface Methods extends Puppeteer {}
interface I extends ReturnType<steps_file> {}
interface Я extends WithTranslation<Methods> {}
namespace Translation {
interface Actions {
"say": "сообщаю",
"waitForElement": "ожидаю_элемент",
"waitForVisible": "ожидаю_увидеть",
"waitForText": "ожидаю_текст",
"amOnPage": "на_странице",
"click": "кликаю",
"doubleClick": "дважды_кликаю",
"see": "вижу",
"dontSee": "не_вижу",
"selectOption": "выбираю_опцию",
"fillField": "заполняю_поле",
"pressKey": "нажимаю_кнопку",
"triggerMouseEvent": "триггерное_событие_мыши",
"attachFile": "загружаю_файл",
"seeInField": "вижу_в_поле",
"dontSeeInField": "не_вижу_в_поле",
"appendField": "дописываю_в_поле",
"checkOption": "выбираю_опцию",
"seeCheckboxIsChecked": "вижу_галочку",
"dontSeeCheckboxIsChecked": "не_вижу_галочку",
"grabTextFrom": "беру_текст_из",
"grabValueFrom": "беру_значение_из",
"grabAttributeFrom": "беру_атрибут_из",
"seeInTitle": "вижу_в_заголовке",
"dontSeeInTitle": "не_вижу_в_заголовке",
"grabTitle": "беру_заголовок",
"seeElement": "вижу_элемент",
"dontSeeElement": "не_вижу_элемент",
"seeInSource": "вижу_в_коде",
"dontSeeInSource": "не_вижу_в_коде",
"executeScript": "выполняю_скрипт",
"executeAsyncScript": "выполняю_скрипт_асинхронно",
"seeInCurrentUrl": "вижу_в_адресе",
"dontSeeInCurrentUrl": "не_вижу_в_адресе",
"seeCurrentUrlEquals": "вижу_адрес_равен",
"dontSeeCurrentUrlEquals": "не_вижу_адрес",
"saveScreenshot": "делаю_скриншот",
"setCookie": "устанавливаю_куки",
"clearCookie": "очищаю_куки",
"seeCookie": "вижу_в_куки",
"dontSeeCookie": "не_вижу_в_куки",
"grabCookie": "беру_куки",
"resizeWindow": "растягиваю_окно",
"wait": "жду"
}
}
}
declare const Цель: typeof Feature;
declare const Сценарий: typeof Scenario;
declare const Начало: typeof Before;
declare const Конец: typeof After;
declare const Перед_всем: typeof BeforeSuite;
declare const После_всего: typeof AfterSuite;
\ No newline at end of file
// in this file you can append custom step methods to 'I' object
module.exports = function() {
return actor({
login: function(email, password) {
this.amOnPage('/sign-in')
this.fillField('email', email)
this.fillField('password', password)
this.click('#test_login')
this.waitForElement('#test_greetings', 5)
}
// Define custom steps here, use 'this' to access default methods of I.
// It is recommended to place a general 'login' function here.
});
}
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