Commit b73a244c authored by Ermolaev Timur's avatar Ermolaev Timur

Merge branch 'task-142-enhance/separation_project_page' into 'development'

Task 142 enhance/separation project page

See merge request !111
parents 1db10f3d 264d311a
...@@ -51,7 +51,6 @@ router.get('/my',auth, async (req:Request, res:Response): Promise<Response>=> { ...@@ -51,7 +51,6 @@ router.get('/my',auth, async (req:Request, res:Response): Promise<Response>=> {
router.post('/',auth, async (req:Request, res:Response): Promise<Response> => { router.post('/',auth, async (req:Request, res:Response): Promise<Response> => {
if (!req.body) return res.status(400).send({Message:'problem in incoming req.body'}) if (!req.body) return res.status(400).send({Message:'problem in incoming req.body'})
const {user, title,color}= req.body; const {user, title,color}= req.body;
const member:Member = new Member; const member:Member = new Member;
member.user= user; member.user= user;
member.roleProject= MemberRole.ADMIN; member.roleProject= MemberRole.ADMIN;
......
...@@ -17,7 +17,6 @@ function NewMemberModalContent({ members, users, onChangeNewMemberHandler, onCha ...@@ -17,7 +17,6 @@ function NewMemberModalContent({ members, users, onChangeNewMemberHandler, onCha
getOptionLabel={(item) => item.displayName || ""} getOptionLabel={(item) => item.displayName || ""}
style={{ marginBottom: '15px' }} style={{ marginBottom: '15px' }}
/> />
<CustomAutocomplete <CustomAutocomplete
name={'role'} name={'role'}
label={'Роль'} label={'Роль'}
......
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 {Typography} from "@mui/material";
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
const ProjectForm = ({onSubmit}) => {
const users = useSelector(state => state.users)
console.log(users)
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const [state, setState] = useState({
title: ""
});
const submitFormHandler = (e) => {
e.preventDefault();
let project = {title: state.title}
console.log(project);
if (project.title === "") {
alert("Нельзя создать проект без названия")
} else {
onSubmit(project);
}
};
const inputChangeHandler = (e) => {
const {name, value} = e.target;
setState(prevState => {
return {...prevState, [name]: value};
});
};
return (
<div >
<Button onClick={handleOpen} >Добавить проект</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
onSubmit={submitFormHandler}
>
<Box sx={style}>
<form >
<Grid container direction="column" spacing={2}>
<Typography variant="h4">Новый проект</Typography>
<FormElement
onChange={inputChangeHandler}
name={"title"}
label={"Title"}
state={state}
/>
<Grid item>
<Button
type="submit"
color="primary"
variant="contained"
>
Создать
</Button>
</Grid>
</Grid>
</form>
</Box>
</Modal>
</div>
);
};
export default ProjectForm;
\ No newline at end of file
import { Button, 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";
import DeleteIcon from "@mui/icons-material/Delete";
import { deleteProject } from "../../../store/actions/projectsActions";
const ProjectItem = ({ title, tasks, projectId }) => {
const user = useSelector(state => state.users.user);
const dispatch = useDispatch();
console.log(user)
const deleteHandle = (projectId) => {
console.log("project id", projectId)
dispatch(deleteProject(projectId))
};
return <>
<Grid item xs={12} sm={12} md={6} lg={4}>
<Card>
<CardContent >
<strong>
<br></br>
Название проекта: {title}
</strong>
<strong>
<br></br>
{/* Задачи: {tasks} */}
</strong>
</CardContent>
<CardActions>
{(title !== "Личные дела") ? <Button onClick={() => {
deleteHandle(projectId);
}} variant="outlined" startIcon={<DeleteIcon />}>
Delete
</Button> : null}
<IconButton component={Link} to={"/projects/" + projectId}>
<ArrowForwardIcon />
</IconButton>
</CardActions>
</Card>
</Grid>
</>
};
export default ProjectItem;
...@@ -5,7 +5,7 @@ import { memo } from "react"; ...@@ -5,7 +5,7 @@ import { memo } from "react";
const ColumnTitle = ({ text }) => { const ColumnTitle = ({ text }) => {
return <> return <>
<Typography variant="h4" textAlign={'center'} sx={{ height: '10%' }}> <Typography variant="h5" textAlign={'center'} sx={{ height: '10%' }}>
{text} {text}
</Typography> </Typography>
</> </>
......
...@@ -7,7 +7,7 @@ const styleColumn = { ...@@ -7,7 +7,7 @@ const styleColumn = {
border: '3px solid black', border: '3px solid black',
borderRadius: '10px', borderRadius: '10px',
height: '60vh', height: '60vh',
flexBasis: 60 / 3 + '%', flexBasis: 70 / 3 + '%',
overflow: 'auto', overflow: 'auto',
overflowX: 'hidden', overflowX: 'hidden',
} }
......
...@@ -6,10 +6,9 @@ import ProjectUsersColumn from "./ProjectUsersColumn/ProjectUsersColumn"; ...@@ -6,10 +6,9 @@ import ProjectUsersColumn from "./ProjectUsersColumn/ProjectUsersColumn";
const style = { const style = {
display: 'flex', display: 'flex',
gap: '150px',
width: '100%', width: '100%',
marginTop: '20px', marginTop: '20px',
justifyContent: 'space-evenly' justifyContent: 'space-between'
} }
......
import {Grid} from "@mui/material";
import ProjectItem from "../ProjectItem/ProjectItem";
const ProjectsList = ({projects}) => {
console.log(projects)
return (
<Grid item container direction="column" spacing={1}>
{projects?.map(project => {
return <ProjectItem
tasks={project.tasks}
workers={project.workers}
title={project.title}
createdAt={project.createdAt}
dateDue={project.dateDue}
admin={project.admin}
projectId={project.id}
key={project.id}
/>
})}
</Grid>
);
};
export default ProjectsList;
\ No newline at end of file
import { Button } from "@mui/material";
import { Box } from "@mui/system";
import { memo } from "react";
import FormElement from "../../UI/Form/FormElement/FormElement"
import { isValidate } from "./helpers";
const NewProjectModalContent = ({ inputChangeHandler, projectTitle, createNewProjectHandler, handleClose }) => {
return <>
<FormElement
onChange={(e)=>{inputChangeHandler(e)}}
name={"title"}
label={"Название"}
state={projectTitle}
/>
<Box sx={{ display: 'flex', justifyContent: 'space-between', marginTop: '10px'}}>
<Button
type="submit"
color="primary"
variant="outlined"
onClick={()=>{createNewProjectHandler()}}
disabled={!isValidate(projectTitle)}
>
Создать
</Button>
<Button
type="submit"
color="primary"
variant="outlined"
onClick={()=>{handleClose()}}
>
Отмена
</Button>
</Box>
</>
};
export default memo(NewProjectModalContent);
\ No newline at end of file
export const isValidate = (title) => {
if (!title) return false
return true
}
\ No newline at end of file
import {Box, Grid, Typography } from "@mui/material";
import { useSelector } from "react-redux";
import { memo, useMemo } from "react";
import DeleteButton from "../../../../UI/DeleteButton/DeleteButton";
import ArrowIncrementButton from "../../../../UI/ArrowIncrementButton/ArrowIncrementButton";
const styleBlock = {
border: '2px solid black',
borderRadius: '5px',
width: '92%',
padding: '10px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}
const styleText = {
fontSize: '15px',
fontWeight: '600',
}
const ProjectItem = ({ title, members, onClickProjectHandler, onClickGoToSpecificProjectHandler, deleteProjectHandler }) => {
const { user } = useSelector(state => state.users);
const currentRoleInProject = useMemo(() => {
return members.find((member) => member.user.id === user.id)?.roleProject
}, [members, user.id])
return <>
<Box onClick={onClickProjectHandler} sx={styleBlock}>
<Box>
<Typography sx={styleText}>
Проект: {title}
</Typography>
<Typography sx={styleText}>
Роль в проекте: {currentRoleInProject}
</Typography>
</Box>
<Box>
{currentRoleInProject === 'admin' ? <DeleteButton onClick={deleteProjectHandler}/> : null}
<ArrowIncrementButton onClick={onClickGoToSpecificProjectHandler}/>
</Box>
</Box>
</>
};
export default memo(ProjectItem);
import {Box} from "@mui/material";
import { memo } from "react";
import ProjectItem from "./ProjectItem/ProjectItem";
const style = {height: '79vh', overflow: 'auto', overflowX: 'hidden', display: 'flex', flexDirection: 'column', gap: '10px'}
const ProjectsList = ({projects, onClickProjectHandler, onClickGoToSpecificProjectHandler, deleteProjectHandler}) => {
return (
<Box sx={style}>
{projects?.map(project => {
return <ProjectItem
title={project.title}
members={project.members}
key={project.id}
onClickProjectHandler={()=>{onClickProjectHandler(project)}}
onClickGoToSpecificProjectHandler={(e)=>{onClickGoToSpecificProjectHandler(e, project.id)}}
deleteProjectHandler={(e)=>{deleteProjectHandler(e, project.id)}}
/>
})}
</Box>
);
};
export default memo(ProjectsList);
\ No newline at end of file
import { Grid, Typography, Button, Card, CardContent } from "@mui/material";
import { memo } from "react";
import ProjectsList from "./ProjectsList/ProjectsList";
const style = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '10px',
}
const ProjectsWrapper = ({ onClickProjectHandler, projects, onClickGoToSpecificProjectHandler, handleOpen, deleteProjectHandler }) => {
return <>
<Grid item xs={4}>
<Typography variant="h2" sx={style}>
Проекты
<Button variant="outlined" onClick={()=>{handleOpen()}}>Создать</Button>
</Typography>
<ProjectsList
projects={projects}
onClickProjectHandler={onClickProjectHandler}
onClickGoToSpecificProjectHandler={onClickGoToSpecificProjectHandler}
deleteProjectHandler={deleteProjectHandler}
/>
</Grid>
</>
};
export default memo(ProjectsWrapper);
\ No newline at end of file
...@@ -6,7 +6,6 @@ const arrowClass = { ...@@ -6,7 +6,6 @@ const arrowClass = {
transition: '0.5s', transition: '0.5s',
"&:hover": { "&:hover": {
background: 'rgb(48, 154, 252, 0.65)',
transition: '0.5s', transition: '0.5s',
transform: 'scale(1.2)' transform: 'scale(1.2)'
} }
......
...@@ -6,7 +6,6 @@ const arrowClass = { ...@@ -6,7 +6,6 @@ const arrowClass = {
transition: '0.5s', transition: '0.5s',
"&:hover": { "&:hover": {
background: 'rgb(48, 154, 252, 0.65)',
transition: '0.5s', transition: '0.5s',
transform: 'scale(1.2)' transform: 'scale(1.2)'
} }
......
import DeleteIcon from "@mui/icons-material/Delete";
import { memo } from 'react';
const arrowClass = {
cursor: 'pointer',
transition: '0.5s',
"&:hover": {
transition: '0.5s',
transform: 'scale(1.2)'
}
}
function DeleteButton({onClick}) {
return (
<>
<DeleteIcon
sx={arrowClass}
onClick={onClick}
/>
</> );
}
export default memo(DeleteButton);
\ No newline at end of file
...@@ -11,22 +11,28 @@ import NewMemberModalContent from "../../components/ProjectComponents/NewMemberM ...@@ -11,22 +11,28 @@ import NewMemberModalContent from "../../components/ProjectComponents/NewMemberM
const FullProject = () => { const FullProject = ({ projectId }) => {
const { project } = useSelector(state => state.projects); const { project } = useSelector(state => state.projects);
const [modal, setModal] = useState(false)
const [newMember, setNewMember] = useState(null)
const [currentMember, setCurrentMember] = useState(null)
const { user, users } = useSelector(state => state.users); const { user, users } = useSelector(state => state.users);
const dispatch = useDispatch(); const dispatch = useDispatch();
const params = useParams() const params = useParams()
const navigate = useNavigate() const navigate = useNavigate()
const [modal, setModal] = useState(false)
const [newMember, setNewMember] = useState(null)
const [currentMember, setCurrentMember] = useState(null)
useEffect(() => { useEffect(() => {
dispatch(fetchProject(params.id)) if (projectId) {
dispatch(fetchProject(projectId))
} else {
dispatch(fetchProject(params.id))
}
dispatch(fetchUsers()) dispatch(fetchUsers())
}, [params.id, dispatch]); }, [params.id, dispatch, projectId]);
const members = useMemo(() => { const members = useMemo(() => {
return project?.project?.members || [] return project?.project?.members || []
...@@ -69,46 +75,42 @@ const FullProject = () => { ...@@ -69,46 +75,42 @@ const FullProject = () => {
dispatch(deleteMember(id, project?.project?.id)) dispatch(deleteMember(id, project?.project?.id))
}, [dispatch, project?.project?.id]) }, [dispatch, project?.project?.id])
const onClickTasksHandler = useCallback((id) => { const onClickTasksHandler = useCallback(() => {
navigate('/workers-tasks') navigate('/workers-tasks')
}, [navigate]) }, [navigate])
return <> return <>
<Grid> <ProjectInfo
project={project.project}
<ProjectInfo handleOpen={handleOpen}
project={project.project} currentRoleInProject={currentRoleInProject}
handleOpen={handleOpen} onClickTasksHandler={onClickTasksHandler}
currentRoleInProject={currentRoleInProject} />
onClickTasksHandler={onClickTasksHandler}
/> <ProjectUsersColumnsWrapper
members={members}
<ProjectUsersColumnsWrapper deleteMemberHandler={deleteMemberHandler}
currentRoleInProject={currentRoleInProject}
dragMemberToNewRole={dragMemberToNewRole}
setCurrentMember={setCurrentMember}
/>
<DefaultModal
modal={modal}
handleClose={() => { handleClose() }}
>
<NewMemberModalContent
users={users}
onChangeNewMemberHandler={onChangeNewMemberHandler}
onChangeRoleHandler={onChangeRoleHandler}
newMember={newMember}
handleClose={handleClose}
createNewMemberHandler={createNewMemberHandler}
members={members} members={members}
deleteMemberHandler={deleteMemberHandler}
currentRoleInProject={currentRoleInProject}
dragMemberToNewRole={dragMemberToNewRole}
setCurrentMember={setCurrentMember}
/>
<DefaultModal
modal={modal}
handleClose={() => { handleClose() }}
> >
<NewMemberModalContent
users={users} </NewMemberModalContent>
onChangeNewMemberHandler={onChangeNewMemberHandler} </DefaultModal>
onChangeRoleHandler={onChangeRoleHandler}
newMember={newMember}
handleClose={handleClose}
createNewMemberHandler={createNewMemberHandler}
members={members}
>
</NewMemberModalContent>
</DefaultModal>
</Grid>
</> </>
}; };
......
import {useNavigate} from "react-router-dom";
import {useDispatch, useSelector} from "react-redux";
import { useEffect } from "react";
import ProjectForm from "../../components/ProjectComponents/ProjectForm/ProjectForm";
import { createProject, fetchProjects } from "../../store/actions/projectsActions";
const NewProject = () => {
const dispatch = useDispatch();
const projects = useSelector(state => state.projects.projects);
const navigate = useNavigate();
const onSubmit = async (projectData) => {
await dispatch(createProject(projectData, navigate));
console.log(projectData)
};
useEffect(()=> {
dispatch(fetchProjects());
}, [dispatch])
return (
<>
<ProjectForm projects={projects} onSubmit={onSubmit} />
</>
);
};
export default NewProject;
\ No newline at end of file
import { Grid, Typography, Button } from "@mui/material"; import { Grid } from "@mui/material";
import { Link } from "react-router-dom"; import { useCallback, useEffect, useState } from "react";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import Loader from "../../components/UI/Loader/Loader"; import { createProject, deleteProject, fetchProjects } from "../../store/actions/projectsActions";
import HasAccess from "../../components/UI/HasAccess/HasAccess"; import FullProject from "../FullProject/FullProject";
import { fetchProjects } from "../../store/actions/projectsActions"; import ProjectsWrapper from "../../components/ProjectsComponents/ProjectsWrapper/ProjectsWrapper";
import ProjectsList from "../../components/ProjectComponents/ProjectsList/ProjectsList"; import { useNavigate } from "react-router-dom";
import NewProject from "../NewProject/NewProject"; import DefaultModal from "../../components/UI/DefaultModal/DefaultModal";
import NewProjectModalContent from "../../components/ProjectsComponents/NewProjectModalContent/NewProjectModalContent";
const Projects = () => { const Projects = () => {
const { projects } = useSelector(state => state.projects.projects);
const { user } = useSelector(state => state.users)
const dispatch = useDispatch(); const dispatch = useDispatch();
const { projects, loading } = useSelector(state => state.projects.projects); const navigate = useNavigate()
const {users} = useSelector(state => state.users);
const members = useSelector(state => state.projects.projects) const [currentProject, setCurrentProject] = useState(null)
const [projectTitle, setProjectTitle] = useState('');
const [modal, setModal] = useState(false)
console.log(projects)
console.log(users)
useEffect(() => { useEffect(() => {
dispatch(fetchProjects()) dispatch(fetchProjects())
}, [dispatch]); }, [dispatch]);
const inputChangeHandler = (e) => {
setProjectTitle(e.target.value)
};
const onClickProjectHandler = useCallback((project) => {
setCurrentProject(project)
}, [])
const onClickGoToSpecificProjectHandler = useCallback((e, projectId) => {
e.stopPropagation();
navigate(`/projects/${projectId}`)
}, [navigate])
const createNewProjectHandler = useCallback(() => {
dispatch(createProject({ title: projectTitle, user: user.id }, navigate))
}, [dispatch, projectTitle, user.id, navigate])
const deleteProjectHandler = useCallback((e, projectId) => {
e.stopPropagation();
dispatch(deleteProject(projectId))
}, [dispatch])
const handleOpen = useCallback(async () => {
setModal(true)
}, [])
const handleClose = useCallback(() => {
setModal(false)
}, [])
return <> return <>
{projects?.length > 0 ? (<> <Grid container justifyContent='space-between'>
<Grid container direction="column" spacing={2}>
<Grid <ProjectsWrapper
container projects={projects}
item onClickProjectHandler={onClickProjectHandler}
direction="row" onClickGoToSpecificProjectHandler={onClickGoToSpecificProjectHandler}
justifyContent="space-between" handleOpen={handleOpen}
alignItems="center" deleteProjectHandler={deleteProjectHandler}
> />
<Grid item>
<Typography variant="h4"> <Grid item xs={7} >
Проекты {currentProject ? <FullProject projectId={currentProject?.id} /> : null}
</Typography>
</Grid>
<HasAccess roles={["superuser", "admin", "user"]} >
<Grid item>
<NewProject/>
</Grid>
</HasAccess>
</Grid>
<Loader loading={loading} />
<ProjectsList projects={projects} members={members} />
</Grid> </Grid>
</>) :
<h1>Созданных проектов нет</h1> </Grid>
}
<DefaultModal
modal={modal}
handleClose={() => { handleClose() }}
>
<NewProjectModalContent
handleClose={handleClose}
projectTitle={projectTitle}
inputChangeHandler={inputChangeHandler}
createNewProjectHandler={createNewProjectHandler}
/>
</DefaultModal>
</> </>
}; };
......
...@@ -264,7 +264,6 @@ function WeekCalendar() { ...@@ -264,7 +264,6 @@ function WeekCalendar() {
}, [currentTask, dispatch, hoursInDay, userId]) }, [currentTask, dispatch, hoursInDay, userId])
const deleteTaskHandler = useCallback(async (e, taskId) => { const deleteTaskHandler = useCallback(async (e, taskId) => {
console.log(e)
e.stopPropagation(); e.stopPropagation();
await dispatch(deleteCalendarTask(taskId, userId)) await dispatch(deleteCalendarTask(taskId, userId))
}, [dispatch, userId]) }, [dispatch, userId])
......
...@@ -60,14 +60,13 @@ export const deleteMember = (memberId, projectId) => { ...@@ -60,14 +60,13 @@ export const deleteMember = (memberId, projectId) => {
} }
} }
export const deleteProject = (projectId, projects) => { export const deleteProject = (projectId) => {
return async (dispatch) => { return async (dispatch) => {
dispatch(deleteProjectRequest()); dispatch(deleteProjectRequest());
try { try {
const response = await axios.delete('/projects', { data: { projectId: projectId } }); const response = await axios.delete('/projects', { data: { projectId: projectId } });
console.log("deleteMember ", response.data)
dispatch(deleteProjectSuccess()) dispatch(deleteProjectSuccess())
dispatch(fetchProjects(projects)) dispatch(fetchProjects())
} catch (error) { } catch (error) {
dispatch(deleteProjectFailure(error.response.data)); dispatch(deleteProjectFailure(error.response.data));
} }
...@@ -102,7 +101,6 @@ export const createProject = (projectData, navigate) => { ...@@ -102,7 +101,6 @@ export const createProject = (projectData, navigate) => {
try { try {
const response = await axios.post("/projects", projectData); const response = await axios.post("/projects", projectData);
dispatch(createProjectSuccess()); dispatch(createProjectSuccess());
console.log(response.data)
navigate("/projects/" + response.data.project.id) navigate("/projects/" + response.data.project.id)
dispatch(showNotification("Проект успешно создан")) dispatch(showNotification("Проект успешно создан"))
} catch (e) { } catch (e) {
...@@ -157,8 +155,7 @@ export const changeMemberRole = (userId, data) => { ...@@ -157,8 +155,7 @@ export const changeMemberRole = (userId, data) => {
return async (dispatch) => { return async (dispatch) => {
dispatch(changeMemberRoleSuccess()); dispatch(changeMemberRoleSuccess());
try { try {
const response = await axios.put(`/projects/change-project-role/${userId}`, data); await axios.put(`/projects/change-project-role/${userId}`, data);
console.log(response.data)
dispatch(changeMemberRoleRequest()); dispatch(changeMemberRoleRequest());
await dispatch(fetchProject(data?.projectId)) await dispatch(fetchProject(data?.projectId))
} catch (e) { } catch (e) {
......
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