feature: add ability to assign tasks
This commit is contained in:
@ -20,6 +20,7 @@
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/jwt-decode": "^2.2.1",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react": "^16.9.21",
|
||||
@ -41,6 +42,7 @@
|
||||
"graphql-tag": "^2.10.3",
|
||||
"history": "^4.10.1",
|
||||
"immer": "^6.0.3",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.0",
|
||||
"prop-types": "^15.7.2",
|
||||
|
@ -7,6 +7,7 @@ export default createGlobalStyle`
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
min-width: 768px;
|
||||
background: #262c49;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -1,9 +1,14 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { Home, Stack } from 'shared/icons';
|
||||
import Navbar, { ActionButton, ButtonContainer, PrimaryLogo } from 'shared/components/Navbar';
|
||||
import { Link } from 'react-router-dom';
|
||||
import UserIDContext from './context';
|
||||
|
||||
const GlobalNavbar = () => {
|
||||
const { userID } = useContext(UserIDContext);
|
||||
if (!userID) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Navbar>
|
||||
<PrimaryLogo />
|
||||
|
@ -12,14 +12,12 @@ type RoutesProps = {
|
||||
};
|
||||
|
||||
const Routes = ({ history }: RoutesProps) => (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Dashboard} />
|
||||
<Route exact path="/projects" component={Projects} />
|
||||
<Route path="/projects/:projectId" component={Project} />
|
||||
<Route exact path="/login" component={Login} />
|
||||
</Switch>
|
||||
</Router>
|
||||
<Switch>
|
||||
<Route exact path="/login" component={Login} />
|
||||
<Route exact path="/" component={Dashboard} />
|
||||
<Route exact path="/projects" component={Projects} />
|
||||
<Route path="/projects/:projectId" component={Project} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
export default Routes;
|
||||
|
@ -1,8 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useContext } from 'react';
|
||||
import TopNavbar from 'shared/components/TopNavbar';
|
||||
import DropdownMenu from 'shared/components/DropdownMenu';
|
||||
import { useHistory } from 'react-router';
|
||||
import UserIDContext from 'App/context';
|
||||
import { useMeQuery } from 'shared/generated/graphql';
|
||||
|
||||
const GlobalTopNavbar: React.FC = () => {
|
||||
const { loading, data } = useMeQuery();
|
||||
const history = useHistory();
|
||||
const { userID, setUserID } = useContext(UserIDContext);
|
||||
const [menu, setMenu] = useState({
|
||||
top: 0,
|
||||
left: 0,
|
||||
@ -15,10 +21,33 @@ const GlobalTopNavbar: React.FC = () => {
|
||||
top: bottom,
|
||||
});
|
||||
};
|
||||
|
||||
const onLogout = () => {
|
||||
fetch('http://localhost:3333/auth/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
}).then(async x => {
|
||||
const { status } = x;
|
||||
if (status === 200) {
|
||||
history.replace('/login');
|
||||
setUserID(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
if (!userID) {
|
||||
return null;
|
||||
}
|
||||
console.log(data);
|
||||
return (
|
||||
<>
|
||||
<TopNavbar onNotificationClick={() => console.log('beep')} onProfileClick={onProfileClick} />
|
||||
{menu.isOpen && <DropdownMenu left={menu.left} top={menu.top} />}
|
||||
<TopNavbar
|
||||
firstName={data ? data.me.firstName : ''}
|
||||
lastName={data ? data.me.lastName : ''}
|
||||
initials={!data ? '' : data.me.profileIcon.initials ?? ''}
|
||||
onNotificationClick={() => console.log('beep')}
|
||||
onProfileClick={onProfileClick}
|
||||
/>
|
||||
{menu.isOpen && <DropdownMenu onLogout={onLogout} left={menu.left} top={menu.top} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
9
web/src/App/context.ts
Normal file
9
web/src/App/context.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
type UserIDContextState = {
|
||||
userID: string | null;
|
||||
setUserID: (userID: string | null) => void;
|
||||
};
|
||||
export const UserIDContext = React.createContext<UserIDContextState>({ userID: null, setUserID: _userID => null });
|
||||
|
||||
export default UserIDContext;
|
@ -1,22 +1,27 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { setAccessToken } from 'shared/utils/accessToken';
|
||||
import Navbar from 'shared/components/Navbar';
|
||||
import GlobalTopNavbar from 'App/TopNavbar';
|
||||
import styled from 'styled-components';
|
||||
import NormalizeStyles from './NormalizeStyles';
|
||||
import BaseStyles from './BaseStyles';
|
||||
import Routes from './Routes';
|
||||
import { UserIDContext } from './context';
|
||||
import Navbar from './Navbar';
|
||||
import { Router } from 'react-router';
|
||||
|
||||
const history = createBrowserHistory();
|
||||
|
||||
const MainContent = styled.div`
|
||||
padding: 0 0 50px 80px;
|
||||
background: #262c49;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const App = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [userID, setUserID] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:3333/auth/refresh_token', {
|
||||
@ -29,28 +34,33 @@ const App = () => {
|
||||
} else {
|
||||
const response: RefreshTokenResponse = await x.json();
|
||||
const { accessToken } = response;
|
||||
const claims: JWTToken = jwtDecode(accessToken);
|
||||
setUserID(claims.userId);
|
||||
setAccessToken(accessToken);
|
||||
}
|
||||
// }
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<MainContent>
|
||||
<GlobalTopNavbar />
|
||||
</MainContent>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
<Routes history={history} />
|
||||
<UserIDContext.Provider value={{ userID, setUserID }}>
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
<Router history={history}>
|
||||
{loading ? (
|
||||
<div>loading</div>
|
||||
) : (
|
||||
<>
|
||||
<Navbar />
|
||||
<MainContent>
|
||||
<GlobalTopNavbar />
|
||||
<Routes history={history} />
|
||||
</MainContent>
|
||||
</>
|
||||
)}
|
||||
</Router>
|
||||
</UserIDContext.Provider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
@ -6,10 +6,13 @@ import { setAccessToken } from 'shared/utils/accessToken';
|
||||
|
||||
import Login from 'shared/components/Login';
|
||||
import { Container, LoginWrapper } from './Styles';
|
||||
import UserIDContext from 'App/context';
|
||||
import JwtDecode from 'jwt-decode';
|
||||
|
||||
const Auth = () => {
|
||||
const [invalidLoginAttempt, setInvalidLoginAttempt] = useState(0);
|
||||
const history = useHistory();
|
||||
const { setUserID } = useContext(UserIDContext);
|
||||
const login = (
|
||||
data: LoginFormData,
|
||||
setComplete: (val: boolean) => void,
|
||||
@ -31,8 +34,11 @@ const Auth = () => {
|
||||
} else {
|
||||
const response = await x.json();
|
||||
const { accessToken } = response;
|
||||
setAccessToken(accessToken);
|
||||
const claims: JWTToken = JwtDecode(accessToken);
|
||||
setUserID(claims.userId);
|
||||
setComplete(true);
|
||||
setAccessToken(accessToken);
|
||||
|
||||
history.push('/');
|
||||
}
|
||||
});
|
||||
|
110
web/src/Projects/Project/Details/index.tsx
Normal file
110
web/src/Projects/Project/Details/index.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import Modal from 'shared/components/Modal';
|
||||
import TaskDetails from 'shared/components/TaskDetails';
|
||||
import PopupMenu from 'shared/components/PopupMenu';
|
||||
import MemberManager from 'shared/components/MemberManager';
|
||||
import { useRouteMatch, useHistory } from 'react-router';
|
||||
import { useFindTaskQuery, useAssignTaskMutation } from 'shared/generated/graphql';
|
||||
import UserIDContext from 'App/context';
|
||||
|
||||
type DetailsProps = {
|
||||
taskID: string;
|
||||
projectURL: string;
|
||||
onTaskNameChange: (task: Task, newName: string) => void;
|
||||
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
|
||||
onDeleteTask: (task: Task) => void;
|
||||
onOpenAddLabelPopup: (task: Task, bounds: ElementBounds) => void;
|
||||
availableMembers: Array<TaskUser>;
|
||||
};
|
||||
|
||||
const initialMemberPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
|
||||
|
||||
const Details: React.FC<DetailsProps> = ({
|
||||
projectURL,
|
||||
taskID,
|
||||
onTaskNameChange,
|
||||
onTaskDescriptionChange,
|
||||
onDeleteTask,
|
||||
onOpenAddLabelPopup,
|
||||
availableMembers,
|
||||
}) => {
|
||||
const { userID } = useContext(UserIDContext);
|
||||
const history = useHistory();
|
||||
const match = useRouteMatch();
|
||||
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
|
||||
const { loading, data } = useFindTaskQuery({ variables: { taskID } });
|
||||
const [assignTask] = useAssignTaskMutation();
|
||||
if (loading) {
|
||||
return <div>loading</div>;
|
||||
}
|
||||
if (!data) {
|
||||
return <div>loading</div>;
|
||||
}
|
||||
const taskMembers = data.findTask.assigned.map(assigned => {
|
||||
return {
|
||||
userID: assigned.userID,
|
||||
displayName: `${assigned.firstName} ${assigned.lastName}`,
|
||||
profileIcon: {
|
||||
url: null,
|
||||
initials: assigned.profileIcon.initials ?? null,
|
||||
},
|
||||
};
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
width={1040}
|
||||
onClose={() => {
|
||||
history.push(projectURL);
|
||||
}}
|
||||
renderContent={() => {
|
||||
return (
|
||||
<TaskDetails
|
||||
task={{
|
||||
...data.findTask,
|
||||
members: taskMembers,
|
||||
description: data.findTask.description ?? '',
|
||||
labels: [],
|
||||
}}
|
||||
onTaskNameChange={onTaskNameChange}
|
||||
onTaskDescriptionChange={onTaskDescriptionChange}
|
||||
onDeleteTask={onDeleteTask}
|
||||
onCloseModal={() => history.push(projectURL)}
|
||||
onOpenAddMemberPopup={(task, bounds) => {
|
||||
console.log(task, bounds);
|
||||
setMemberPopupData({
|
||||
isOpen: true,
|
||||
taskID: task.taskID,
|
||||
top: bounds.position.top + bounds.size.height + 10,
|
||||
left: bounds.position.left,
|
||||
});
|
||||
}}
|
||||
onOpenAddLabelPopup={onOpenAddLabelPopup}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{memberPopupData.isOpen && (
|
||||
<PopupMenu
|
||||
title="Members"
|
||||
top={memberPopupData.top}
|
||||
onClose={() => setMemberPopupData(initialMemberPopupState)}
|
||||
left={memberPopupData.left}
|
||||
>
|
||||
<MemberManager
|
||||
availableMembers={availableMembers}
|
||||
activeMembers={[]}
|
||||
onMemberChange={(member, isActive) => {
|
||||
if (isActive) {
|
||||
assignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } });
|
||||
}
|
||||
console.log(member, isActive);
|
||||
}}
|
||||
/>
|
||||
</PopupMenu>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Details;
|
@ -11,18 +11,17 @@ import {
|
||||
useUpdateTaskGroupLocationMutation,
|
||||
useCreateTaskGroupMutation,
|
||||
useDeleteTaskGroupMutation,
|
||||
useUpdateTaskDescriptionMutation,
|
||||
useAssignTaskMutation,
|
||||
} from 'shared/generated/graphql';
|
||||
|
||||
import Navbar from 'App/Navbar';
|
||||
import TopNavbar from 'App/TopNavbar';
|
||||
import QuickCardEditor from 'shared/components/QuickCardEditor';
|
||||
import PopupMenu from 'shared/components/PopupMenu';
|
||||
import ListActions from 'shared/components/ListActions';
|
||||
import Modal from 'shared/components/Modal';
|
||||
import TaskDetails from 'shared/components/TaskDetails';
|
||||
import MemberManager from 'shared/components/MemberManager';
|
||||
import { LabelsPopup } from 'shared/components/PopupMenu/PopupMenu.stories';
|
||||
import KanbanBoard from 'Projects/Project/KanbanBoard';
|
||||
import Details from './Details';
|
||||
|
||||
type TaskRouteProps = {
|
||||
taskID: string;
|
||||
@ -35,17 +34,6 @@ interface QuickCardEditorState {
|
||||
task?: Task;
|
||||
}
|
||||
|
||||
const MainContent = styled.div`
|
||||
padding: 0 0 50px 80px;
|
||||
height: 100%;
|
||||
background: #262c49;
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
font-size: 16px;
|
||||
background-color: red;
|
||||
`;
|
||||
|
||||
const TitleWrapper = styled.div`
|
||||
margin-left: 38px;
|
||||
margin-bottom: 15px;
|
||||
@ -64,7 +52,6 @@ interface ProjectParams {
|
||||
const initialState: BoardState = { tasks: {}, columns: {} };
|
||||
const initialPopupState = { left: 0, top: 0, isOpen: false, taskGroupID: '' };
|
||||
const initialQuickCardEditorState: QuickCardEditorState = { isOpen: false, top: 0, left: 0 };
|
||||
const initialMemberPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
|
||||
const initialLabelsPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
|
||||
const initialTaskDetailsState = { isOpen: false, taskID: '' };
|
||||
|
||||
@ -73,9 +60,9 @@ const Project = () => {
|
||||
const match = useRouteMatch();
|
||||
const history = useHistory();
|
||||
|
||||
const [updateTaskDescription] = useUpdateTaskDescriptionMutation();
|
||||
const [listsData, setListsData] = useState(initialState);
|
||||
const [popupData, setPopupData] = useState(initialPopupState);
|
||||
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
|
||||
const [taskDetails, setTaskDetails] = useState(initialTaskDetailsState);
|
||||
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
|
||||
const [updateTaskLocation] = useUpdateTaskLocationMutation();
|
||||
@ -142,6 +129,7 @@ const Project = () => {
|
||||
name: task.name,
|
||||
position: task.position,
|
||||
labels: [],
|
||||
description: task.description ?? undefined,
|
||||
};
|
||||
});
|
||||
});
|
||||
@ -199,30 +187,35 @@ const Project = () => {
|
||||
createTaskGroup({ variables: { projectID: projectId, name: listName, position } });
|
||||
};
|
||||
|
||||
const [assignTask] = useAssignTaskMutation();
|
||||
|
||||
if (loading) {
|
||||
return <Wrapper>Loading</Wrapper>;
|
||||
return <Title>Error Loading</Title>;
|
||||
}
|
||||
if (data) {
|
||||
const availableMembers = data.findProject.members.map(member => {
|
||||
return {
|
||||
displayName: `${member.firstName} ${member.lastName}`,
|
||||
profileIcon: { url: null, initials: member.profileIcon.initials ?? null },
|
||||
userID: member.userID,
|
||||
};
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<MainContent>
|
||||
<TopNavbar />
|
||||
<TitleWrapper>
|
||||
<Title>{data.findProject.name}</Title>
|
||||
<KanbanBoard
|
||||
listsData={listsData}
|
||||
onCardDrop={onCardDrop}
|
||||
onListDrop={onListDrop}
|
||||
onCardCreate={onCardCreate}
|
||||
onCreateList={onCreateList}
|
||||
onQuickEditorOpen={onQuickEditorOpen}
|
||||
onOpenListActionsPopup={(isOpen, left, top, taskGroupID) => {
|
||||
setPopupData({ isOpen, top, left, taskGroupID });
|
||||
}}
|
||||
/>
|
||||
</TitleWrapper>
|
||||
</MainContent>
|
||||
<TitleWrapper>
|
||||
<Title>{data.findProject.name}</Title>
|
||||
</TitleWrapper>
|
||||
<KanbanBoard
|
||||
listsData={listsData}
|
||||
onCardDrop={onCardDrop}
|
||||
onListDrop={onListDrop}
|
||||
onCardCreate={onCardCreate}
|
||||
onCreateList={onCreateList}
|
||||
onQuickEditorOpen={onQuickEditorOpen}
|
||||
onOpenListActionsPopup={(isOpen, left, top, taskGroupID) => {
|
||||
setPopupData({ isOpen, top, left, taskGroupID });
|
||||
}}
|
||||
/>
|
||||
{popupData.isOpen && (
|
||||
<PopupMenu
|
||||
title="List Actions"
|
||||
@ -259,50 +252,28 @@ const Project = () => {
|
||||
<Route
|
||||
path={`${match.path}/c/:taskID`}
|
||||
render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
|
||||
<Modal
|
||||
width={1040}
|
||||
onClose={() => {
|
||||
history.push(match.url);
|
||||
<Details
|
||||
availableMembers={availableMembers}
|
||||
projectURL={match.url}
|
||||
taskID={routeProps.match.params.taskID}
|
||||
onTaskNameChange={(updatedTask, newName) => {
|
||||
updateTaskName({ variables: { taskID: updatedTask.taskID, name: newName } });
|
||||
}}
|
||||
renderContent={() => {
|
||||
const task = listsData.tasks[routeProps.match.params.taskID];
|
||||
if (!task) {
|
||||
return <div>loading</div>;
|
||||
}
|
||||
return (
|
||||
<TaskDetails
|
||||
task={task}
|
||||
onTaskNameChange={(updatedTask, newName) => {
|
||||
updateTaskName({ variables: { taskID: updatedTask.taskID, name: newName } });
|
||||
}}
|
||||
onTaskDescriptionChange={(updatedTask, newDescription) => {
|
||||
console.log(updatedTask, newDescription);
|
||||
}}
|
||||
onDeleteTask={deletedTask => {
|
||||
setTaskDetails(initialTaskDetailsState);
|
||||
deleteTask({ variables: { taskID: deletedTask.taskID } });
|
||||
}}
|
||||
onCloseModal={() => history.push(match.url)}
|
||||
onOpenAddMemberPopup={(task, bounds) => {
|
||||
console.log(task, bounds);
|
||||
setMemberPopupData({
|
||||
isOpen: true,
|
||||
taskID: task.taskID,
|
||||
top: bounds.position.top + bounds.size.height + 10,
|
||||
left: bounds.position.left,
|
||||
});
|
||||
}}
|
||||
onOpenAddLabelPopup={(task, bounds) => {}}
|
||||
/>
|
||||
);
|
||||
onTaskDescriptionChange={(updatedTask, newDescription) => {
|
||||
updateTaskDescription({ variables: { taskID: updatedTask.taskID, description: newDescription } });
|
||||
}}
|
||||
onDeleteTask={deletedTask => {
|
||||
setTaskDetails(initialTaskDetailsState);
|
||||
deleteTask({ variables: { taskID: deletedTask.taskID } });
|
||||
}}
|
||||
onOpenAddLabelPopup={(task, bounds) => {}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <Wrapper>Error</Wrapper>;
|
||||
return <div>Error</div>;
|
||||
};
|
||||
|
||||
export default Project;
|
||||
|
@ -2,7 +2,6 @@ import React, { useState } from 'react';
|
||||
import styled from 'styled-components/macro';
|
||||
import { useGetProjectsQuery } from 'shared/generated/graphql';
|
||||
|
||||
import TopNavbar from 'App/TopNavbar';
|
||||
import ProjectGridItem from 'shared/components/ProjectGridItem';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Navbar from 'App/Navbar';
|
||||
@ -15,59 +14,42 @@ const MainContent = styled.div`
|
||||
|
||||
const ProjectGrid = styled.div`
|
||||
width: 60%;
|
||||
max-width: 780px;
|
||||
margin: 25px auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
const Wrapper = styled.div`
|
||||
font-size: 16px;
|
||||
background-color: red;
|
||||
|
||||
const ProjectLink = styled(Link)`
|
||||
flex: 1 0 33%;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const Projects = () => {
|
||||
const { loading, data } = useGetProjectsQuery();
|
||||
console.log(loading, data);
|
||||
if (loading) {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<MainContent>
|
||||
<TopNavbar />
|
||||
</MainContent>
|
||||
<span>loading</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (data) {
|
||||
const { teams } = data.organizations[0];
|
||||
const projects: Project[] = [];
|
||||
teams.forEach(team =>
|
||||
team.projects.forEach(project => {
|
||||
projects.push({
|
||||
taskGroups: [],
|
||||
projectID: project.projectID,
|
||||
teamTitle: team.name,
|
||||
name: project.name,
|
||||
color: '#aa62e3',
|
||||
});
|
||||
}),
|
||||
);
|
||||
const { projects } = data;
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<MainContent>
|
||||
<TopNavbar />
|
||||
<ProjectGrid>
|
||||
{projects.map(project => (
|
||||
<Link to={`/projects/${project.projectID}`}>
|
||||
<ProjectGridItem project={project} />
|
||||
</Link>
|
||||
))}
|
||||
</ProjectGrid>
|
||||
</MainContent>
|
||||
</>
|
||||
<ProjectGrid>
|
||||
{projects.map(project => (
|
||||
<ProjectLink key={project.projectID} to={`/projects/${project.projectID}`}>
|
||||
<ProjectGridItem project={{ ...project, teamTitle: project.team.name, taskGroups: [] }} />
|
||||
</ProjectLink>
|
||||
))}
|
||||
</ProjectGrid>
|
||||
);
|
||||
}
|
||||
return <Wrapper>Error</Wrapper>;
|
||||
return <div>Error!</div>;
|
||||
};
|
||||
|
||||
export default Projects;
|
||||
|
11
web/src/citadel.d.ts
vendored
11
web/src/citadel.d.ts
vendored
@ -1,3 +1,8 @@
|
||||
interface JWTToken {
|
||||
userId: string;
|
||||
iat: string;
|
||||
exp: string;
|
||||
}
|
||||
interface ColumnState {
|
||||
[key: string]: TaskGroup;
|
||||
}
|
||||
@ -29,9 +34,15 @@ type InnerTaskGroup = {
|
||||
position?: number;
|
||||
};
|
||||
|
||||
type ProfileIcon = {
|
||||
url: string | null;
|
||||
initials: string | null;
|
||||
};
|
||||
|
||||
type TaskUser = {
|
||||
userID: string;
|
||||
displayName: string;
|
||||
profileIcon: ProfileIcon;
|
||||
};
|
||||
|
||||
type Task = {
|
||||
|
@ -2,6 +2,7 @@ import React, { createRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import DropdownMenu from '.';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
export default {
|
||||
component: DropdownMenu,
|
||||
@ -49,7 +50,7 @@ export const Default = () => {
|
||||
Click me
|
||||
</Button>
|
||||
</Container>
|
||||
{menu.isOpen && <DropdownMenu left={menu.left} top={menu.top} />}
|
||||
{menu.isOpen && <DropdownMenu onLogout={action('on logout')} left={menu.left} top={menu.top} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -6,9 +6,10 @@ import { Separator, Container, WrapperDiamond, Wrapper, ActionsList, ActionItem,
|
||||
type DropdownMenuProps = {
|
||||
left: number;
|
||||
top: number;
|
||||
onLogout: () => void;
|
||||
};
|
||||
|
||||
const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top }) => {
|
||||
const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout }) => {
|
||||
return (
|
||||
<Container left={left} top={top}>
|
||||
<Wrapper>
|
||||
@ -18,7 +19,7 @@ const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top }) => {
|
||||
</ActionItem>
|
||||
<Separator />
|
||||
<ActionsList>
|
||||
<ActionItem>
|
||||
<ActionItem onClick={onLogout}>
|
||||
<Exit size={16} color="#c2c6dc" />
|
||||
<ActionTitle>Logout</ActionTitle>
|
||||
</ActionItem>
|
||||
|
@ -23,7 +23,7 @@ export const Default = () => {
|
||||
position: 1,
|
||||
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||
description: 'hello!',
|
||||
members: [{ userID: '1', displayName: 'Jordan Knott' }],
|
||||
members: [{ userID: '1', profileIcon: { url: null, initials: null }, displayName: 'Jordan Knott' }],
|
||||
}}
|
||||
onCancel={action('cancel')}
|
||||
onDueDateChange={action('due date change')}
|
||||
|
@ -55,7 +55,7 @@ const Login = ({ onSubmit }: LoginProps) => {
|
||||
<FormLabel htmlFor="password">
|
||||
Password
|
||||
<FormTextInput
|
||||
type="text"
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
ref={register({ required: 'Password is required' })}
|
||||
|
@ -114,19 +114,15 @@ export const MemberManagerPopup = () => {
|
||||
{popupData.isOpen && (
|
||||
<PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
|
||||
<MemberManager
|
||||
availableMembers={[{ userID: '1', displayName: 'Jordan Knott' }]}
|
||||
availableMembers={[
|
||||
{ userID: '1', displayName: 'Jordan Knott', profileIcon: { url: null, initials: null } },
|
||||
]}
|
||||
activeMembers={[]}
|
||||
onMemberChange={action('member change')}
|
||||
/>
|
||||
</PopupMenu>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
width: '60px',
|
||||
textAlign: 'center',
|
||||
margin: '25px auto',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
ref={$buttonRef}
|
||||
onClick={() => {
|
||||
if ($buttonRef && $buttonRef.current) {
|
||||
@ -162,7 +158,7 @@ export const DueDateManagerPopup = () => {
|
||||
position: 1,
|
||||
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||
description: 'hello!',
|
||||
members: [{ userID: '1', displayName: 'Jordan Knott' }],
|
||||
members: [{ userID: '1', profileIcon: { url: null, initials: null }, displayName: 'Jordan Knott' }],
|
||||
}}
|
||||
onCancel={action('cancel')}
|
||||
onDueDateChange={action('due date change')}
|
||||
|
@ -4,27 +4,22 @@ import { mixin } from 'shared/utils/styles';
|
||||
export const ProjectContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const ProjectTitle = styled.span`
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
transition: transform 0.25s ease;
|
||||
text-align: center;
|
||||
`;
|
||||
export const TeamTitle = styled.span`
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
color: #c2c6dc;
|
||||
`;
|
||||
|
||||
export const ProjectWrapper = styled.div<{ color: string }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 25px;
|
||||
border-radius: 20px;
|
||||
${mixin.boxShadowCard}
|
||||
@ -32,11 +27,10 @@ export const ProjectWrapper = styled.div<{ color: string }>`
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
margin: 0 10px;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 240px;
|
||||
height: 100px;
|
||||
transition: transform 0.25s ease;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
|
@ -35,7 +35,7 @@ export const Default = () => {
|
||||
position: 1,
|
||||
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||
description,
|
||||
members: [{ userID: '1', displayName: 'Jordan Knott' }],
|
||||
members: [{ userID: '1', profileIcon: { url: null, initials: null }, displayName: 'Jordan Knott' }],
|
||||
}}
|
||||
onTaskNameChange={action('task name change')}
|
||||
onTaskDescriptionChange={(_task, desc) => setDescription(desc)}
|
||||
|
@ -97,9 +97,9 @@ type TaskDetailsProps = {
|
||||
onTaskNameChange: (task: Task, newName: string) => void;
|
||||
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
|
||||
onDeleteTask: (task: Task) => void;
|
||||
onCloseModal: () => void;
|
||||
onOpenAddMemberPopup: (task: Task, bounds: ElementBounds) => void;
|
||||
onOpenAddLabelPopup: (task: Task, bounds: ElementBounds) => void;
|
||||
onCloseModal: () => void;
|
||||
};
|
||||
|
||||
const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
@ -112,6 +112,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
onOpenAddLabelPopup,
|
||||
}) => {
|
||||
const [editorOpen, setEditorOpen] = useState(false);
|
||||
const [description, setDescription] = useState(task.description ?? '');
|
||||
const [taskName, setTaskName] = useState(task.name);
|
||||
const handleClick = () => {
|
||||
setEditorOpen(!editorOpen);
|
||||
@ -141,6 +142,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
onOpenAddLabelPopup(task, bounds);
|
||||
}
|
||||
};
|
||||
console.log(task);
|
||||
return (
|
||||
<>
|
||||
<TaskActions>
|
||||
@ -172,9 +174,10 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<TaskDetailsLabel>Description</TaskDetailsLabel>
|
||||
{editorOpen ? (
|
||||
<DetailsEditor
|
||||
description={task.description ?? ''}
|
||||
description={description}
|
||||
onTaskDescriptionChange={newDescription => {
|
||||
setEditorOpen(false);
|
||||
setDescription(newDescription);
|
||||
onTaskDescriptionChange(task, newDescription);
|
||||
}}
|
||||
onCancel={() => {
|
||||
@ -182,7 +185,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<TaskContent description={task.description ?? ''} onEditContent={handleClick} />
|
||||
<TaskContent description={description} onEditContent={handleClick} />
|
||||
)}
|
||||
</TaskDetailsContent>
|
||||
<TaskDetailsSidebar>
|
||||
@ -190,10 +193,10 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<TaskDetailAssignees>
|
||||
{task.members &&
|
||||
task.members.map(member => {
|
||||
const initials = 'JK';
|
||||
console.log(member);
|
||||
return (
|
||||
<TaskDetailAssignee key={member.userID}>
|
||||
<ProfileIcon>{initials}</ProfileIcon>
|
||||
<ProfileIcon>{member.profileIcon.initials ?? ''}</ProfileIcon>
|
||||
</TaskDetailAssignee>
|
||||
);
|
||||
})}
|
||||
|
@ -37,8 +37,14 @@ export const Default = () => {
|
||||
<>
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
<TopNavbar onNotificationClick={action('notifications click')} onProfileClick={onClick} />
|
||||
{menu.isOpen && <DropdownMenu left={menu.left} top={menu.top} />}
|
||||
<TopNavbar
|
||||
firstName="Jordan"
|
||||
lastName="Knott"
|
||||
initials="JK"
|
||||
onNotificationClick={action('notifications click')}
|
||||
onProfileClick={onClick}
|
||||
/>
|
||||
{menu.isOpen && <DropdownMenu onLogout={action('on logout')} left={menu.left} top={menu.top} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -19,8 +19,11 @@ import {
|
||||
type NavBarProps = {
|
||||
onProfileClick: (bottom: number, right: number) => void;
|
||||
onNotificationClick: () => void;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
initials: string;
|
||||
};
|
||||
const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick }) => {
|
||||
const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick, firstName, lastName, initials }) => {
|
||||
const $profileRef: any = useRef(null);
|
||||
const handleProfileClick = () => {
|
||||
console.log('click');
|
||||
@ -45,11 +48,13 @@ const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick })
|
||||
</NotificationContainer>
|
||||
<ProfileContainer>
|
||||
<ProfileNameWrapper>
|
||||
<ProfileNamePrimary>Jordan Knott</ProfileNamePrimary>
|
||||
<ProfileNamePrimary>
|
||||
{firstName} {lastName}
|
||||
</ProfileNamePrimary>
|
||||
<ProfileNameSecondary>Manager</ProfileNameSecondary>
|
||||
</ProfileNameWrapper>
|
||||
<ProfileIcon ref={$profileRef} onClick={handleProfileClick}>
|
||||
JK
|
||||
{initials}
|
||||
</ProfileIcon>
|
||||
</ProfileContainer>
|
||||
</GlobalActions>
|
||||
|
@ -15,6 +15,27 @@ export type Scalars = {
|
||||
|
||||
|
||||
|
||||
export type TaskLabel = {
|
||||
__typename?: 'TaskLabel';
|
||||
taskLabelID: Scalars['ID'];
|
||||
labelColorID: Scalars['UUID'];
|
||||
colorHex: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ProfileIcon = {
|
||||
__typename?: 'ProfileIcon';
|
||||
url?: Maybe<Scalars['String']>;
|
||||
initials?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ProjectMember = {
|
||||
__typename?: 'ProjectMember';
|
||||
userID: Scalars['ID'];
|
||||
firstName: Scalars['String'];
|
||||
lastName: Scalars['String'];
|
||||
profileIcon: ProfileIcon;
|
||||
};
|
||||
|
||||
export type RefreshToken = {
|
||||
__typename?: 'RefreshToken';
|
||||
tokenId: Scalars['ID'];
|
||||
@ -28,16 +49,10 @@ export type UserAccount = {
|
||||
userID: Scalars['ID'];
|
||||
email: Scalars['String'];
|
||||
createdAt: Scalars['Time'];
|
||||
displayName: Scalars['String'];
|
||||
firstName: Scalars['String'];
|
||||
lastName: Scalars['String'];
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Organization = {
|
||||
__typename?: 'Organization';
|
||||
organizationID: Scalars['ID'];
|
||||
createdAt: Scalars['Time'];
|
||||
name: Scalars['String'];
|
||||
teams: Array<Team>;
|
||||
profileIcon: ProfileIcon;
|
||||
};
|
||||
|
||||
export type Team = {
|
||||
@ -45,16 +60,17 @@ export type Team = {
|
||||
teamID: Scalars['ID'];
|
||||
createdAt: Scalars['Time'];
|
||||
name: Scalars['String'];
|
||||
projects: Array<Project>;
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
__typename?: 'Project';
|
||||
projectID: Scalars['ID'];
|
||||
teamID: Scalars['String'];
|
||||
createdAt: Scalars['Time'];
|
||||
name: Scalars['String'];
|
||||
team: Team;
|
||||
owner: ProjectMember;
|
||||
taskGroups: Array<TaskGroup>;
|
||||
members: Array<ProjectMember>;
|
||||
};
|
||||
|
||||
export type TaskGroup = {
|
||||
@ -74,6 +90,9 @@ export type Task = {
|
||||
createdAt: Scalars['Time'];
|
||||
name: Scalars['String'];
|
||||
position: Scalars['Float'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
assigned: Array<ProjectMember>;
|
||||
labels: Array<TaskLabel>;
|
||||
};
|
||||
|
||||
export type ProjectsFilter = {
|
||||
@ -88,15 +107,19 @@ export type FindProject = {
|
||||
projectId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type FindTask = {
|
||||
taskID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
organizations: Array<Organization>;
|
||||
users: Array<UserAccount>;
|
||||
findUser: UserAccount;
|
||||
findProject: Project;
|
||||
teams: Array<Team>;
|
||||
findTask: Task;
|
||||
projects: Array<Project>;
|
||||
taskGroups: Array<TaskGroup>;
|
||||
me: UserAccount;
|
||||
};
|
||||
|
||||
|
||||
@ -110,6 +133,11 @@ export type QueryFindProjectArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindTaskArgs = {
|
||||
input: FindTask;
|
||||
};
|
||||
|
||||
|
||||
export type QueryProjectsArgs = {
|
||||
input?: Maybe<ProjectsFilter>;
|
||||
};
|
||||
@ -121,7 +149,8 @@ export type NewRefreshToken = {
|
||||
export type NewUserAccount = {
|
||||
username: Scalars['String'];
|
||||
email: Scalars['String'];
|
||||
displayName: Scalars['String'];
|
||||
firstName: Scalars['String'];
|
||||
lastName: Scalars['String'];
|
||||
password: Scalars['String'];
|
||||
};
|
||||
|
||||
@ -131,7 +160,8 @@ export type NewTeam = {
|
||||
};
|
||||
|
||||
export type NewProject = {
|
||||
teamID: Scalars['String'];
|
||||
userID: Scalars['UUID'];
|
||||
teamID: Scalars['UUID'];
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
@ -141,10 +171,6 @@ export type NewTaskGroup = {
|
||||
position: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type NewOrganization = {
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type LogoutUser = {
|
||||
userID: Scalars['String'];
|
||||
};
|
||||
@ -191,20 +217,43 @@ export type DeleteTaskGroupPayload = {
|
||||
taskGroup: TaskGroup;
|
||||
};
|
||||
|
||||
export type AssignTaskInput = {
|
||||
taskID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type UpdateTaskDescriptionInput = {
|
||||
taskID: Scalars['UUID'];
|
||||
description: Scalars['String'];
|
||||
};
|
||||
|
||||
export type AddTaskLabelInput = {
|
||||
taskID: Scalars['UUID'];
|
||||
labelColorID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type RemoveTaskLabelInput = {
|
||||
taskID: Scalars['UUID'];
|
||||
taskLabelID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
createRefreshToken: RefreshToken;
|
||||
createUserAccount: UserAccount;
|
||||
createOrganization: Organization;
|
||||
createTeam: Team;
|
||||
createProject: Project;
|
||||
createTaskGroup: TaskGroup;
|
||||
updateTaskGroupLocation: TaskGroup;
|
||||
deleteTaskGroup: DeleteTaskGroupPayload;
|
||||
addTaskLabel: Task;
|
||||
removeTaskLabel: Task;
|
||||
createTask: Task;
|
||||
updateTaskDescription: Task;
|
||||
updateTaskLocation: Task;
|
||||
updateTaskName: Task;
|
||||
deleteTask: DeleteTaskPayload;
|
||||
assignTask: Task;
|
||||
logoutUser: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
@ -219,11 +268,6 @@ export type MutationCreateUserAccountArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateOrganizationArgs = {
|
||||
input: NewOrganization;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateTeamArgs = {
|
||||
input: NewTeam;
|
||||
};
|
||||
@ -249,11 +293,26 @@ export type MutationDeleteTaskGroupArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationAddTaskLabelArgs = {
|
||||
input?: Maybe<AddTaskLabelInput>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationRemoveTaskLabelArgs = {
|
||||
input?: Maybe<RemoveTaskLabelInput>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateTaskArgs = {
|
||||
input: NewTask;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateTaskDescriptionArgs = {
|
||||
input: UpdateTaskDescriptionInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateTaskLocationArgs = {
|
||||
input: NewTaskLocation;
|
||||
};
|
||||
@ -269,10 +328,33 @@ export type MutationDeleteTaskArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationAssignTaskArgs = {
|
||||
input?: Maybe<AssignTaskInput>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationLogoutUserArgs = {
|
||||
input: LogoutUser;
|
||||
};
|
||||
|
||||
export type AssignTaskMutationVariables = {
|
||||
taskID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
export type AssignTaskMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { assignTask: (
|
||||
{ __typename?: 'Task' }
|
||||
& Pick<Task, 'taskID'>
|
||||
& { assigned: Array<(
|
||||
{ __typename?: 'ProjectMember' }
|
||||
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type CreateTaskMutationVariables = {
|
||||
taskGroupID: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
@ -351,36 +433,92 @@ export type FindProjectQuery = (
|
||||
& { findProject: (
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'name'>
|
||||
& { taskGroups: Array<(
|
||||
& { members: Array<(
|
||||
{ __typename?: 'ProjectMember' }
|
||||
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
|
||||
& { profileIcon: (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'url' | 'initials'>
|
||||
) }
|
||||
)>, taskGroups: Array<(
|
||||
{ __typename?: 'TaskGroup' }
|
||||
& Pick<TaskGroup, 'taskGroupID' | 'name' | 'position'>
|
||||
& { tasks: Array<(
|
||||
{ __typename?: 'Task' }
|
||||
& Pick<Task, 'taskID' | 'name' | 'position'>
|
||||
& Pick<Task, 'taskID' | 'name' | 'position' | 'description'>
|
||||
)> }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type FindTaskQueryVariables = {
|
||||
taskID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
export type FindTaskQuery = (
|
||||
{ __typename?: 'Query' }
|
||||
& { findTask: (
|
||||
{ __typename?: 'Task' }
|
||||
& Pick<Task, 'taskID' | 'name' | 'description' | 'position'>
|
||||
& { taskGroup: (
|
||||
{ __typename?: 'TaskGroup' }
|
||||
& Pick<TaskGroup, 'taskGroupID'>
|
||||
), assigned: Array<(
|
||||
{ __typename?: 'ProjectMember' }
|
||||
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
|
||||
& { profileIcon: (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'url' | 'initials'>
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type GetProjectsQueryVariables = {};
|
||||
|
||||
|
||||
export type GetProjectsQuery = (
|
||||
{ __typename?: 'Query' }
|
||||
& { organizations: Array<(
|
||||
{ __typename?: 'Organization' }
|
||||
& Pick<Organization, 'name'>
|
||||
& { teams: Array<(
|
||||
& { projects: Array<(
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'projectID' | 'name'>
|
||||
& { team: (
|
||||
{ __typename?: 'Team' }
|
||||
& Pick<Team, 'name'>
|
||||
& { projects: Array<(
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'name' | 'projectID'>
|
||||
)> }
|
||||
)> }
|
||||
& Pick<Team, 'teamID' | 'name'>
|
||||
) }
|
||||
)> }
|
||||
);
|
||||
|
||||
export type MeQueryVariables = {};
|
||||
|
||||
|
||||
export type MeQuery = (
|
||||
{ __typename?: 'Query' }
|
||||
& { me: (
|
||||
{ __typename?: 'UserAccount' }
|
||||
& Pick<UserAccount, 'firstName' | 'lastName'>
|
||||
& { profileIcon: (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'initials'>
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type UpdateTaskDescriptionMutationVariables = {
|
||||
taskID: Scalars['UUID'];
|
||||
description: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type UpdateTaskDescriptionMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { updateTaskDescription: (
|
||||
{ __typename?: 'Task' }
|
||||
& Pick<Task, 'taskID'>
|
||||
) }
|
||||
);
|
||||
|
||||
export type UpdateTaskGroupLocationMutationVariables = {
|
||||
taskGroupID: Scalars['UUID'];
|
||||
position: Scalars['Float'];
|
||||
@ -425,6 +563,44 @@ export type UpdateTaskNameMutation = (
|
||||
);
|
||||
|
||||
|
||||
export const AssignTaskDocument = gql`
|
||||
mutation assignTask($taskID: UUID!, $userID: UUID!) {
|
||||
assignTask(input: {taskID: $taskID, userID: $userID}) {
|
||||
assigned {
|
||||
userID
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
taskID
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type AssignTaskMutationFn = ApolloReactCommon.MutationFunction<AssignTaskMutation, AssignTaskMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useAssignTaskMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useAssignTaskMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useAssignTaskMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [assignTaskMutation, { data, loading, error }] = useAssignTaskMutation({
|
||||
* variables: {
|
||||
* taskID: // value for 'taskID'
|
||||
* userID: // value for 'userID'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useAssignTaskMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<AssignTaskMutation, AssignTaskMutationVariables>) {
|
||||
return ApolloReactHooks.useMutation<AssignTaskMutation, AssignTaskMutationVariables>(AssignTaskDocument, baseOptions);
|
||||
}
|
||||
export type AssignTaskMutationHookResult = ReturnType<typeof useAssignTaskMutation>;
|
||||
export type AssignTaskMutationResult = ApolloReactCommon.MutationResult<AssignTaskMutation>;
|
||||
export type AssignTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<AssignTaskMutation, AssignTaskMutationVariables>;
|
||||
export const CreateTaskDocument = gql`
|
||||
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
|
||||
createTask(input: {taskGroupID: $taskGroupID, name: $name, position: $position}) {
|
||||
@ -576,6 +752,15 @@ export const FindProjectDocument = gql`
|
||||
query findProject($projectId: String!) {
|
||||
findProject(input: {projectId: $projectId}) {
|
||||
name
|
||||
members {
|
||||
userID
|
||||
firstName
|
||||
lastName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
}
|
||||
}
|
||||
taskGroups {
|
||||
taskGroupID
|
||||
name
|
||||
@ -584,6 +769,7 @@ export const FindProjectDocument = gql`
|
||||
taskID
|
||||
name
|
||||
position
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -615,16 +801,62 @@ export function useFindProjectLazyQuery(baseOptions?: ApolloReactHooks.LazyQuery
|
||||
export type FindProjectQueryHookResult = ReturnType<typeof useFindProjectQuery>;
|
||||
export type FindProjectLazyQueryHookResult = ReturnType<typeof useFindProjectLazyQuery>;
|
||||
export type FindProjectQueryResult = ApolloReactCommon.QueryResult<FindProjectQuery, FindProjectQueryVariables>;
|
||||
export const FindTaskDocument = gql`
|
||||
query findTask($taskID: UUID!) {
|
||||
findTask(input: {taskID: $taskID}) {
|
||||
taskID
|
||||
name
|
||||
description
|
||||
position
|
||||
taskGroup {
|
||||
taskGroupID
|
||||
}
|
||||
assigned {
|
||||
userID
|
||||
firstName
|
||||
lastName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useFindTaskQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useFindTaskQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useFindTaskQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useFindTaskQuery({
|
||||
* variables: {
|
||||
* taskID: // value for 'taskID'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useFindTaskQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<FindTaskQuery, FindTaskQueryVariables>) {
|
||||
return ApolloReactHooks.useQuery<FindTaskQuery, FindTaskQueryVariables>(FindTaskDocument, baseOptions);
|
||||
}
|
||||
export function useFindTaskLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<FindTaskQuery, FindTaskQueryVariables>) {
|
||||
return ApolloReactHooks.useLazyQuery<FindTaskQuery, FindTaskQueryVariables>(FindTaskDocument, baseOptions);
|
||||
}
|
||||
export type FindTaskQueryHookResult = ReturnType<typeof useFindTaskQuery>;
|
||||
export type FindTaskLazyQueryHookResult = ReturnType<typeof useFindTaskLazyQuery>;
|
||||
export type FindTaskQueryResult = ApolloReactCommon.QueryResult<FindTaskQuery, FindTaskQueryVariables>;
|
||||
export const GetProjectsDocument = gql`
|
||||
query getProjects {
|
||||
organizations {
|
||||
projects {
|
||||
projectID
|
||||
name
|
||||
teams {
|
||||
team {
|
||||
teamID
|
||||
name
|
||||
projects {
|
||||
name
|
||||
projectID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -654,6 +886,75 @@ export function useGetProjectsLazyQuery(baseOptions?: ApolloReactHooks.LazyQuery
|
||||
export type GetProjectsQueryHookResult = ReturnType<typeof useGetProjectsQuery>;
|
||||
export type GetProjectsLazyQueryHookResult = ReturnType<typeof useGetProjectsLazyQuery>;
|
||||
export type GetProjectsQueryResult = ApolloReactCommon.QueryResult<GetProjectsQuery, GetProjectsQueryVariables>;
|
||||
export const MeDocument = gql`
|
||||
query me {
|
||||
me {
|
||||
firstName
|
||||
lastName
|
||||
profileIcon {
|
||||
initials
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useMeQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useMeQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useMeQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useMeQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useMeQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<MeQuery, MeQueryVariables>) {
|
||||
return ApolloReactHooks.useQuery<MeQuery, MeQueryVariables>(MeDocument, baseOptions);
|
||||
}
|
||||
export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<MeQuery, MeQueryVariables>) {
|
||||
return ApolloReactHooks.useLazyQuery<MeQuery, MeQueryVariables>(MeDocument, baseOptions);
|
||||
}
|
||||
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
|
||||
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
|
||||
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
|
||||
export const UpdateTaskDescriptionDocument = gql`
|
||||
mutation updateTaskDescription($taskID: UUID!, $description: String!) {
|
||||
updateTaskDescription(input: {taskID: $taskID, description: $description}) {
|
||||
taskID
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UpdateTaskDescriptionMutationFn = ApolloReactCommon.MutationFunction<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateTaskDescriptionMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateTaskDescriptionMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateTaskDescriptionMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [updateTaskDescriptionMutation, { data, loading, error }] = useUpdateTaskDescriptionMutation({
|
||||
* variables: {
|
||||
* taskID: // value for 'taskID'
|
||||
* description: // value for 'description'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateTaskDescriptionMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>) {
|
||||
return ApolloReactHooks.useMutation<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>(UpdateTaskDescriptionDocument, baseOptions);
|
||||
}
|
||||
export type UpdateTaskDescriptionMutationHookResult = ReturnType<typeof useUpdateTaskDescriptionMutation>;
|
||||
export type UpdateTaskDescriptionMutationResult = ApolloReactCommon.MutationResult<UpdateTaskDescriptionMutation>;
|
||||
export type UpdateTaskDescriptionMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>;
|
||||
export const UpdateTaskGroupLocationDocument = gql`
|
||||
mutation updateTaskGroupLocation($taskGroupID: UUID!, $position: Float!) {
|
||||
updateTaskGroupLocation(input: {taskGroupID: $taskGroupID, position: $position}) {
|
||||
|
10
web/src/shared/graphql/assignTask.graphqls
Normal file
10
web/src/shared/graphql/assignTask.graphqls
Normal file
@ -0,0 +1,10 @@
|
||||
mutation assignTask($taskID: UUID!, $userID: UUID!) {
|
||||
assignTask(input: {taskID: $taskID, userID: $userID}) {
|
||||
assigned {
|
||||
userID
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
taskID
|
||||
}
|
||||
}
|
@ -1,6 +1,15 @@
|
||||
query findProject($projectId: String!) {
|
||||
findProject(input: { projectId: $projectId }) {
|
||||
name
|
||||
members {
|
||||
userID
|
||||
firstName
|
||||
lastName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
}
|
||||
}
|
||||
taskGroups {
|
||||
taskGroupID
|
||||
name
|
||||
@ -9,6 +18,7 @@ query findProject($projectId: String!) {
|
||||
taskID
|
||||
name
|
||||
position
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
web/src/shared/graphql/findTask.graphqls
Normal file
20
web/src/shared/graphql/findTask.graphqls
Normal file
@ -0,0 +1,20 @@
|
||||
query findTask($taskID: UUID!) {
|
||||
findTask(input: {taskID: $taskID}) {
|
||||
taskID
|
||||
name
|
||||
description
|
||||
position
|
||||
taskGroup {
|
||||
taskGroupID
|
||||
}
|
||||
assigned {
|
||||
userID
|
||||
firstName
|
||||
lastName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
query getProjects {
|
||||
organizations {
|
||||
projects {
|
||||
projectID
|
||||
name
|
||||
teams {
|
||||
team {
|
||||
teamID
|
||||
name
|
||||
projects {
|
||||
name
|
||||
projectID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
web/src/shared/graphql/me.graphqls
Normal file
9
web/src/shared/graphql/me.graphqls
Normal file
@ -0,0 +1,9 @@
|
||||
query me {
|
||||
me {
|
||||
firstName
|
||||
lastName
|
||||
profileIcon {
|
||||
initials
|
||||
}
|
||||
}
|
||||
}
|
5
web/src/shared/graphql/updateTaskDescription.graphqls
Normal file
5
web/src/shared/graphql/updateTaskDescription.graphqls
Normal file
@ -0,0 +1,5 @@
|
||||
mutation updateTaskDescription($taskID: UUID!, $description: String!) {
|
||||
updateTaskDescription(input: {taskID: $taskID, description: $description}) {
|
||||
taskID
|
||||
}
|
||||
}
|
@ -3104,6 +3104,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
|
||||
integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
|
||||
|
||||
"@types/jwt-decode@^2.2.1":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jwt-decode/-/jwt-decode-2.2.1.tgz#afdf5c527fcfccbd4009b5fd02d1e18241f2d2f2"
|
||||
integrity sha512-aWw2YTtAdT7CskFyxEX2K21/zSDStuf/ikI3yBqmwpwJF0pS+/IX5DWv+1UFffZIbruP6cnT9/LAJV1gFwAT1A==
|
||||
|
||||
"@types/lodash@^4.14.149":
|
||||
version "4.14.149"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
|
||||
@ -10202,6 +10207,11 @@ jws@^3.2.2:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jwt-decode@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79"
|
||||
integrity sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=
|
||||
|
||||
killable@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
|
||||
|
Reference in New Issue
Block a user