feature: add first time install process

This commit is contained in:
Jordan Knott
2020-07-16 19:40:23 -05:00
parent 90515f6aa4
commit 2cf6be082c
42 changed files with 1834 additions and 1053 deletions

View File

@ -1,12 +1,12 @@
import React, {useState, useRef, useContext, useEffect} from 'react';
import {MENU_TYPES} from 'shared/components/TopNavbar';
import React, { useState, useRef, useContext, useEffect } from 'react';
import { MENU_TYPES } from 'shared/components/TopNavbar';
import updateApolloCache from 'shared/utils/cache';
import GlobalTopNavbar, {ProjectPopup} from 'App/TopNavbar';
import styled, {css} from 'styled-components/macro';
import {Bolt, ToggleOn, Tags, CheckCircle, Sort, Filter} from 'shared/icons';
import LabelManagerEditor from '../LabelManagerEditor'
import {usePopup, Popup} from 'shared/components/PopupMenu';
import {useParams, Route, useRouteMatch, useHistory, RouteComponentProps, useLocation} from 'react-router-dom';
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
import styled, { css } from 'styled-components/macro';
import { Bolt, ToggleOn, Tags, CheckCircle, Sort, Filter } from 'shared/icons';
import LabelManagerEditor from '../LabelManagerEditor';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps, useLocation } from 'react-router-dom';
import {
useSetProjectOwnerMutation,
useUpdateProjectMemberRoleMutation,
@ -61,7 +61,7 @@ const ProjectActions = styled.div`
align-items: center;
`;
const ProjectAction = styled.div<{disabled?: boolean}>`
const ProjectAction = styled.div<{ disabled?: boolean }>`
cursor: pointer;
display: flex;
align-items: center;
@ -103,18 +103,21 @@ const initialQuickCardEditorState: QuickCardEditorState = {
};
type ProjectBoardProps = {
onCardLabelClick: () => void;
cardLabelVariant: CardLabelVariant;
projectID: string;
};
const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick, cardLabelVariant }) => {
const [assignTask] = useAssignTaskMutation();
const [unassignTask] = useUnassignTaskMutation();
const $labelsRef = useRef<HTMLDivElement>(null);
const match = useRouteMatch();
const labelsRef = useRef<Array<ProjectLabel>>([]);
const {showPopup, hidePopup} = usePopup();
const { showPopup, hidePopup } = usePopup();
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
const {userID} = useContext(UserIDContext);
const { userID } = useContext(UserIDContext);
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({});
const history = useHistory();
const [deleteTaskGroup] = useDeleteTaskGroupMutation({
@ -128,7 +131,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
(taskGroup: TaskGroup) => taskGroup.id !== deletedTaskGroupData.data.deleteTaskGroup.taskGroup.id,
);
}),
{projectId: projectID},
{ projectId: projectID },
);
},
});
@ -140,13 +143,13 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
FindProjectDocument,
cache =>
produce(cache, draftCache => {
const {taskGroups} = cache.findProject;
const { taskGroups } = cache.findProject;
const idx = taskGroups.findIndex(taskGroup => taskGroup.id === newTaskData.data.createTask.taskGroup.id);
if (idx !== -1) {
draftCache.findProject.taskGroups[idx].tasks.push({...newTaskData.data.createTask});
draftCache.findProject.taskGroups[idx].tasks.push({ ...newTaskData.data.createTask });
}
}),
{projectId: projectID},
{ projectId: projectID },
);
},
});
@ -156,17 +159,18 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
updateApolloCache<FindProjectQuery>(
client,
FindProjectDocument,
cache => produce(cache, draftCache => {
draftCache.findProject.taskGroups.push({...newTaskGroupData.data.createTaskGroup, tasks: []});
}),
{projectId: projectID},
cache =>
produce(cache, draftCache => {
draftCache.findProject.taskGroups.push({ ...newTaskGroupData.data.createTaskGroup, tasks: [] });
}),
{ projectId: projectID },
);
},
});
const [updateTaskGroupName] = useUpdateTaskGroupNameMutation({});
const {loading, data} = useFindProjectQuery({
variables: {projectId: projectID},
const { loading, data } = useFindProjectQuery({
variables: { projectId: projectID },
});
const [updateTaskDueDate] = useUpdateTaskDueDateMutation();
@ -178,9 +182,9 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
FindProjectDocument,
cache =>
produce(cache, draftCache => {
const {previousTaskGroupID, task} = newTask.data.updateTaskLocation;
const { previousTaskGroupID, task } = newTask.data.updateTaskLocation;
if (previousTaskGroupID !== task.taskGroup.id) {
const {taskGroups} = cache.findProject;
const { taskGroups } = cache.findProject;
const oldTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === previousTaskGroupID);
const newTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === task.taskGroup.id);
if (oldTaskGroupIdx !== -1 && newTaskGroupIdx !== -1) {
@ -189,12 +193,12 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
);
draftCache.findProject.taskGroups[newTaskGroupIdx].tasks = [
...taskGroups[newTaskGroupIdx].tasks,
{...task},
{ ...task },
];
}
}
}),
{projectId: projectID},
{ projectId: projectID },
);
},
});
@ -222,7 +226,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
console.log(`position ${position}`);
createTask({
variables: {taskGroupID, name, position},
variables: { taskGroupID, name, position },
optimisticResponse: {
__typename: 'Mutation',
createTask: {
@ -258,7 +262,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
if (lastColumn) {
position = lastColumn.position * 2 + 1;
}
createTaskGroup({variables: {projectID, name: listName, position}});
createTaskGroup({ variables: { projectID, name: listName, position } });
}
};
@ -341,6 +345,8 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
onTaskClick={task => {
history.push(`${match.url}/c/${task.id}`);
}}
onCardLabelClick={onCardLabelClick}
cardLabelVariant={cardLabelVariant}
onTaskDrop={(droppedTask, previousTaskGroupID) => {
updateTaskLocation({
variables: {
@ -370,7 +376,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
}}
onTaskGroupDrop={droppedTaskGroup => {
updateTaskGroupLocation({
variables: {taskGroupID: droppedTaskGroup.id, position: droppedTaskGroup.position},
variables: { taskGroupID: droppedTaskGroup.id, position: droppedTaskGroup.position },
optimisticResponse: {
__typename: 'Mutation',
updateTaskGroupLocation: {
@ -400,7 +406,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
}
}}
onChangeTaskGroupName={(taskGroupID, name) => {
updateTaskGroupName({variables: {taskGroupID, name}});
updateTaskGroupName({ variables: { taskGroupID, name } });
}}
onQuickEditorOpen={onQuickEditorOpen}
onExtraMenuOpen={(taskGroupID: string, $targetRef: any) => {
@ -410,7 +416,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
<ListActions
taskGroupID={taskGroupID}
onArchiveTaskGroup={tgID => {
deleteTaskGroup({variables: {taskGroupID: tgID}});
deleteTaskGroup({ variables: { taskGroupID: tgID } });
hidePopup();
}}
/>
@ -423,7 +429,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
task={currentQuickTask}
onCloseEditor={() => setQuickCardEditor(initialQuickCardEditorState)}
onEditCard={(_taskGroupID: string, taskID: string, cardName: string) => {
updateTaskName({variables: {taskID, name: cardName}});
updateTaskName({ variables: { taskID, name: cardName } });
}}
onOpenMembersPopup={($targetRef, task) => {
showPopup(
@ -434,9 +440,9 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
activeMembers={task.assigned ?? []}
onMemberChange={(member, isActive) => {
if (isActive) {
assignTask({variables: {taskID: task.id, userID: userID ?? ''}});
assignTask({ variables: { taskID: task.id, userID: userID ?? '' } });
} else {
unassignTask({variables: {taskID: task.id, userID: userID ?? ''}});
unassignTask({ variables: { taskID: task.id, userID: userID ?? '' } });
}
}}
/>
@ -464,7 +470,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
$targetRef,
<LabelManagerEditor
onLabelToggle={labelID => {
toggleTaskLabel({variables: {taskID: task.id, projectLabelID: labelID}});
toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } });
}}
labelColors={data.labelColors}
labels={labelsRef}
@ -475,7 +481,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
}}
onArchiveCard={(_listId: string, cardId: string) =>
deleteTask({
variables: {taskID: cardId},
variables: { taskID: cardId },
update: client => {
updateApolloCache<FindProjectQuery>(
client,
@ -487,7 +493,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
tasks: taskGroup.tasks.filter(t => t.id !== cardId),
}));
}),
{projectId: projectID},
{ projectId: projectID },
);
},
})
@ -499,11 +505,11 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
<DueDateManager
task={task}
onRemoveDueDate={t => {
updateTaskDueDate({variables: {taskID: t.id, dueDate: null}});
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null } });
hidePopup();
}}
onDueDateChange={(t, newDueDate) => {
updateTaskDueDate({variables: {taskID: t.id, dueDate: newDueDate}});
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup();
}}
onCancel={() => {}}
@ -512,7 +518,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({projectID}) => {
);
}}
onToggleComplete={task => {
setTaskComplete({variables: {taskID: task.id, complete: !task.complete}});
setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } });
}}
target={quickCardEditor.target}
/>

View File

@ -5,22 +5,22 @@ import PopupMenu, { Popup, usePopup } from 'shared/components/PopupMenu';
import MemberManager from 'shared/components/MemberManager';
import { useRouteMatch, useHistory } from 'react-router';
import {
useDeleteTaskChecklistMutation,
useUpdateTaskChecklistNameMutation,
useUpdateTaskChecklistItemLocationMutation,
useCreateTaskChecklistMutation,
useFindTaskQuery,
useUpdateTaskDueDateMutation,
useSetTaskCompleteMutation,
useAssignTaskMutation,
useUnassignTaskMutation,
useSetTaskChecklistItemCompleteMutation,
useUpdateTaskChecklistLocationMutation,
useDeleteTaskChecklistItemMutation,
useUpdateTaskChecklistItemNameMutation,
useCreateTaskChecklistItemMutation,
FindTaskDocument,
FindTaskQuery,
useDeleteTaskChecklistMutation,
useUpdateTaskChecklistNameMutation,
useUpdateTaskChecklistItemLocationMutation,
useCreateTaskChecklistMutation,
useFindTaskQuery,
useUpdateTaskDueDateMutation,
useSetTaskCompleteMutation,
useAssignTaskMutation,
useUnassignTaskMutation,
useSetTaskChecklistItemCompleteMutation,
useUpdateTaskChecklistLocationMutation,
useDeleteTaskChecklistItemMutation,
useUpdateTaskChecklistItemNameMutation,
useCreateTaskChecklistItemMutation,
FindTaskDocument,
FindTaskQuery,
} from 'shared/generated/graphql';
import UserIDContext from 'App/context';
import MiniProfile from 'shared/components/MiniProfile';
@ -33,23 +33,23 @@ import { useForm } from 'react-hook-form';
import updateApolloCache from 'shared/utils/cache';
const calculateChecklistBadge = (checklists: Array<TaskChecklist>) => {
const total = checklists.reduce((prev: any, next: any) => {
return (
prev +
next.items.reduce((innerPrev: any, _item: any) => {
return innerPrev + 1;
}, 0)
);
}, 0);
const complete = checklists.reduce(
(prev: any, next: any) =>
prev +
next.items.reduce((innerPrev: any, item: any) => {
return innerPrev + (item.complete ? 1 : 0);
}, 0),
0,
const total = checklists.reduce((prev: any, next: any) => {
return (
prev +
next.items.reduce((innerPrev: any, _item: any) => {
return innerPrev + 1;
}, 0)
);
return { total, complete };
}, 0);
const complete = checklists.reduce(
(prev: any, next: any) =>
prev +
next.items.reduce((innerPrev: any, item: any) => {
return innerPrev + (item.complete ? 1 : 0);
}, 0),
0,
);
return { total, complete };
};
const DeleteChecklistButton = styled(Button)`
@ -58,7 +58,7 @@ const DeleteChecklistButton = styled(Button)`
margin-top: 8px;
`;
type CreateChecklistData = {
name: string;
name: string;
};
const CreateChecklistForm = styled.form`
display: flex;
@ -80,437 +80,441 @@ const InputError = styled.span`
font-size: 12px;
`;
type CreateChecklistPopupProps = {
onCreateChecklist: (data: CreateChecklistData) => void;
onCreateChecklist: (data: CreateChecklistData) => void;
};
const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChecklist }) => {
const { register, handleSubmit, errors } = useForm<CreateChecklistData>();
const createUser = (data: CreateChecklistData) => {
onCreateChecklist(data);
};
console.log(errors);
return (
<CreateChecklistForm onSubmit={handleSubmit(createUser)}>
<CreateChecklistInput
floatingLabel
value="Checklist"
width="100%"
label="Name"
id="name"
name="name"
variant="alternate"
ref={register({ required: 'Checklist name is required' })}
/>
<CreateChecklistButton type="submit">Create</CreateChecklistButton>
</CreateChecklistForm>
);
const { register, handleSubmit, errors } = useForm<CreateChecklistData>();
const createUser = (data: CreateChecklistData) => {
onCreateChecklist(data);
};
console.log(errors);
return (
<CreateChecklistForm onSubmit={handleSubmit(createUser)}>
<CreateChecklistInput
floatingLabel
defaultValue="Checklist"
width="100%"
label="Name"
id="name"
name="name"
variant="alternate"
ref={register({ required: 'Checklist name is required' })}
/>
<CreateChecklistButton type="submit">Create</CreateChecklistButton>
</CreateChecklistForm>
);
};
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, $targetRef: React.RefObject<HTMLElement>) => void;
availableMembers: Array<TaskUser>;
refreshCache: () => void;
taskID: string;
projectURL: string;
onTaskNameChange: (task: Task, newName: string) => void;
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
onDeleteTask: (task: Task) => void;
onOpenAddLabelPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
availableMembers: Array<TaskUser>;
refreshCache: () => void;
};
const initialMemberPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
const Details: React.FC<DetailsProps> = ({
projectURL,
taskID,
onTaskNameChange,
onTaskDescriptionChange,
onDeleteTask,
onOpenAddLabelPopup,
availableMembers,
refreshCache,
projectURL,
taskID,
onTaskNameChange,
onTaskDescriptionChange,
onDeleteTask,
onOpenAddLabelPopup,
availableMembers,
refreshCache,
}) => {
const { userID } = useContext(UserIDContext);
const { showPopup, hidePopup } = usePopup();
const history = useHistory();
const match = useRouteMatch();
const [currentMemberTask, setCurrentMemberTask] = useState('');
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const [updateTaskChecklistLocation] = useUpdateTaskChecklistLocationMutation();
const [updateTaskChecklistItemLocation] = useUpdateTaskChecklistItemLocationMutation({
update: (client, response) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const { prevChecklistID, checklistID, checklistItem } = response.data.updateTaskChecklistItemLocation;
console.log(`${checklistID} !== ${prevChecklistID}`);
if (checklistID !== prevChecklistID) {
const oldIdx = cache.findTask.checklists.findIndex(c => c.id === prevChecklistID);
const newIdx = cache.findTask.checklists.findIndex(c => c.id === checklistID);
console.log(`oldIdx ${oldIdx} newIdx ${newIdx}`);
if (oldIdx > -1 && newIdx > -1) {
const item = cache.findTask.checklists[oldIdx].items.find(item => item.id === checklistItem.id);
console.log(item);
if (item) {
draftCache.findTask.checklists[oldIdx].items = cache.findTask.checklists[oldIdx].items.filter(
i => i.id !== checklistItem.id,
);
draftCache.findTask.checklists[newIdx].items.push({
...item,
position: checklistItem.position,
taskChecklistID: checklistID,
});
}
}
}
}),
{ taskID },
const { userID } = useContext(UserIDContext);
const { showPopup, hidePopup } = usePopup();
const history = useHistory();
const match = useRouteMatch();
const [currentMemberTask, setCurrentMemberTask] = useState('');
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const [updateTaskChecklistLocation] = useUpdateTaskChecklistLocationMutation();
const [updateTaskChecklistItemLocation] = useUpdateTaskChecklistItemLocationMutation({
update: (client, response) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const { prevChecklistID, checklistID, checklistItem } = response.data.updateTaskChecklistItemLocation;
console.log(`${checklistID} !== ${prevChecklistID}`);
if (checklistID !== prevChecklistID) {
const oldIdx = cache.findTask.checklists.findIndex(c => c.id === prevChecklistID);
const newIdx = cache.findTask.checklists.findIndex(c => c.id === checklistID);
console.log(`oldIdx ${oldIdx} newIdx ${newIdx}`);
if (oldIdx > -1 && newIdx > -1) {
const item = cache.findTask.checklists[oldIdx].items.find(item => item.id === checklistItem.id);
console.log(item);
if (item) {
draftCache.findTask.checklists[oldIdx].items = cache.findTask.checklists[oldIdx].items.filter(
i => i.id !== checklistItem.id,
);
draftCache.findTask.checklists[newIdx].items.push({
...item,
position: checklistItem.position,
taskChecklistID: checklistID,
});
}
}
}
}),
{ taskID },
);
},
});
const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation({
update: client => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}),
{ taskID },
);
},
});
const [deleteTaskChecklist] = useDeleteTaskChecklistMutation({
update: (client, deleteData) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const { checklists } = cache.findTask;
console.log(deleteData);
draftCache.findTask.checklists = checklists.filter(
c => c.id !== deleteData.data.deleteTaskChecklist.taskChecklist.id,
);
},
});
const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation({
update: client => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}),
{ taskID },
);
},
});
const [deleteTaskChecklist] = useDeleteTaskChecklistMutation({
update: (client, deleteData) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const { checklists } = cache.findTask;
console.log(deleteData)
draftCache.findTask.checklists = checklists.filter(c => c.id !== deleteData.data.deleteTaskChecklist.taskChecklist.id);
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
if (complete === 0 && total === 0) {
draftCache.findTask.badges.checklist = null;
}
}),
{ taskID },
);
},
});
const [updateTaskChecklistItemName] = useUpdateTaskChecklistItemNameMutation();
const [createTaskChecklist] = useCreateTaskChecklistMutation({
update: (client, createData) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const item = createData.data.createTaskChecklist;
draftCache.findTask.checklists.push({ ...item });
}),
{ taskID },
);
},
});
const [updateTaskChecklistName] = useUpdateTaskChecklistNameMutation();
const [deleteTaskChecklistItem] = useDeleteTaskChecklistItemMutation({
update: (client, deleteData) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const item = deleteData.data.deleteTaskChecklistItem.taskChecklistItem;
const targetIdx = cache.findTask.checklists.findIndex(c => c.id === item.taskChecklistID)
if (targetIdx > -1) {
draftCache.findTask.checklists[targetIdx].items = cache.findTask.checklists[targetIdx].items.filter(c => item.id !== c.id);
}
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}),
{ taskID },
);
},
});
const [createTaskChecklistItem] = useCreateTaskChecklistItemMutation({
update: (client, newTaskItem) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const item = newTaskItem.data.createTaskChecklistItem;
const { checklists } = cache.findTask;
const idx = checklists.findIndex(c => c.id === item.taskChecklistID);
if (idx !== -1) {
draftCache.findTask.checklists[idx].items.push({ ...item });
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}
}),
{ taskID },
);
},
});
const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
const [setTaskComplete] = useSetTaskCompleteMutation();
const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
const [assignTask] = useAssignTaskMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
const [unassignTask] = useUnassignTaskMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
if (loading) {
return <div>loading</div>;
}
if (!data) {
return <div>loading</div>;
}
return (
<>
<Modal
width={768}
onClose={() => {
history.push(projectURL);
}}
renderContent={() => {
return (
<TaskDetails
task={data.findTask}
onChecklistDrop={checklist => {
updateTaskChecklistLocation({
variables: { checklistID: checklist.id, position: checklist.position },
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
if (complete === 0 && total === 0) {
draftCache.findTask.badges.checklist = null;
}
}),
{ taskID },
);
},
});
const [updateTaskChecklistItemName] = useUpdateTaskChecklistItemNameMutation();
const [createTaskChecklist] = useCreateTaskChecklistMutation({
update: (client, createData) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const item = createData.data.createTaskChecklist;
draftCache.findTask.checklists.push({ ...item });
}),
{ taskID },
);
},
});
const [updateTaskChecklistName] = useUpdateTaskChecklistNameMutation();
const [deleteTaskChecklistItem] = useDeleteTaskChecklistItemMutation({
update: (client, deleteData) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const item = deleteData.data.deleteTaskChecklistItem.taskChecklistItem;
const targetIdx = cache.findTask.checklists.findIndex(c => c.id === item.taskChecklistID);
if (targetIdx > -1) {
draftCache.findTask.checklists[targetIdx].items = cache.findTask.checklists[targetIdx].items.filter(
c => item.id !== c.id,
);
}
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}),
{ taskID },
);
},
});
const [createTaskChecklistItem] = useCreateTaskChecklistItemMutation({
update: (client, newTaskItem) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const item = newTaskItem.data.createTaskChecklistItem;
const { checklists } = cache.findTask;
const idx = checklists.findIndex(c => c.id === item.taskChecklistID);
if (idx !== -1) {
draftCache.findTask.checklists[idx].items.push({ ...item });
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}
}),
{ taskID },
);
},
});
const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
const [setTaskComplete] = useSetTaskCompleteMutation();
const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
const [assignTask] = useAssignTaskMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
const [unassignTask] = useUnassignTaskMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
if (loading) {
return <div>loading</div>;
}
if (!data) {
return <div>loading</div>;
}
return (
<>
<Modal
width={768}
onClose={() => {
history.push(projectURL);
}}
renderContent={() => {
return (
<TaskDetails
task={data.findTask}
onChecklistDrop={checklist => {
updateTaskChecklistLocation({
variables: { checklistID: checklist.id, position: checklist.position },
optimisticResponse: {
__typename: 'Mutation',
updateTaskChecklistLocation: {
__typename: 'UpdateTaskChecklistLocationPayload',
checklist: {
__typename: 'TaskChecklist',
position: checklist.position,
id: checklist.id,
},
},
},
});
}}
onChecklistItemDrop={(prevChecklistID, checklistID, checklistItem) => {
updateTaskChecklistItemLocation({
variables: { checklistID, checklistItemID: checklistItem.id, position: checklistItem.position },
optimisticResponse: {
__typename: 'Mutation',
updateTaskChecklistLocation: {
__typename: 'UpdateTaskChecklistLocationPayload',
checklist: {
__typename: 'TaskChecklist',
position: checklist.position,
id: checklist.id,
},
},
},
});
}}
onChecklistItemDrop={(prevChecklistID, checklistID, checklistItem) => {
updateTaskChecklistItemLocation({
variables: { checklistID, checklistItemID: checklistItem.id, position: checklistItem.position },
optimisticResponse: {
__typename: 'Mutation',
updateTaskChecklistItemLocation: {
__typename: 'UpdateTaskChecklistItemLocationPayload',
prevChecklistID,
checklistID,
checklistItem: {
__typename: 'TaskChecklistItem',
position: checklistItem.position,
id: checklistItem.id,
taskChecklistID: checklistID,
},
},
},
});
}}
onTaskNameChange={onTaskNameChange}
onTaskDescriptionChange={onTaskDescriptionChange}
onToggleTaskComplete={task => {
setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } });
}}
onDeleteTask={onDeleteTask}
onChangeItemName={(itemID, itemName) => {
updateTaskChecklistItemName({ variables: { taskChecklistItemID: itemID, name: itemName } });
}}
onCloseModal={() => history.push(projectURL)}
onChangeChecklistName={(checklistID, newName) => {
updateTaskChecklistName({ variables: { taskChecklistID: checklistID, name: newName } });
}}
onDeleteItem={(checklistID, itemID) => {
deleteTaskChecklistItem({
variables: { taskChecklistItemID: itemID },
optimisticResponse: {
__typename: 'Mutation',
deleteTaskChecklistItem: {
__typename: 'DeleteTaskChecklistItemPayload',
ok: true,
taskChecklistItem: {
__typename: 'TaskChecklistItem',
id: itemID,
taskChecklistID: checklistID,
}
}
}
});
}}
onToggleChecklistItem={(itemID, complete) => {
setTaskChecklistItemComplete({
variables: { taskChecklistItemID: itemID, complete },
optimisticResponse: {
__typename: 'Mutation',
setTaskChecklistItemComplete: {
__typename: 'TaskChecklistItem',
id: itemID,
complete,
},
},
});
}}
onAddItem={(taskChecklistID, name, position) => {
createTaskChecklistItem({ variables: { taskChecklistID, name, position } });
}}
onMemberProfile={($targetRef, memberID) => {
const member = data.findTask.assigned.find(m => m.id === memberID);
if (member) {
showPopup(
$targetRef,
<Popup title={null} onClose={() => { }} tab={0}>
<MiniProfile
user={member}
bio="None"
onRemoveFromTask={() => {
unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });
}}
/>
</Popup>,
);
}
}}
onOpenAddMemberPopup={(task, $targetRef) => {
showPopup(
$targetRef,
<Popup title="Members" tab={0} onClose={() => { }}>
<MemberManager
availableMembers={availableMembers}
activeMembers={data.findTask.assigned}
onMemberChange={(member, isActive) => {
if (isActive) {
assignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });
} else {
unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });
}
}}
/>
</Popup>,
);
}}
onOpenAddLabelPopup={onOpenAddLabelPopup}
onOpenAddChecklistPopup={(_task, $target) => {
showPopup(
$target,
<Popup
title={'Add checklist'}
tab={0}
onClose={() => {
hidePopup();
}}
>
<CreateChecklistPopup
onCreateChecklist={checklistData => {
let position = 65535;
console.log(data.findTask.checklists);
if (data.findTask.checklists) {
const [lastChecklist] = data.findTask.checklists.slice(-1);
console.log(`lastCheclist ${lastChecklist}`);
if (lastChecklist) {
position = lastChecklist.position * 2 + 1;
}
}
createTaskChecklist({
variables: {
taskID: data.findTask.id,
name: checklistData.name,
position,
},
});
hidePopup();
}}
/>
</Popup>,
);
}}
onDeleteChecklist={($target, checklistID) => {
showPopup(
$target,
<Popup tab={0} title="Delete checklist?" onClose={() => hidePopup()}>
<p>Deleting a checklist is permanent and there is no way to get it back.</p>
<DeleteChecklistButton
color="danger"
onClick={() => {
deleteTaskChecklist({ variables: { taskChecklistID: checklistID } });
hidePopup();
}}
>
Delete Checklist
optimisticResponse: {
__typename: 'Mutation',
updateTaskChecklistItemLocation: {
__typename: 'UpdateTaskChecklistItemLocationPayload',
prevChecklistID,
checklistID,
checklistItem: {
__typename: 'TaskChecklistItem',
position: checklistItem.position,
id: checklistItem.id,
taskChecklistID: checklistID,
},
},
},
});
}}
onTaskNameChange={onTaskNameChange}
onTaskDescriptionChange={onTaskDescriptionChange}
onToggleTaskComplete={task => {
setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } });
}}
onDeleteTask={onDeleteTask}
onChangeItemName={(itemID, itemName) => {
updateTaskChecklistItemName({ variables: { taskChecklistItemID: itemID, name: itemName } });
}}
onCloseModal={() => history.push(projectURL)}
onChangeChecklistName={(checklistID, newName) => {
updateTaskChecklistName({ variables: { taskChecklistID: checklistID, name: newName } });
}}
onDeleteItem={(checklistID, itemID) => {
deleteTaskChecklistItem({
variables: { taskChecklistItemID: itemID },
optimisticResponse: {
__typename: 'Mutation',
deleteTaskChecklistItem: {
__typename: 'DeleteTaskChecklistItemPayload',
ok: true,
taskChecklistItem: {
__typename: 'TaskChecklistItem',
id: itemID,
taskChecklistID: checklistID,
},
},
},
});
}}
onToggleChecklistItem={(itemID, complete) => {
setTaskChecklistItemComplete({
variables: { taskChecklistItemID: itemID, complete },
optimisticResponse: {
__typename: 'Mutation',
setTaskChecklistItemComplete: {
__typename: 'TaskChecklistItem',
id: itemID,
complete,
},
},
});
}}
onAddItem={(taskChecklistID, name, position) => {
createTaskChecklistItem({ variables: { taskChecklistID, name, position } });
}}
onMemberProfile={($targetRef, memberID) => {
const member = data.findTask.assigned.find(m => m.id === memberID);
if (member) {
showPopup(
$targetRef,
<Popup title={null} onClose={() => {}} tab={0}>
<MiniProfile
user={member}
bio="None"
onRemoveFromTask={() => {
unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });
}}
/>
</Popup>,
);
}
}}
onOpenAddMemberPopup={(task, $targetRef) => {
showPopup(
$targetRef,
<Popup title="Members" tab={0} onClose={() => {}}>
<MemberManager
availableMembers={availableMembers}
activeMembers={data.findTask.assigned}
onMemberChange={(member, isActive) => {
if (isActive) {
assignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });
} else {
unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });
}
}}
/>
</Popup>,
);
}}
onOpenAddLabelPopup={onOpenAddLabelPopup}
onOpenAddChecklistPopup={(_task, $target) => {
showPopup(
$target,
<Popup
title={'Add checklist'}
tab={0}
onClose={() => {
hidePopup();
}}
>
<CreateChecklistPopup
onCreateChecklist={checklistData => {
let position = 65535;
console.log(data.findTask.checklists);
if (data.findTask.checklists) {
const [lastChecklist] = data.findTask.checklists.slice(-1);
console.log(`lastCheclist ${lastChecklist}`);
if (lastChecklist) {
position = lastChecklist.position * 2 + 1;
}
}
createTaskChecklist({
variables: {
taskID: data.findTask.id,
name: checklistData.name,
position,
},
});
hidePopup();
}}
/>
</Popup>,
);
}}
onDeleteChecklist={($target, checklistID) => {
showPopup(
$target,
<Popup tab={0} title="Delete checklist?" onClose={() => hidePopup()}>
<p>Deleting a checklist is permanent and there is no way to get it back.</p>
<DeleteChecklistButton
color="danger"
onClick={() => {
deleteTaskChecklist({ variables: { taskChecklistID: checklistID } });
hidePopup();
}}
>
Delete Checklist
</DeleteChecklistButton>
</Popup>,
);
}}
onOpenDueDatePopop={(task, $targetRef) => {
showPopup(
$targetRef,
<Popup
title={'Change Due Date'}
tab={0}
onClose={() => {
hidePopup();
}}
>
<DueDateManager
task={task}
onRemoveDueDate={t => {
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null } });
hidePopup();
}}
onDueDateChange={(t, newDueDate) => {
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup();
}}
onCancel={() => { }}
/>
</Popup>,
);
}}
/>
);
}}
</Popup>,
);
}}
onOpenDueDatePopop={(task, $targetRef) => {
showPopup(
$targetRef,
<Popup
title={'Change Due Date'}
tab={0}
onClose={() => {
hidePopup();
}}
>
<DueDateManager
task={task}
onRemoveDueDate={t => {
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null } });
hidePopup();
}}
onDueDateChange={(t, newDueDate) => {
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup();
}}
onCancel={() => {}}
/>
</Popup>,
);
}}
/>
</>
);
);
}}
/>
</>
);
};
export default Details;

View File

@ -1,11 +1,19 @@
// LOC830
import React, {useState, useRef, useEffect, useContext} from 'react';
import React, { useState, useRef, useEffect, useContext } from 'react';
import updateApolloCache from 'shared/utils/cache';
import GlobalTopNavbar, {ProjectPopup} from 'App/TopNavbar';
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
import styled from 'styled-components/macro';
import {usePopup, Popup} from 'shared/components/PopupMenu';
import LabelManagerEditor from './LabelManagerEditor'
import {useParams, Route, useRouteMatch, useHistory, RouteComponentProps, useLocation, Redirect} from 'react-router-dom';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import LabelManagerEditor from './LabelManagerEditor';
import {
useParams,
Route,
useRouteMatch,
useHistory,
RouteComponentProps,
useLocation,
Redirect,
} from 'react-router-dom';
import {
useSetProjectOwnerMutation,
useUpdateProjectMemberRoleMutation,
@ -29,8 +37,20 @@ import produce from 'immer';
import UserIDContext from 'App/context';
import Input from 'shared/components/Input';
import Member from 'shared/components/Member';
import Board from './Board'
import Details from './Details'
import Board from './Board';
import Details from './Details';
const CARD_LABEL_VARIANT_STORAGE_KEY = 'card_label_variant';
const useStateWithLocalStorage = (localStorageKey: string): [string, React.Dispatch<React.SetStateAction<string>>] => {
const [value, setValue] = React.useState<string>(localStorage.getItem(localStorageKey) || '');
React.useEffect(() => {
localStorage.setItem(localStorageKey, value);
}, [value]);
return [value, setValue];
};
const SearchInput = styled(Input)`
margin: 0;
@ -55,7 +75,7 @@ type UserManagementPopupProps = {
onAddProjectMember: (userID: string) => void;
};
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({users, projectMembers, onAddProjectMember}) => {
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projectMembers, onAddProjectMember }) => {
return (
<Popup tab={0} title="Invite a user">
<SearchInput width="100%" variant="alternate" placeholder="Email address or name" name="search" />
@ -99,7 +119,7 @@ const initialQuickCardEditorState: QuickCardEditorState = {
};
const Project = () => {
const {projectID} = useParams<ProjectParams>();
const { projectID } = useParams<ProjectParams>();
const history = useHistory();
const match = useRouteMatch();
@ -110,14 +130,16 @@ const Project = () => {
console.log(taskLabelsRef.current);
},
});
const [value, setValue] = useStateWithLocalStorage(CARD_LABEL_VARIANT_STORAGE_KEY);
const [updateProjectMemberRole] = useUpdateProjectMemberRoleMutation();
const [deleteTask] = useDeleteTaskMutation();
const [updateTaskName] = useUpdateTaskNameMutation();
const {loading, data} = useFindProjectQuery({
variables: {projectId: projectID},
const { loading, data } = useFindProjectQuery({
variables: { projectId: projectID },
});
const [updateProjectName] = useUpdateProjectNameMutation({
@ -129,7 +151,7 @@ const Project = () => {
produce(cache, draftCache => {
draftCache.findProject.name = newName.data.updateProjectName.name;
}),
{projectId: projectID},
{ projectId: projectID },
);
},
});
@ -141,9 +163,9 @@ const Project = () => {
FindProjectDocument,
cache =>
produce(cache, draftCache => {
draftCache.findProject.members.push({...response.data.createProjectMember.member});
draftCache.findProject.members.push({ ...response.data.createProjectMember.member });
}),
{projectId: projectID},
{ projectId: projectID },
);
},
});
@ -159,15 +181,15 @@ const Project = () => {
m => m.id !== response.data.deleteProjectMember.member.id,
);
}),
{projectId: projectID},
{ projectId: projectID },
);
},
});
const {userID} = useContext(UserIDContext);
const { userID } = useContext(UserIDContext);
const location = useLocation();
const {showPopup, hidePopup} = usePopup();
const { showPopup, hidePopup } = usePopup();
const $labelsRef = useRef<HTMLDivElement>(null);
const labelsRef = useRef<Array<ProjectLabel>>([]);
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
@ -192,25 +214,25 @@ const Project = () => {
<>
<GlobalTopNavbar
onChangeRole={(userID, roleCode) => {
updateProjectMemberRole({variables: {userID, roleCode, projectID}});
updateProjectMemberRole({ variables: { userID, roleCode, projectID } });
}}
onChangeProjectOwner={uid => {
setProjectOwner({variables: {ownerID: uid, projectID}});
setProjectOwner({ variables: { ownerID: uid, projectID } });
hidePopup();
}}
onRemoveFromBoard={userID => {
deleteProjectMember({variables: {userID, projectID}});
deleteProjectMember({ variables: { userID, projectID } });
hidePopup();
}}
onSaveProjectName={projectName => {
updateProjectName({variables: {projectID, name: projectName}});
updateProjectName({ variables: { projectID, name: projectName } });
}}
onInviteUser={$target => {
showPopup(
$target,
<UserManagementPopup
onAddProjectMember={userID => {
createProjectMember({variables: {userID, projectID}});
createProjectMember({ variables: { userID, projectID } });
}}
users={data.users}
projectMembers={data.findProject.members}
@ -218,23 +240,24 @@ const Project = () => {
);
}}
popupContent={<ProjectPopup history={history} name={data.findProject.name} projectID={projectID} />}
menuType={[{name: 'Board', link: location.pathname}]}
menuType={[{ name: 'Board', link: location.pathname }]}
currentTab={0}
projectMembers={data.findProject.members}
projectID={projectID}
name={data.findProject.name}
/>
<Route
path={`${match.path}`}
exact
render={() => (
<Redirect to={`${match.url}/board`} />
)}
/>
<Route path={`${match.path}`} exact render={() => <Redirect to={`${match.url}/board`} />} />
<Route
path={`${match.path}/board`}
render={() => (
<Board projectID={projectID} />
<Board
cardLabelVariant={value === 'small' ? 'large' : 'small'}
onCardLabelClick={() => {
const variant = value === 'small' ? 'large' : 'small';
setValue(() => variant);
}}
projectID={projectID}
/>
)}
/>
<Route
@ -246,13 +269,13 @@ const Project = () => {
projectURL={`${match.url}/board`}
taskID={routeProps.match.params.taskID}
onTaskNameChange={(updatedTask, newName) => {
updateTaskName({variables: {taskID: updatedTask.id, name: newName}});
updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
}}
onTaskDescriptionChange={(updatedTask, newDescription) => {
updateTaskDescription({variables: {taskID: updatedTask.id, description: newDescription}});
updateTaskDescription({ variables: { taskID: updatedTask.id, description: newDescription } });
}}
onDeleteTask={deletedTask => {
deleteTask({variables: {taskID: deletedTask.id}});
deleteTask({ variables: { taskID: deletedTask.id } });
}}
onOpenAddLabelPopup={(task, $targetRef) => {
taskLabelsRef.current = task.labels;
@ -260,7 +283,7 @@ const Project = () => {
$targetRef,
<LabelManagerEditor
onLabelToggle={labelID => {
toggleTaskLabel({variables: {taskID: task.id, projectLabelID: labelID}});
toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } });
}}
labelColors={data.labelColors}
labels={labelsRef}