feature: add labels & remove old types

This commit is contained in:
Jordan Knott
2020-05-30 23:11:19 -05:00
parent 539259effd
commit 2a59cddadb
48 changed files with 1671 additions and 834 deletions

View File

@ -8,8 +8,9 @@ import { useMeQuery } from 'shared/generated/graphql';
type GlobalTopNavbarProps = {
name: string;
projectMembers?: null | Array<TaskUser>;
onSaveProjectName?: (projectName: string) => void;
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers }) => {
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers, onSaveProjectName }) => {
const { loading, data } = useMeQuery();
const history = useHistory();
const { userID, setUserID } = useContext(UserIDContext);
@ -52,6 +53,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers
onNotificationClick={() => {}}
projectMembers={projectMembers}
onProfileClick={onProfileClick}
onSaveProjectName={onSaveProjectName}
/>
{menu.isOpen && (
<DropdownMenu

View File

@ -56,17 +56,6 @@ const Details: React.FC<DetailsProps> = ({
if (!data) {
return <div>loading</div>;
}
const taskMembers = data.findTask.assigned.map(assigned => {
return {
userID: assigned.id,
displayName: `${assigned.firstName} ${assigned.lastName}`,
profileIcon: {
url: null,
initials: assigned.profileIcon.initials ?? null,
bgColor: assigned.profileIcon.bgColor ?? null,
},
};
});
return (
<>
<Modal
@ -77,24 +66,19 @@ const Details: React.FC<DetailsProps> = ({
renderContent={() => {
return (
<TaskDetails
task={{
...data.findTask,
taskID: data.findTask.id,
taskGroup: { taskGroupID: data.findTask.taskGroup.id },
members: taskMembers,
description: data.findTask.description ?? '',
labels: [],
}}
task={data.findTask}
onTaskNameChange={onTaskNameChange}
onTaskDescriptionChange={onTaskDescriptionChange}
onDeleteTask={onDeleteTask}
onCloseModal={() => history.push(projectURL)}
onMemberProfile={($targetRef, memberID) => {
const member = data.findTask.assigned.find(m => m.id === memberID);
const profileIcon = member ? member.profileIcon : null;
showPopup(
$targetRef,
<Popup title={null} onClose={() => {}} tab={0}>
<MiniProfile
profileIcon={taskMembers[0].profileIcon}
profileIcon={profileIcon}
displayName="Jordan Knott"
username="@jordanthedev"
bio="None"
@ -111,7 +95,7 @@ const Details: React.FC<DetailsProps> = ({
<Popup title="Members" tab={0} onClose={() => {}}>
<MemberManager
availableMembers={availableMembers}
activeMembers={taskMembers}
activeMembers={data.findTask.assigned}
onMemberChange={(member, isActive) => {
if (isActive) {
assignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });

View File

@ -5,7 +5,6 @@ import Lists from 'shared/components/Lists';
import { Board } from './Styles';
type KanbanBoardProps = {
listsData: BoardState;
onOpenListActionsPopup: ($targetRef: React.RefObject<HTMLElement>, taskGroupID: string) => void;
onCardDrop: (task: Task) => void;
onListDrop: (taskGroup: TaskGroup) => void;
@ -16,7 +15,6 @@ type KanbanBoardProps = {
};
const KanbanBoard: React.FC<KanbanBoardProps> = ({
listsData,
onOpenListActionsPopup,
onQuickEditorOpen,
onCardCreate,
@ -27,25 +25,7 @@ const KanbanBoard: React.FC<KanbanBoardProps> = ({
}) => {
const match = useRouteMatch();
const history = useHistory();
return (
<Board>
<Lists
onCardClick={task => {
history.push(`${match.url}/c/${task.taskID}`);
}}
onExtraMenuOpen={(taskGroupID, $targetRef) => {
onOpenListActionsPopup($targetRef, taskGroupID);
}}
onQuickEditorOpen={onQuickEditorOpen}
onCardCreate={onCardCreate}
onCardDrop={onCardDrop}
onListDrop={onListDrop}
{...listsData}
onCreateList={onCreateList}
onCardMemberClick={onCardMemberClick}
/>
</Board>
);
return <Board></Board>;
};
export default KanbanBoard;

View File

@ -1,11 +1,12 @@
import React, { useState, useRef } from 'react';
import * as BoardStateUtils from 'shared/utils/boardState';
import GlobalTopNavbar from 'App/TopNavbar';
import styled from 'styled-components/macro';
import { Bolt, ToggleOn, Tags } from 'shared/icons';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom';
import {
useToggleTaskLabelMutation,
useUpdateProjectNameMutation,
useFindProjectQuery,
useUpdateTaskNameMutation,
useUpdateProjectLabelMutation,
@ -29,12 +30,14 @@ import ListActions from 'shared/components/ListActions';
import MemberManager from 'shared/components/MemberManager';
import { LabelsPopup } from 'shared/components/PopupMenu/PopupMenu.stories';
import KanbanBoard from 'Projects/Project/KanbanBoard';
import SimpleLists from 'shared/components/Lists';
import { mixin } from 'shared/utils/styles';
import LabelManager from 'shared/components/PopupMenu/LabelManager';
import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
import produce from 'immer';
import MiniProfile from 'shared/components/MiniProfile';
import Details from './Details';
import { useApolloClient } from '@apollo/react-hooks';
const getCacheData = (client: any, projectID: string) => {
const cacheData: any = client.readQuery({
@ -85,12 +88,20 @@ const ProjectMembers = styled.div`
`;
type LabelManagerEditorProps = {
labels: React.RefObject<Array<Label>>;
labels: React.RefObject<Array<ProjectLabel>>;
taskLabels: null | React.RefObject<Array<TaskLabel>>;
projectID: string;
labelColors: Array<LabelColor>;
onLabelToggle?: (labelId: string) => void;
};
const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({ labels: labelsRef, projectID, labelColors }) => {
const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({
labels: labelsRef,
projectID,
labelColors,
onLabelToggle,
taskLabels: taskLabelsRef,
}) => {
const [currentLabel, setCurrentLabel] = useState('');
const [createProjectLabel] = useCreateProjectLabelMutation({
update: (client, newLabelData) => {
@ -116,12 +127,16 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({ labels: labelsR
},
});
const labels = labelsRef.current ? labelsRef.current : [];
const taskLabels = taskLabelsRef && taskLabelsRef.current ? taskLabelsRef.current : [];
const [currentTaskLabels, setCurrentTaskLabels] = useState(taskLabels);
console.log(taskLabels);
const { setTab } = usePopup();
return (
<>
<Popup title="Labels" tab={0} onClose={() => {}}>
<LabelManager
labels={labels}
taskLabels={currentTaskLabels}
onLabelCreate={() => {
setTab(2);
}}
@ -130,18 +145,34 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({ labels: labelsR
setTab(1);
}}
onLabelToggle={labelId => {
setCurrentLabel(labelId);
setTab(1);
if (onLabelToggle) {
if (currentTaskLabels.find(t => t.projectLabel.id === labelId)) {
setCurrentTaskLabels(currentTaskLabels.filter(t => t.projectLabel.id !== labelId));
} else {
const newProjectLabel = labels.find(l => l.id === labelId);
if (newProjectLabel) {
setCurrentTaskLabels([
...currentTaskLabels,
{ id: '', assignedDate: '', projectLabel: { ...newProjectLabel } },
]);
}
}
setCurrentLabel(labelId);
onLabelToggle(labelId);
} else {
setCurrentLabel(labelId);
setTab(1);
}
}}
/>
</Popup>
<Popup onClose={() => {}} title="Edit label" tab={1}>
<LabelEditor
labelColors={labelColors}
label={labels.find(label => label.labelId === currentLabel) ?? null}
label={labels.find(label => label.id === currentLabel) ?? null}
onLabelEdit={(projectLabelID, name, color) => {
if (projectLabelID) {
updateProjectLabel({ variables: { projectLabelID, labelColorID: color.id, name } });
updateProjectLabel({ variables: { projectLabelID, labelColorID: color.id, name: name ?? '' } });
}
setTab(0);
}}
@ -156,7 +187,7 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({ labels: labelsR
labelColors={labelColors}
label={null}
onLabelEdit={(_labelId, name, color) => {
createProjectLabel({ variables: { projectID, labelColorID: color.id, name } });
createProjectLabel({ variables: { projectID, labelColorID: color.id, name: name ?? '' } });
setTab(0);
}}
/>
@ -169,10 +200,7 @@ interface ProjectParams {
projectID: string;
}
const initialState: BoardState = { tasks: {}, columns: {} };
const initialPopupState = { left: 0, top: 0, isOpen: false, taskGroupID: '' };
const initialQuickCardEditorState: QuickCardEditorState = { isOpen: false, top: 0, left: 0 };
const initialTaskDetailsState = { isOpen: false, taskID: '' };
const ProjectBar = styled.div`
display: flex;
@ -209,18 +237,16 @@ const ProjectActionText = styled.span`
const Project = () => {
const { projectID } = useParams<ProjectParams>();
const history = useHistory();
const match = useRouteMatch();
const [updateTaskDescription] = useUpdateTaskDescriptionMutation();
const [listsData, setListsData] = useState(initialState);
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
const [updateTaskLocation] = useUpdateTaskLocationMutation();
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation();
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({});
const [deleteTaskGroup] = useDeleteTaskGroupMutation({
onCompleted: deletedTaskGroupData => {
setListsData(BoardStateUtils.deleteTaskGroup(listsData, deletedTaskGroupData.deleteTaskGroup.taskGroup.id));
},
onCompleted: deletedTaskGroupData => {},
update: (client, deletedTaskGroupData) => {
const cacheData = getCacheData(client, projectID);
const newData = {
@ -235,14 +261,7 @@ const Project = () => {
});
const [createTaskGroup] = useCreateTaskGroupMutation({
onCompleted: newTaskGroupData => {
const newTaskGroup = {
taskGroupID: newTaskGroupData.createTaskGroup.id,
tasks: [],
...newTaskGroupData.createTaskGroup,
};
setListsData(BoardStateUtils.addTaskGroup(listsData, newTaskGroup));
},
onCompleted: newTaskGroupData => {},
update: (client, newTaskGroupData) => {
const cacheData = getCacheData(client, projectID);
const newData = {
@ -255,15 +274,7 @@ const Project = () => {
});
const [createTask] = useCreateTaskMutation({
onCompleted: newTaskData => {
const newTask = {
...newTaskData.createTask,
taskID: newTaskData.createTask.id,
taskGroup: { taskGroupID: newTaskData.createTask.taskGroup.id },
labels: [],
};
setListsData(BoardStateUtils.addTask(listsData, newTask));
},
onCompleted: newTaskData => {},
update: (client, newTaskData) => {
const cacheData = getCacheData(client, projectID);
const newTaskGroups = produce(cacheData.findProject.taskGroups, (draftState: any) => {
@ -284,158 +295,144 @@ const Project = () => {
});
const [deleteTask] = useDeleteTaskMutation({
onCompleted: deletedTask => {
setListsData(BoardStateUtils.deleteTask(listsData, deletedTask.deleteTask.taskID));
},
onCompleted: deletedTask => {},
});
const [updateTaskName] = useUpdateTaskNameMutation({
onCompleted: newTaskData => {
setListsData(
BoardStateUtils.updateTaskName(listsData, newTaskData.updateTaskName.id, newTaskData.updateTaskName.name),
);
onCompleted: newTaskData => {},
});
const [toggleTaskLabel] = useToggleTaskLabelMutation({
onCompleted: newTaskLabel => {
taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels;
console.log(taskLabelsRef.current);
},
});
const { loading, data, refetch } = useFindProjectQuery({
variables: { projectId: projectID },
onCompleted: newData => {},
});
const onCardCreate = (taskGroupID: string, name: string) => {
const taskGroupTasks = Object.values(listsData.tasks).filter(
(task: Task) => task.taskGroup.taskGroupID === taskGroupID,
);
let position = 65535;
if (taskGroupTasks.length !== 0) {
const [lastTask] = taskGroupTasks.sort((a: any, b: any) => a.position - b.position).slice(-1);
position = Math.ceil(lastTask.position) * 2 + 1;
}
if (data) {
const taskGroupTasks = data.findProject.taskGroups.filter(t => t.id === taskGroupID);
if (taskGroupTasks) {
let position = 65535;
if (taskGroupTasks.length !== 0) {
const [lastTask] = taskGroupTasks.sort((a: any, b: any) => a.position - b.position).slice(-1);
position = Math.ceil(lastTask.position) * 2 + 1;
}
createTask({ variables: { taskGroupID, name, position } });
createTask({ variables: { taskGroupID, name, position } });
}
}
};
const onCreateTask = (taskGroupID: string, name: string) => {
if (data) {
const taskGroup = data.findProject.taskGroups.find(t => t.id === taskGroupID);
console.log(`taskGroup ${taskGroup}`);
if (taskGroup) {
let position = 65535;
if (taskGroup.tasks.length !== 0) {
const [lastTask] = taskGroup.tasks.sort((a: any, b: any) => a.position - b.position).slice(-1);
position = Math.ceil(lastTask.position) * 2 + 1;
}
console.log(`position ${position}`);
createTask({ variables: { taskGroupID, name, position } });
}
}
};
const onCardDrop = (droppedTask: Task) => {
updateTaskLocation({
variables: {
taskID: droppedTask.taskID,
taskGroupID: droppedTask.taskGroup.taskGroupID,
taskID: droppedTask.id,
taskGroupID: droppedTask.taskGroup.id,
position: droppedTask.position,
},
optimisticResponse: {
updateTaskLocation: {
name: droppedTask.name,
id: droppedTask.taskID,
id: droppedTask.id,
position: droppedTask.position,
createdAt: '',
},
},
});
setListsData(BoardStateUtils.updateTask(listsData, droppedTask));
};
const onListDrop = (droppedColumn: TaskGroup) => {
console.log(`list drop ${droppedColumn.taskGroupID}`);
console.log(`list drop ${droppedColumn.id}`);
const cacheData = getCacheData(client, projectID);
const newData = produce(cacheData, (draftState: any) => {
const taskGroupIdx = cacheData.findProject.taskGroups.findIndex((t: any) => t.id === droppedColumn.id);
cacheData.findProject.taskGroups[taskGroupIdx].position = droppedColumn.position;
});
writeCacheData(client, projectID, cacheData, newData);
updateTaskGroupLocation({
variables: { taskGroupID: droppedColumn.taskGroupID, position: droppedColumn.position },
variables: { taskGroupID: droppedColumn.id, position: droppedColumn.position },
optimisticResponse: {
updateTaskGroupLocation: {
id: droppedColumn.taskGroupID,
id: droppedColumn.id,
position: droppedColumn.position,
},
},
});
// setListsData(BoardStateUtils.updateTaskGroup(listsData, droppedColumn));
};
const onCreateList = (listName: string) => {
const [lastColumn] = Object.values(listsData.columns)
.sort((a, b) => a.position - b.position)
.slice(-1);
let position = 65535;
if (lastColumn) {
position = lastColumn.position * 2 + 1;
if (data) {
const [lastColumn] = data.findProject.taskGroups.sort((a, b) => a.position - b.position).slice(-1);
let position = 65535;
if (lastColumn) {
position = lastColumn.position * 2 + 1;
}
createTaskGroup({ variables: { projectID, name: listName, position } });
}
createTaskGroup({ variables: { projectID, name: listName, position } });
};
const [assignTask] = useAssignTaskMutation();
const [updateProjectName] = useUpdateProjectNameMutation();
const client = useApolloClient();
const { showPopup, hidePopup } = usePopup();
const $labelsRef = useRef<HTMLDivElement>(null);
const labelsRef = useRef<Array<Label>>([]);
const labelsRef = useRef<Array<ProjectLabel>>([]);
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
if (loading) {
return (
<>
<GlobalTopNavbar name="Project" />
<Title>Error Loading</Title>
<GlobalTopNavbar onSaveProjectName={projectName => {}} name="Loading..." />
</>
);
}
if (data) {
console.log(data);
const currentListsData: BoardState = { tasks: {}, columns: {} };
data.findProject.taskGroups.forEach(taskGroup => {
currentListsData.columns[taskGroup.id] = {
taskGroupID: taskGroup.id,
name: taskGroup.name,
position: taskGroup.position,
tasks: [],
};
taskGroup.tasks.forEach(task => {
const taskMembers = task.assigned.map(assigned => {
return {
userID: assigned.id,
displayName: `${assigned.firstName} ${assigned.lastName}`,
profileIcon: {
url: null,
initials: assigned.profileIcon.initials ?? '',
bgColor: assigned.profileIcon.bgColor ?? '#7367F0',
},
};
});
currentListsData.tasks[task.id] = {
taskID: task.id,
taskGroup: {
taskGroupID: taskGroup.id,
},
name: task.name,
labels: [],
position: task.position,
description: task.description ?? undefined,
members: taskMembers,
};
});
});
const availableMembers = data.findProject.members.map(member => {
return {
displayName: `${member.firstName} ${member.lastName}`,
profileIcon: {
url: null,
initials: member.profileIcon.initials ?? null,
bgColor: member.profileIcon.bgColor ?? null,
},
userID: member.id,
};
});
const onQuickEditorOpen = (e: ContextMenuEvent) => {
const currentTask = Object.values(currentListsData.tasks).find(task => task.taskID === e.taskID);
setQuickCardEditor({
top: e.top,
left: e.left,
isOpen: true,
task: currentTask,
});
const taskGroup = data.findProject.taskGroups.find(t => t.id === e.taskGroupID);
const currentTask = taskGroup ? taskGroup.tasks.find(t => t.id === e.taskID) : null;
if (currentTask) {
setQuickCardEditor({
top: e.top,
left: e.left,
isOpen: true,
task: currentTask,
});
}
};
labelsRef.current = data.findProject.labels.map(label => {
return {
labelId: label.id,
name: label.name ?? '',
labelColor: label.labelColor,
active: false,
};
});
labelsRef.current = data.findProject.labels;
return (
<>
<GlobalTopNavbar projectMembers={availableMembers} name={data.findProject.name} />
<GlobalTopNavbar
onSaveProjectName={projectName => {
updateProjectName({ variables: { projectID, name: projectName } });
}}
projectMembers={data.findProject.members}
name={data.findProject.name}
/>
<ProjectBar>
<ProjectActions>
<ProjectAction
@ -443,7 +440,12 @@ const Project = () => {
onClick={() => {
showPopup(
$labelsRef,
<LabelManagerEditor labelColors={data.labelColors} labels={labelsRef} projectID={projectID} />,
<LabelManagerEditor
taskLabels={null}
labelColors={data.labelColors}
labels={labelsRef}
projectID={projectID}
/>,
);
}}
>
@ -460,18 +462,53 @@ const Project = () => {
</ProjectAction>
</ProjectActions>
</ProjectBar>
<KanbanBoard
listsData={currentListsData}
onCardDrop={onCardDrop}
onListDrop={onListDrop}
onCardCreate={onCardCreate}
onCreateList={onCreateList}
<SimpleLists
onTaskClick={task => {
history.push(`${match.url}/c/${task.id}`);
}}
onTaskDrop={droppedTask => {
updateTaskLocation({
variables: {
taskID: droppedTask.id,
taskGroupID: droppedTask.taskGroup.id,
position: droppedTask.position,
},
optimisticResponse: {
__typename: 'Mutation',
updateTaskLocation: {
name: droppedTask.name,
id: droppedTask.id,
position: droppedTask.position,
createdAt: '',
__typename: 'Task',
},
},
});
}}
onTaskGroupDrop={droppedTaskGroup => {
updateTaskGroupLocation({
variables: { taskGroupID: droppedTaskGroup.id, position: droppedTaskGroup.position },
optimisticResponse: {
__typename: 'Mutation',
updateTaskGroupLocation: {
id: droppedTaskGroup.id,
position: droppedTaskGroup.position,
__typename: 'TaskGroup',
},
},
});
}}
taskGroups={data.findProject.taskGroups}
onCreateTask={onCreateTask}
onCreateTaskGroup={onCreateList}
onCardMemberClick={($targetRef, taskID, memberID) => {
const member = data.findProject.members.find(m => m.id === memberID);
const profileIcon = member ? member.profileIcon : null;
showPopup(
$targetRef,
<Popup title={null} onClose={() => {}} tab={0}>
<MiniProfile
profileIcon={availableMembers[0].profileIcon}
profileIcon={profileIcon}
displayName="Jordan Knott"
username="@jordanthedev"
bio="None"
@ -483,7 +520,7 @@ const Project = () => {
);
}}
onQuickEditorOpen={onQuickEditorOpen}
onOpenListActionsPopup={($targetRef, taskGroupID) => {
onExtraMenuOpen={(taskGroupID: string, $targetRef: any) => {
showPopup(
$targetRef,
<Popup title="List actions" tab={0} onClose={() => {}}>
@ -501,8 +538,8 @@ const Project = () => {
{quickCardEditor.isOpen && (
<QuickCardEditor
isOpen
taskID={quickCardEditor.task ? quickCardEditor.task.taskID : ''}
taskGroupID={quickCardEditor.task ? quickCardEditor.task.taskGroup.taskGroupID : ''}
taskID={quickCardEditor.task ? quickCardEditor.task.id : ''}
taskGroupID={quickCardEditor.task ? quickCardEditor.task.taskGroup.id : ''}
cardTitle={quickCardEditor.task ? quickCardEditor.task.name : ''}
onCloseEditor={() => setQuickCardEditor(initialQuickCardEditorState)}
onEditCard={(_listId: string, cardId: string, cardName: string) => {
@ -537,19 +574,33 @@ const Project = () => {
render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
<Details
refreshCache={() => {}}
availableMembers={availableMembers}
availableMembers={data.findProject.members}
projectURL={match.url}
taskID={routeProps.match.params.taskID}
onTaskNameChange={(updatedTask, newName) => {
updateTaskName({ variables: { taskID: updatedTask.taskID, name: newName } });
updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
}}
onTaskDescriptionChange={(updatedTask, newDescription) => {
updateTaskDescription({ variables: { taskID: updatedTask.taskID, description: newDescription } });
updateTaskDescription({ variables: { taskID: updatedTask.id, description: newDescription } });
}}
onDeleteTask={deletedTask => {
deleteTask({ variables: { taskID: deletedTask.taskID } });
deleteTask({ variables: { taskID: deletedTask.id } });
}}
onOpenAddLabelPopup={(task, $targetRef) => {
taskLabelsRef.current = task.labels;
showPopup(
$targetRef,
<LabelManagerEditor
onLabelToggle={labelID => {
toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } });
}}
labelColors={data.labelColors}
labels={labelsRef}
taskLabels={taskLabelsRef}
projectID={projectID}
/>,
);
}}
onOpenAddLabelPopup={(task, $targetRef) => {}}
/>
)}
/>

View File

@ -41,7 +41,7 @@ const Projects = () => {
const { projects } = data;
return (
<>
<GlobalTopNavbar name="Projects" />
<GlobalTopNavbar onSaveProjectName={() => {}} name="Projects" />
<ProjectGrid>
{projects.map(project => (
<ProjectLink key={project.id} to={`/projects/${project.id}`}>

79
web/src/citadel.d.ts vendored
View File

@ -3,18 +3,6 @@ interface JWTToken {
iat: string;
exp: string;
}
interface ColumnState {
[key: string]: TaskGroup;
}
interface TaskState {
[key: string]: Task;
}
interface BoardState {
columns: ColumnState;
tasks: TaskState;
}
interface DraggableElement {
id: string;
@ -28,66 +16,13 @@ type ContextMenuEvent = {
taskGroupID: string;
};
type InnerTaskGroup = {
taskGroupID: string;
name?: string;
position?: number;
};
type ProfileIcon = {
url: string | null;
initials: string | null;
bgColor: string | null;
};
type TaskUser = {
userID: string;
displayName: string;
id: string;
firstName: string;
lastName: string;
profileIcon: ProfileIcon;
};
type Task = {
taskID: string;
taskGroup: InnerTaskGroup;
name: string;
position: number;
labels: Label[];
description?: string | null;
members?: Array<TaskUser>;
};
type TaskGroup = {
taskGroupID: string;
name: string;
position: number;
tasks: Task[];
};
type Project = {
projectID: string;
name: string;
color?: string;
teamTitle?: string;
taskGroups: TaskGroup[];
};
type Organization = {
name: string;
teams: Team[];
};
type Team = {
name: string;
projects: Project[];
};
type Label = {
labelId: string;
name: string;
labelColor: LabelColor;
active: boolean;
};
type RefreshTokenResponse = {
accessToken: string;
};
@ -117,17 +52,9 @@ type ElementSize = {
height: number;
};
type LabelColor = {
id: string;
name: string;
colorHex: string;
position: number;
};
type OnCardMemberClick = ($targetRef: RefObject<HTMLElement>, taskID: string, memberID: string) => void;
type ElementBounds = {
size: ElementSize;
position: ElementPosition;
};

66
web/src/projects.d.ts vendored Normal file
View File

@ -0,0 +1,66 @@
type ProfileIcon = {
url?: string | null;
initials?: string | null;
bgColor?: string | null;
};
type TaskGroup = {
id: string;
name: string;
position: number;
tasks: Task[];
};
type LabelColor = {
id: string;
name: string;
colorHex: string;
position: number;
};
type InnerTaskGroup = {
id: string;
name?: string;
position?: number;
};
type TaskLabel = {
id: string;
assignedDate: string;
projectLabel: ProjectLabel;
};
type Task = {
id: string;
taskGroup: InnerTaskGroup;
name: string;
position: number;
labels: TaskLabel[];
description?: string | null;
assigned?: Array<TaskUser>;
};
type Project = {
projectID: string;
name: string;
color?: string;
teamTitle?: string;
taskGroups: TaskGroup[];
};
type Organization = {
name: string;
teams: Team[];
};
type Team = {
name: string;
projects: Project[];
};
type ProjectLabel = {
id: string;
createdDate: string;
name?: string | null;
labelColor: LabelColor;
};

View File

@ -14,28 +14,17 @@ export default {
},
};
const labelData = [
const labelData: Array<ProjectLabel> = [
{
labelId: 'development',
id: 'development',
name: 'Development',
createdDate: new Date().toString(),
labelColor: {
id: '1',
colorHex: LabelColors.BLUE,
name: 'blue',
position: 1,
},
active: false,
},
{
labelId: 'general',
name: 'General',
labelColor: {
id: '2',
colorHex: LabelColors.PINK,
name: 'pink',
position: 2,
},
active: false,
},
];

View File

@ -47,10 +47,10 @@ const Member: React.FC<MemberProps> = ({ onCardMemberClick, taskID, member }) =>
onClick={e => {
if (onCardMemberClick) {
e.stopPropagation();
onCardMemberClick($targetRef, taskID, member.userID);
onCardMemberClick($targetRef, taskID, member.id);
}
}}
key={member.userID}
key={member.id}
bgColor={member.profileIcon.bgColor ?? '#7367F0'}
>
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
@ -68,7 +68,7 @@ type Props = {
dueDate?: DueDate;
checklists?: Checklist;
watched?: boolean;
labels?: Label[];
labels?: Array<ProjectLabel>;
wrapperProps?: any;
members?: Array<TaskUser> | null;
onCardMemberClick?: OnCardMemberClick;
@ -136,7 +136,7 @@ const Card = React.forwardRef(
<ListCardLabels>
{labels &&
labels.map(label => (
<ListCardLabel color={label.labelColor.colorHex} key={label.name}>
<ListCardLabel color={label.labelColor.colorHex} key={label.id}>
{label.name}
</ListCardLabel>
))}
@ -169,7 +169,7 @@ const Card = React.forwardRef(
<CardMembers>
{members &&
members.map(member => (
<Member key={member.userID} taskID={taskID} member={member} onCardMemberClick={onCardMemberClick} />
<Member key={member.id} taskID={taskID} member={member} onCardMemberClick={onCardMemberClick} />
))}
</CardMembers>
</ListCardDetails>

View File

@ -17,21 +17,35 @@ export const Default = () => {
return (
<DueDateManager
task={{
taskID: '1',
taskGroup: { name: 'General', taskGroupID: '1' },
id: '1',
taskGroup: { name: 'General', id: '1', position: 1 },
name: 'Hello, world',
position: 1,
labels: [
{
labelId: 'soft-skills',
labelColor: { id: '1', colorHex: '#fff', name: 'white', position: 1 },
active: true,
name: 'Soft Skills',
id: 'soft-skills',
assignedDate: new Date().toString(),
projectLabel: {
createdDate: new Date().toString(),
id: 'label-soft-skills',
name: 'Soft Skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
},
},
},
],
description: 'hello!',
members: [
{ userID: '1', profileIcon: { url: null, initials: null, bgColor: null }, displayName: 'Jordan Knott' },
assigned: [
{
id: '1',
profileIcon: { url: null, initials: null, bgColor: null },
firstName: 'Jordan',
lastName: 'Knott',
},
],
}}
onCancel={action('cancel')}

View File

@ -16,28 +16,17 @@ export default {
},
};
const labelData = [
const labelData: Array<ProjectLabel> = [
{
labelId: 'development',
id: 'development',
name: 'Development',
createdDate: new Date().toString(),
labelColor: {
id: '1',
colorHex: LabelColors.BLUE,
name: 'blue',
position: 1,
},
active: false,
},
{
labelId: 'general',
name: 'General',
labelColor: {
id: '2',
colorHex: LabelColors.PINK,
name: 'pink',
position: 2,
},
active: false,
},
];
@ -68,7 +57,6 @@ export const Default = () => {
isComposerOpen={false}
onSaveName={action('on save name')}
onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={action('extra menu open')}
>
<ListCards>
@ -94,7 +82,6 @@ export const WithCardComposer = () => {
isComposerOpen
onSaveName={action('on save name')}
onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={action('extra menu open')}
>
<ListCards>
@ -121,7 +108,6 @@ export const WithCard = () => {
isComposerOpen={false}
onSaveName={action('on save name')}
onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={action('extra menu open')}
>
<ListCards>
@ -160,7 +146,6 @@ export const WithCardAndComposer = () => {
isComposerOpen
onSaveName={action('on save name')}
onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={action('extra menu open')}
>
<ListCards>

View File

@ -97,9 +97,7 @@ export const Header = styled.div<{ isEditing: boolean }>`
props.isEditing &&
css`
& ${HeaderName} {
background: #fff;
border: none;
box-shadow: inset 0 0 0 2px #0079bf;
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
}
`}
`;

View File

@ -22,7 +22,6 @@ type Props = {
onSaveName: (name: string) => void;
isComposerOpen: boolean;
onOpenComposer: (id: string) => void;
tasks: Task[];
wrapperProps?: any;
headerProps?: any;
index?: number;

View File

@ -70,14 +70,12 @@ export const Default = () => {
...listsData,
tasks: {
...listsData.tasks,
[droppedTask.taskID]: droppedTask,
[droppedTask.id]: droppedTask,
},
};
console.log(newState);
setListsData(newState);
};
const onListDrop = (droppedColumn: any) => {
console.log(droppedColumn);
const newState = {
...listsData,
columns: {
@ -85,44 +83,9 @@ export const Default = () => {
[droppedColumn.taskGroupID]: droppedColumn,
},
};
console.log(newState);
setListsData(newState);
};
return (
<Lists
{...listsData}
onCardClick={action('card click')}
onExtraMenuOpen={action('extra menu open')}
onQuickEditorOpen={action('card composer open')}
onCardDrop={onCardDrop}
onListDrop={onListDrop}
onCardMemberClick={action('card member click')}
onCardCreate={action('card create')}
onCreateList={listName => {
const [lastColumn] = Object.values(listsData.columns)
.sort((a, b) => a.position - b.position)
.slice(-1);
let position = 1;
if (lastColumn) {
position = lastColumn.position + 1;
}
const taskGroupID = Math.random().toString();
const newListsData = {
...listsData,
columns: {
...listsData.columns,
[taskGroupID]: {
taskGroupID,
name: listName,
position,
tasks: [],
},
},
};
setListsData(newListsData);
}}
/>
);
return <span />;
};
const createColumn = (id: any, name: any, position: any) => {
@ -202,13 +165,13 @@ export const ListsWithManyList = () => {
};
return (
<Lists
{...listsData}
onCardClick={action('card click')}
taskGroups={[]}
onTaskClick={action('card click')}
onQuickEditorOpen={action('card composer open')}
onCardCreate={action('card create')}
onCardDrop={onCardDrop}
onListDrop={onListDrop}
onCreateList={action('create list')}
onCreateTask={action('card create')}
onTaskDrop={onCardDrop}
onTaskGroupDrop={onListDrop}
onCreateTaskGroup={action('create list')}
onExtraMenuOpen={action('extra menu open')}
onCardMemberClick={action('card member click')}
/>

View File

@ -11,5 +11,7 @@ export const Container = styled.div`
export const BoardWrapper = styled.div`
display: flex;
margin-top: 12px;
margin-left: 8px;
`;
export default Container;

View File

@ -13,37 +13,29 @@ import {
import { Container, BoardWrapper } from './Styles';
interface Columns {
[key: string]: TaskGroup;
}
interface Tasks {
[key: string]: Task;
}
interface SimpleProps {
taskGroups: Array<TaskGroup>;
onTaskDrop: (task: Task) => void;
onTaskGroupDrop: (taskGroup: TaskGroup) => void;
type Props = {
columns: Columns;
tasks: Tasks;
onCardClick: (task: Task) => void;
onCardDrop: (task: Task) => void;
onListDrop: (taskGroup: TaskGroup) => void;
onCardCreate: (taskGroupID: string, name: string) => void;
onTaskClick: (task: Task) => void;
onCreateTask: (taskGroupID: string, name: string) => void;
onQuickEditorOpen: (e: ContextMenuEvent) => void;
onCreateList: (listName: string) => void;
onCreateTaskGroup: (listName: string) => void;
onExtraMenuOpen: (taskGroupID: string, $targetRef: React.RefObject<HTMLElement>) => void;
onCardMemberClick: OnCardMemberClick;
};
}
const Lists: React.FC<Props> = ({
columns,
tasks,
onCardClick,
onCardDrop,
onListDrop,
onCardCreate,
const SimpleLists: React.FC<SimpleProps> = ({
taskGroups,
onTaskDrop,
onTaskGroupDrop,
onTaskClick,
onCreateTask,
onQuickEditorOpen,
onCreateList,
onCardMemberClick,
onCreateTaskGroup,
onExtraMenuOpen,
onCardMemberClick,
}) => {
const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => {
if (typeof destination === 'undefined') return;
@ -51,64 +43,78 @@ const Lists: React.FC<Props> = ({
const isList = type === 'column';
const isSameList = destination.droppableId === source.droppableId;
const droppedDraggable: DraggableElement = isList
? {
id: draggableId,
position: columns[draggableId].position,
}
: {
id: draggableId,
position: tasks[draggableId].position,
};
const beforeDropDraggables = isList
? getSortedDraggables(
Object.values(columns).map(column => {
return { id: column.taskGroupID, position: column.position };
}),
)
: getSortedDraggables(
Object.values(tasks)
.filter((t: any) => t.taskGroup.taskGroupID === destination.droppableId)
.map(task => {
return { id: task.taskID, position: task.position };
}),
);
const afterDropDraggables = getAfterDropDraggableList(
beforeDropDraggables,
droppedDraggable,
isList,
isSameList,
destination,
);
const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
let droppedDraggable: DraggableElement | null = null;
let beforeDropDraggables: Array<DraggableElement> | null = null;
if (isList) {
const droppedList = columns[droppedDraggable.id];
onListDrop({
...droppedList,
position: newPosition,
});
const droppedGroup = taskGroups.find(taskGroup => taskGroup.id === draggableId);
if (droppedGroup) {
droppedDraggable = {
id: draggableId,
position: droppedGroup.position,
};
beforeDropDraggables = getSortedDraggables(
taskGroups.map(taskGroup => {
return { id: taskGroup.id, position: taskGroup.position };
}),
);
if (droppedDraggable === null || beforeDropDraggables === null) {
throw new Error('before drop draggables is null');
}
const afterDropDraggables = getAfterDropDraggableList(
beforeDropDraggables,
droppedDraggable,
isList,
isSameList,
destination,
);
const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
onTaskGroupDrop({
...droppedGroup,
position: newPosition,
});
} else {
throw { error: 'task group can not be found' };
}
} else {
const droppedCard = tasks[droppedDraggable.id];
const newCard = {
...droppedCard,
position: newPosition,
taskGroup: {
taskGroupID: destination.droppableId,
},
};
onCardDrop(newCard);
const targetGroup = taskGroups.findIndex(
taskGroup => taskGroup.tasks.findIndex(task => task.id === draggableId) !== -1,
);
const droppedTask = taskGroups[targetGroup].tasks.find(task => task.id === draggableId);
if (droppedTask) {
droppedDraggable = {
id: draggableId,
position: droppedTask.position,
};
beforeDropDraggables = getSortedDraggables(
taskGroups[targetGroup].tasks.map(task => {
return { id: task.id, position: task.position };
}),
);
if (droppedDraggable === null || beforeDropDraggables === null) {
throw new Error('before drop draggables is null');
}
const afterDropDraggables = getAfterDropDraggableList(
beforeDropDraggables,
droppedDraggable,
isList,
isSameList,
destination,
);
const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
const newTask = {
...droppedTask,
position: newPosition,
taskGroup: {
id: destination.droppableId,
},
};
onTaskDrop(newTask);
}
}
};
const orderedColumns = getSortedDraggables(
Object.values(columns).map(column => {
return { id: column.taskGroupID, position: column.position };
}),
);
console.log(orderedColumns);
const [currentComposer, setCurrentComposer] = useState('');
return (
<BoardWrapper>
@ -116,85 +122,92 @@ const Lists: React.FC<Props> = ({
<Droppable direction="horizontal" type="column" droppableId="root">
{provided => (
<Container {...provided.droppableProps} ref={provided.innerRef}>
{orderedColumns.map((columnDraggable, index: number) => {
const column = columns[columnDraggable.id];
const columnCards = Object.values(tasks)
.filter((t: Task) => t.taskGroup.taskGroupID === column.taskGroupID)
.sort((a, b) => a.position - b.position);
return (
<Draggable draggableId={column.taskGroupID} key={column.taskGroupID} index={index}>
{columnDragProvided => (
<Droppable type="tasks" droppableId={column.taskGroupID}>
{(columnDropProvided, snapshot) => (
<List
name={column.name}
onOpenComposer={id => setCurrentComposer(id)}
isComposerOpen={currentComposer === column.taskGroupID}
onSaveName={name => {}}
tasks={columnCards}
ref={columnDragProvided.innerRef}
wrapperProps={columnDragProvided.draggableProps}
headerProps={columnDragProvided.dragHandleProps}
onExtraMenuOpen={onExtraMenuOpen}
id={column.taskGroupID}
key={column.taskGroupID}
index={index}
>
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
{columnCards.map((task: Task, taskIndex: any) => {
return (
<Draggable key={task.taskID} draggableId={task.taskID} index={taskIndex}>
{taskProvided => {
return (
<Card
wrapperProps={{
...taskProvided.draggableProps,
...taskProvided.dragHandleProps,
}}
ref={taskProvided.innerRef}
taskID={task.taskID}
taskGroupID={column.taskGroupID}
description=""
title={task.name}
labels={task.labels}
members={task.members}
onClick={() => onCardClick(task)}
onCardMemberClick={onCardMemberClick}
onContextMenu={onQuickEditorOpen}
/>
);
{taskGroups
.slice()
.sort((a: any, b: any) => a.position - b.position)
.map((taskGroup: TaskGroup, index: number) => {
return (
<Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
{columnDragProvided => (
<Droppable type="tasks" droppableId={taskGroup.id}>
{(columnDropProvided, snapshot) => (
<List
name={taskGroup.name}
onOpenComposer={id => setCurrentComposer(id)}
isComposerOpen={currentComposer === taskGroup.id}
onSaveName={name => {}}
ref={columnDragProvided.innerRef}
wrapperProps={columnDragProvided.draggableProps}
headerProps={columnDragProvided.dragHandleProps}
onExtraMenuOpen={onExtraMenuOpen}
id={taskGroup.id}
key={taskGroup.id}
index={index}
>
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
{taskGroup.tasks
.slice()
.sort((a: any, b: any) => a.position - b.position)
.map((task: Task, taskIndex: any) => {
return (
<Draggable key={task.id} draggableId={task.id} index={taskIndex}>
{taskProvided => {
return (
<Card
wrapperProps={{
...taskProvided.draggableProps,
...taskProvided.dragHandleProps,
}}
ref={taskProvided.innerRef}
taskID={task.id}
taskGroupID={taskGroup.id}
description=""
labels={task.labels.map(label => label.projectLabel)}
title={task.name}
members={task.assigned}
onClick={() => {
onTaskClick(task);
}}
onCardMemberClick={onCardMemberClick}
onContextMenu={onQuickEditorOpen}
/>
);
}}
</Draggable>
);
})}
{columnDropProvided.placeholder}
{currentComposer === taskGroup.id && (
<CardComposer
onClose={() => {
setCurrentComposer('');
}}
</Draggable>
);
})}
{columnDropProvided.placeholder}
{currentComposer === column.taskGroupID && (
<CardComposer
onClose={() => {
setCurrentComposer('');
}}
onCreateCard={name => {
onCardCreate(column.taskGroupID, name);
}}
isOpen
/>
)}
</ListCards>
</List>
)}
</Droppable>
)}
</Draggable>
);
})}
onCreateCard={name => {
onCreateTask(taskGroup.id, name);
}}
isOpen
/>
)}
</ListCards>
</List>
)}
</Droppable>
)}
</Draggable>
);
})}
{provided.placeholder}
</Container>
)}
</Droppable>
</DragDropContext>
<AddList onSave={onCreateList} />
<AddList
onSave={listName => {
onCreateTaskGroup(listName);
}}
/>
</BoardWrapper>
);
};
export default Lists;
export default SimpleLists;

View File

@ -39,16 +39,18 @@ const MemberManager: React.FC<MemberManagerProps> = ({
<BoardMembersList>
{availableMembers
.filter(
member => currentSearch === '' || member.displayName.toLowerCase().startsWith(currentSearch.toLowerCase()),
member =>
currentSearch === '' ||
`${member.firstName} ${member.lastName}`.toLowerCase().startsWith(currentSearch.toLowerCase()),
)
.map(member => {
return (
<BoardMembersListItem key={member.userID}>
<BoardMembersListItem key={member.id}>
<BoardMemberListItemContent
onClick={() => {
const isActive = activeMembers.findIndex(m => m.userID === member.userID) !== -1;
const isActive = activeMembers.findIndex(m => m.id === member.id) !== -1;
if (isActive) {
setActiveMembers(activeMembers.filter(m => m.userID !== member.userID));
setActiveMembers(activeMembers.filter(m => m.id !== member.id));
} else {
setActiveMembers([...activeMembers, member]);
}
@ -56,8 +58,8 @@ const MemberManager: React.FC<MemberManagerProps> = ({
}}
>
<ProfileIcon>JK</ProfileIcon>
<MemberName>{member.displayName}</MemberName>
{activeMembers.findIndex(m => m.userID === member.userID) !== -1 && (
<MemberName>{`${member.firstName} ${member.lastName}`}</MemberName>
{activeMembers.findIndex(m => m.id === member.id) !== -1 && (
<ActiveIconWrapper>
<Checkmark size={16} color="#42526e" />
</ActiveIconWrapper>

View File

@ -16,14 +16,14 @@ type MiniProfileProps = {
displayName: string;
username: string;
bio: string;
profileIcon: ProfileIcon;
profileIcon: ProfileIcon | null;
onRemoveFromTask: () => void;
};
const MiniProfile: React.FC<MiniProfileProps> = ({ displayName, username, bio, profileIcon, onRemoveFromTask }) => {
return (
<>
<Profile>
<ProfileIcon bgColor={profileIcon.bgColor ?? ''}>{profileIcon.initials}</ProfileIcon>
{profileIcon && <ProfileIcon bgColor={profileIcon.bgColor ?? ''}>{profileIcon.initials}</ProfileIcon>}
<ProfileInfo>
<InfoTitle>{displayName}</InfoTitle>
<InfoUsername>{username}</InfoUsername>

View File

@ -5,7 +5,7 @@ import { SaveButton, DeleteButton, LabelBox, EditLabelForm, FieldLabel, FieldNam
type Props = {
labelColors: Array<LabelColor>;
label: Label | null;
label: ProjectLabel | null;
onLabelEdit: (labelId: string | null, labelName: string, labelColor: LabelColor) => void;
onLabelDelete?: (labelId: string) => void;
};
@ -32,7 +32,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props)
onChange={e => {
setCurrentLabel(e.currentTarget.value);
}}
value={currentLabel}
value={currentLabel ?? ''}
/>
<FieldLabel>Select a color</FieldLabel>
<div>
@ -56,7 +56,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props)
e.preventDefault();
console.log(currentColor);
if (currentColor) {
onLabelEdit(label ? label.labelId : null, currentLabel, currentColor);
onLabelEdit(label ? label.id : null, currentLabel ?? '', currentColor);
}
}}
/>
@ -66,7 +66,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props)
type="submit"
onClick={e => {
e.preventDefault();
onLabelDelete(label.labelId);
onLabelDelete(label.id);
}}
/>
)}

View File

@ -14,12 +14,14 @@ import {
} from './Styles';
type Props = {
labels?: Label[];
labels?: Array<ProjectLabel>;
taskLabels?: Array<TaskLabel>;
onLabelToggle: (labelId: string) => void;
onLabelEdit: (labelId: string) => void;
onLabelCreate: () => void;
};
const LabelManager: React.FC<Props> = ({ labels, onLabelToggle, onLabelEdit, onLabelCreate }) => {
const LabelManager: React.FC<Props> = ({ labels, taskLabels, onLabelToggle, onLabelEdit, onLabelCreate }) => {
const $fieldName = useRef<HTMLInputElement>(null);
const [currentLabel, setCurrentLabel] = useState('');
const [currentSearch, setCurrentSearch] = useState('');
@ -44,27 +46,31 @@ const LabelManager: React.FC<Props> = ({ labels, onLabelToggle, onLabelEdit, onL
<Labels>
{labels &&
labels
.filter(label => currentSearch === '' || label.name.toLowerCase().startsWith(currentSearch.toLowerCase()))
.filter(
label =>
currentSearch === '' ||
(label.name && label.name.toLowerCase().startsWith(currentSearch.toLowerCase())),
)
.map(label => (
<Label key={label.labelId}>
<Label key={label.id}>
<LabelIcon
onClick={() => {
onLabelEdit(label.labelId);
onLabelEdit(label.id);
}}
>
<Pencil color="#c2c6dc" />
</LabelIcon>
<CardLabel
key={label.labelId}
key={label.id}
color={label.labelColor.colorHex}
active={currentLabel === label.labelId}
active={currentLabel === label.id}
onMouseEnter={() => {
setCurrentLabel(label.labelId);
setCurrentLabel(label.id);
}}
onClick={() => onLabelToggle(label.labelId)}
onClick={() => onLabelToggle(label.id)}
>
{label.name}
{label.active && (
{taskLabels && taskLabels.find(t => t.projectLabel.id === label.id) && (
<ActiveIcon>
<Checkmark color="#fff" />
</ActiveIcon>

View File

@ -7,12 +7,13 @@ import ListActions from 'shared/components/ListActions';
import MemberManager from 'shared/components/MemberManager';
import DueDateManager from 'shared/components/DueDateManager';
import MiniProfile from 'shared/components/MiniProfile';
import styled from 'styled-components';
import PopupMenu, { PopupProvider, usePopup, Popup } from '.';
import styled from 'styled-components';
import produce from 'immer';
import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles';
import produce from 'immer';
import PopupMenu, { PopupProvider, usePopup, Popup } from '.';
export default {
component: PopupMenu,
@ -24,28 +25,17 @@ export default {
],
},
};
const labelData = [
const labelData: Array<ProjectLabel> = [
{
labelId: 'development',
id: 'development',
name: 'Development',
createdDate: new Date().toString(),
labelColor: {
id: '1',
name: 'white',
colorHex: LabelColors.BLUE,
name: 'blue',
position: 1,
},
active: false,
},
{
labelId: 'general',
name: 'General',
labelColor: {
id: '1',
name: 'white',
colorHex: LabelColors.PINK,
position: 1,
},
active: false,
},
];
@ -74,9 +64,9 @@ const LabelManagerEditor = () => {
onLabelToggle={labelId => {
setLabels(
produce(labels, draftState => {
const idx = labels.findIndex(label => label.labelId === labelId);
const idx = labels.findIndex(label => label.id === labelId);
if (idx !== -1) {
draftState[idx] = { ...draftState[idx], active: !labels[idx].active };
draftState[idx] = { ...draftState[idx] };
}
}),
);
@ -86,13 +76,21 @@ const LabelManagerEditor = () => {
<Popup onClose={action('on close')} title="Edit label" tab={1}>
<LabelEditor
labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]}
label={labels.find(label => label.labelId === currentLabel) ?? null}
label={labels.find(label => label.id === currentLabel) ?? null}
onLabelEdit={(_labelId, name, color) => {
setLabels(
produce(labels, draftState => {
const idx = labels.findIndex(label => label.labelId === currentLabel);
const idx = labels.findIndex(label => label.id === currentLabel);
if (idx !== -1) {
draftState[idx] = { ...draftState[idx], name, labelColor: color };
draftState[idx] = {
...draftState[idx],
name,
labelColor: {
...draftState[idx].labelColor,
name: color.name ?? '',
colorHex: color.colorHex,
},
};
}
}),
);
@ -105,7 +103,20 @@ const LabelManagerEditor = () => {
label={null}
labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]}
onLabelEdit={(_labelId, name, color) => {
setLabels([...labels, { labelId: name, name, labelColor: color, active: false }]);
setLabels([
...labels,
{
id: name,
name,
createdDate: new Date().toString(),
labelColor: {
id: color.id,
colorHex: color.colorHex,
name: color.name ?? '',
position: 1,
},
},
]);
setTab(0);
}}
/>
@ -214,7 +225,12 @@ export const MemberManagerPopup = () => {
<PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<MemberManager
availableMembers={[
{ userID: '1', displayName: 'Jordan Knott', profileIcon: { bgColor: null, url: null, initials: null } },
{
id: '1',
firstName: 'Jordan',
lastName: 'Knott',
profileIcon: { bgColor: null, url: null, initials: null },
},
]}
activeMembers={[]}
onMemberChange={action('member change')}
@ -251,26 +267,35 @@ export const DueDateManagerPopup = () => {
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<DueDateManager
task={{
taskID: '1',
taskGroup: { name: 'General', taskGroupID: '1' },
id: '1',
taskGroup: { name: 'General', id: '1', position: 1 },
name: 'Hello, world',
position: 1,
labels: [
{
labelId: 'soft-skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
id: 'soft-skills',
assignedDate: new Date().toString(),
projectLabel: {
createdDate: new Date().toString(),
id: 'label-soft-skills',
name: 'Soft Skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
},
},
active: true,
name: 'Soft Skills',
},
],
description: 'hello!',
members: [
{ userID: '1', profileIcon: { bgColor: null, url: null, initials: null }, displayName: 'Jordan Knott' },
assigned: [
{
id: '1',
profileIcon: { bgColor: null, url: null, initials: null },
firstName: 'Jordan',
lastName: 'Knott',
},
],
}}
onCancel={action('cancel')}

View File

@ -17,28 +17,17 @@ export default {
},
};
const labelData = [
const labelData: Array<ProjectLabel> = [
{
labelId: 'development',
id: 'development',
name: 'Development',
createdDate: 'date',
labelColor: {
id: '1',
name: 'white',
id: 'label-color-blue',
colorHex: LabelColors.BLUE,
name: 'blue',
position: 1,
},
active: false,
},
{
labelId: 'general',
name: 'General',
labelColor: {
id: '1',
name: 'white',
colorHex: LabelColors.PINK,
position: 1,
},
active: false,
},
];
@ -70,7 +59,6 @@ export const Default = () => {
isComposerOpen={false}
onSaveName={action('on save name')}
onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={(taskGroupID, $targetRef) => console.log(taskGroupID, $targetRef)}
>
<ListCards>

View File

@ -22,7 +22,7 @@ type Props = {
onEditCard: (taskGroupID: string, taskID: string, cardName: string) => void;
onOpenPopup: (popupType: number, top: number, left: number) => void;
onArchiveCard: (taskGroupID: string, taskID: string) => void;
labels?: Label[];
labels?: Array<ProjectLabel>;
isOpen: boolean;
top: number;
left: number;
@ -72,7 +72,7 @@ const QuickCardEditor = ({
<ListCardLabels>
{labels &&
labels.map(label => (
<ListCardLabel color={label.labelColor.colorHex} key={label.name}>
<ListCardLabel color={label.labelColor.colorHex} key={label.id}>
{label.name}
</ListCardLabel>
))}

View File

@ -31,7 +31,7 @@ type TaskAssigneeProps = {
const TaskAssignee: React.FC<TaskAssigneeProps> = ({ member, onMemberProfile, size }) => {
const $memberRef = useRef<HTMLDivElement>(null);
return (
<TaskDetailAssignee ref={$memberRef} onClick={() => onMemberProfile($memberRef, member.userID)} key={member.userID}>
<TaskDetailAssignee ref={$memberRef} onClick={() => onMemberProfile($memberRef, member.id)} key={member.id}>
<ProfileIcon size={size}>{member.profileIcon.initials ?? ''}</ProfileIcon>
</TaskDetailAssignee>
);

View File

@ -244,11 +244,11 @@ export const TaskDetailLabels = styled.div`
flex-wrap: wrap;
`;
export const TaskDetailLabel = styled.div`
export const TaskDetailLabel = styled.div<{ color: string }>`
&:hover {
opacity: 0.8;
}
background-color: #00c2e0;
background-color: ${props => props.color};
color: #fff;
cursor: pointer;

View File

@ -29,29 +29,34 @@ export const Default = () => {
return (
<TaskDetails
task={{
taskID: '1',
taskGroup: { name: 'General', taskGroupID: '1' },
id: '1',
taskGroup: { name: 'General', id: '1' },
name: 'Hello, world',
position: 1,
labels: [
{
labelId: 'soft-skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
id: 'soft-skills',
assignedDate: new Date().toString(),
projectLabel: {
createdDate: new Date().toString(),
id: 'label-soft-skills',
name: 'Soft Skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
},
},
active: true,
name: 'Soft Skills',
},
],
description,
members: [
assigned: [
{
userID: '1',
id: '1',
profileIcon: { bgColor: null, url: null, initials: null },
displayName: 'Jordan Knott',
firstName: 'Jordan',
lastName: 'Knott',
},
],
}}

View File

@ -193,15 +193,15 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskDetailsSidebar>
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
<TaskDetailAssignees>
{task.members && task.members.length === 0 ? (
{task.assigned && task.assigned.length === 0 ? (
<UnassignedLabel ref={$unassignedRef} onClick={onUnassignedClick}>
Unassigned
</UnassignedLabel>
) : (
<>
{task.members &&
task.members.map(member => (
<TaskAssignee size={32} member={member} onMemberProfile={onMemberProfile} />
{task.assigned &&
task.assigned.map(member => (
<TaskAssignee key={member.id} size={32} member={member} onMemberProfile={onMemberProfile} />
))}
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}>
<TaskDetailsAddMemberIcon>
@ -214,7 +214,11 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskDetailSectionTitle>Labels</TaskDetailSectionTitle>
<TaskDetailLabels>
{task.labels.map(label => {
return <TaskDetailLabel>{label.name}</TaskDetailLabel>;
return (
<TaskDetailLabel key={label.projectLabel.id} color={label.projectLabel.labelColor.colorHex}>
{label.projectLabel.name}
</TaskDetailLabel>
);
})}
<TaskDetailsAddLabel ref={$addLabelRef} onClick={onAddLabel}>
<TaskDetailsAddLabelIcon>

View File

@ -1,4 +1,5 @@
import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea';
import { mixin } from 'shared/utils/styles';
export const NavbarWrapper = styled.div`
@ -135,7 +136,36 @@ export const ProjectName = styled.h1`
color: #c2c6dc;
font-weight: 600;
font-size: 20px;
padding: 6px 10px 6px 8px;
padding: 3px 10px 3px 8px;
font-family: 'Droid Sans';
margin: -4px 0;
`;
export const ProjectNameTextarea = styled(TextareaAutosize)`
font-family: 'Droid Sans';
border: none;
resize: none;
overflow: hidden;
overflow-wrap: break-word;
background: transparent;
border-radius: 3px;
box-shadow: none;
margin: -4px 0;
letter-spacing: normal;
word-spacing: normal;
text-transform: none;
text-indent: 0px;
text-shadow: none;
flex-direction: column;
text-align: start;
color: #c2c6dc;
font-weight: 600;
font-size: 20px;
padding: 3px 10px 3px 8px;
&:focus {
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
}
`;
export const ProjectSwitcher = styled.button`

View File

@ -1,8 +1,9 @@
import React, { useRef } from 'react';
import { Star, Bell, Cog, AngleDown } from 'shared/icons';
import React, { useRef, useState, useEffect } from 'react';
import { Star, Ellipsis, Bell, Cog, AngleDown } from 'shared/icons';
import {
NotificationContainer,
ProjectNameTextarea,
InviteButton,
GlobalActions,
ProjectActions,
@ -28,9 +29,72 @@ import TaskAssignee from 'shared/components/TaskAssignee';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import MiniProfile from 'shared/components/MiniProfile';
type ProjectHeadingProps = {
projectName: string;
onSaveProjectName?: (projectName: string) => void;
};
const ProjectHeading: React.FC<ProjectHeadingProps> = ({ projectName: initialProjectName, onSaveProjectName }) => {
const [isEditProjectName, setEditProjectName] = useState(false);
const [projectName, setProjectName] = useState(initialProjectName);
const $projectName = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
if (isEditProjectName && $projectName && $projectName.current) {
$projectName.current.focus();
$projectName.current.select();
}
}, [isEditProjectName]);
useEffect(() => {
setProjectName(initialProjectName);
}, [initialProjectName]);
const onProjectNameChange = (event: React.FormEvent<HTMLTextAreaElement>): void => {
setProjectName(event.currentTarget.value);
};
const onProjectNameBlur = () => {
if (onSaveProjectName) {
onSaveProjectName(projectName);
}
setEditProjectName(false);
};
const onProjectNameKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
if ($projectName && $projectName.current) {
$projectName.current.blur();
}
}
};
return (
<>
<Separator>»</Separator>
{isEditProjectName ? (
<ProjectNameTextarea
ref={$projectName}
onChange={onProjectNameChange}
onKeyDown={onProjectNameKeyDown}
onBlur={onProjectNameBlur}
spellCheck={false}
value={projectName}
/>
) : (
<ProjectName
onClick={() => {
setEditProjectName(true);
}}
>
{projectName}
</ProjectName>
)}
</>
);
};
type NavBarProps = {
projectName: string;
onProfileClick: (bottom: number, right: number) => void;
onSaveProjectName?: (projectName: string) => void;
onNotificationClick: () => void;
bgColor: string;
firstName: string;
@ -38,8 +102,10 @@ type NavBarProps = {
initials: string;
projectMembers?: Array<TaskUser> | null;
};
const NavBar: React.FC<NavBarProps> = ({
projectName,
onSaveProjectName,
onProfileClick,
onNotificationClick,
firstName,
@ -68,14 +134,14 @@ const NavBar: React.FC<NavBarProps> = ({
</Popup>,
);
};
return (
<NavbarWrapper>
<NavbarHeader>
<ProjectActions>
<ProjectMeta>
<ProjectSwitcher>Projects</ProjectSwitcher>
<Separator>»</Separator>
<ProjectName>{projectName}</ProjectName>
<ProjectHeading projectName={projectName} onSaveProjectName={onSaveProjectName} />
<ProjectSettingsButton>
<AngleDown color="#c2c6dc" />
</ProjectSettingsButton>
@ -94,7 +160,7 @@ const NavBar: React.FC<NavBarProps> = ({
{projectMembers && (
<ProjectMembers>
{projectMembers.map(member => (
<TaskAssignee key={member.userID} size={28} member={member} onMemberProfile={onMemberProfile} />
<TaskAssignee key={member.id} size={28} member={member} onMemberProfile={onMemberProfile} />
))}
<InviteButton>Invite</InviteButton>
</ProjectMembers>
@ -104,9 +170,7 @@ const NavBar: React.FC<NavBarProps> = ({
</NotificationContainer>
<ProfileContainer>
<ProfileNameWrapper>
<ProfileNamePrimary>
{firstName} {lastName}
</ProfileNamePrimary>
<ProfileNamePrimary>{`${firstName} ${lastName}`}</ProfileNamePrimary>
<ProfileNameSecondary>Manager</ProfileNameSecondary>
</ProfileNameWrapper>
<ProfileIcon ref={$profileRef} onClick={handleProfileClick} bgColor={bgColor}>

View File

@ -34,10 +34,8 @@ export type LabelColor = {
export type TaskLabel = {
__typename?: 'TaskLabel';
id: Scalars['ID'];
projectLabelID: Scalars['UUID'];
projectLabel: ProjectLabel;
assignedDate: Scalars['Time'];
colorHex: Scalars['String'];
name?: Maybe<Scalars['String']>;
};
export type ProfileIcon = {
@ -255,11 +253,10 @@ export type UpdateTaskDescriptionInput = {
export type AddTaskLabelInput = {
taskID: Scalars['UUID'];
labelColorID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
};
export type RemoveTaskLabelInput = {
taskID: Scalars['UUID'];
taskLabelID: Scalars['UUID'];
};
@ -289,12 +286,29 @@ export type UpdateProjectLabelColor = {
labelColorID: Scalars['UUID'];
};
export type ToggleTaskLabelInput = {
taskID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
};
export type ToggleTaskLabelPayload = {
__typename?: 'ToggleTaskLabelPayload';
active: Scalars['Boolean'];
task: Task;
};
export type UpdateProjectName = {
projectID: Scalars['UUID'];
name: Scalars['String'];
};
export type Mutation = {
__typename?: 'Mutation';
createRefreshToken: RefreshToken;
createUserAccount: UserAccount;
createTeam: Team;
createProject: Project;
updateProjectName: Project;
createProjectLabel: ProjectLabel;
deleteProjectLabel: ProjectLabel;
updateProjectLabel: ProjectLabel;
@ -305,6 +319,7 @@ export type Mutation = {
deleteTaskGroup: DeleteTaskGroupPayload;
addTaskLabel: Task;
removeTaskLabel: Task;
toggleTaskLabel: ToggleTaskLabelPayload;
createTask: Task;
updateTaskDescription: Task;
updateTaskLocation: Task;
@ -336,6 +351,11 @@ export type MutationCreateProjectArgs = {
};
export type MutationUpdateProjectNameArgs = {
input?: Maybe<UpdateProjectName>;
};
export type MutationCreateProjectLabelArgs = {
input: NewProjectLabel;
};
@ -386,6 +406,11 @@ export type MutationRemoveTaskLabelArgs = {
};
export type MutationToggleTaskLabelArgs = {
input: ToggleTaskLabelInput;
};
export type MutationCreateTaskArgs = {
input: NewTask;
};
@ -476,8 +501,19 @@ export type CreateTaskMutation = (
& Pick<Task, 'id' | 'name' | 'position' | 'description'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
), assigned: Array<(
& Pick<TaskGroup, 'id' | 'name' | 'position'>
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'firstName' | 'lastName'>
& { profileIcon: (
@ -580,7 +616,21 @@ export type FindProjectQuery = (
& { tasks: Array<(
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'position' | 'description'>
& { assigned: Array<(
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id' | 'name' | 'position'>
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'firstName' | 'lastName'>
& { profileIcon: (
@ -609,7 +659,18 @@ export type FindTaskQuery = (
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
), assigned: Array<(
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'firstName' | 'lastName'>
& { profileIcon: (
@ -650,6 +711,36 @@ export type MeQuery = (
) }
);
export type ToggleTaskLabelMutationVariables = {
taskID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
};
export type ToggleTaskLabelMutation = (
{ __typename?: 'Mutation' }
& { toggleTaskLabel: (
{ __typename?: 'ToggleTaskLabelPayload' }
& Pick<ToggleTaskLabelPayload, 'active'>
& { task: (
{ __typename?: 'Task' }
& Pick<Task, 'id'>
& { labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'createdDate' | 'name'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'name' | 'position'>
) }
) }
)> }
) }
) }
);
export type UnassignTaskMutationVariables = {
taskID: Scalars['UUID'];
userID: Scalars['UUID'];
@ -687,6 +778,20 @@ export type UpdateProjectLabelMutation = (
) }
);
export type UpdateProjectNameMutationVariables = {
projectID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateProjectNameMutation = (
{ __typename?: 'Mutation' }
& { updateProjectName: (
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
) }
);
export type UpdateTaskDescriptionMutationVariables = {
taskID: Scalars['UUID'];
description: Scalars['String'];
@ -834,6 +939,23 @@ export const CreateTaskDocument = gql`
description
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
@ -1032,13 +1154,13 @@ export const FindProjectDocument = gql`
labels {
id
createdDate
name
labelColor {
id
name
colorHex
position
}
name
}
taskGroups {
id
@ -1049,6 +1171,26 @@ export const FindProjectDocument = gql`
name
position
description
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
firstName
@ -1106,6 +1248,21 @@ export const FindTaskDocument = gql`
taskGroup {
id
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
firstName
@ -1219,6 +1376,57 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
export const ToggleTaskLabelDocument = gql`
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) {
active
task {
id
labels {
id
assignedDate
projectLabel {
id
createdDate
labelColor {
id
colorHex
name
position
}
name
}
}
}
}
}
`;
export type ToggleTaskLabelMutationFn = ApolloReactCommon.MutationFunction<ToggleTaskLabelMutation, ToggleTaskLabelMutationVariables>;
/**
* __useToggleTaskLabelMutation__
*
* To run a mutation, you first call `useToggleTaskLabelMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useToggleTaskLabelMutation` 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 [toggleTaskLabelMutation, { data, loading, error }] = useToggleTaskLabelMutation({
* variables: {
* taskID: // value for 'taskID'
* projectLabelID: // value for 'projectLabelID'
* },
* });
*/
export function useToggleTaskLabelMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<ToggleTaskLabelMutation, ToggleTaskLabelMutationVariables>) {
return ApolloReactHooks.useMutation<ToggleTaskLabelMutation, ToggleTaskLabelMutationVariables>(ToggleTaskLabelDocument, baseOptions);
}
export type ToggleTaskLabelMutationHookResult = ReturnType<typeof useToggleTaskLabelMutation>;
export type ToggleTaskLabelMutationResult = ApolloReactCommon.MutationResult<ToggleTaskLabelMutation>;
export type ToggleTaskLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<ToggleTaskLabelMutation, ToggleTaskLabelMutationVariables>;
export const UnassignTaskDocument = gql`
mutation unassignTask($taskID: UUID!, $userID: UUID!) {
unassignTask(input: {taskID: $taskID, userID: $userID}) {
@ -1299,6 +1507,40 @@ export function useUpdateProjectLabelMutation(baseOptions?: ApolloReactHooks.Mut
export type UpdateProjectLabelMutationHookResult = ReturnType<typeof useUpdateProjectLabelMutation>;
export type UpdateProjectLabelMutationResult = ApolloReactCommon.MutationResult<UpdateProjectLabelMutation>;
export type UpdateProjectLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateProjectLabelMutation, UpdateProjectLabelMutationVariables>;
export const UpdateProjectNameDocument = gql`
mutation updateProjectName($projectID: UUID!, $name: String!) {
updateProjectName(input: {projectID: $projectID, name: $name}) {
id
name
}
}
`;
export type UpdateProjectNameMutationFn = ApolloReactCommon.MutationFunction<UpdateProjectNameMutation, UpdateProjectNameMutationVariables>;
/**
* __useUpdateProjectNameMutation__
*
* To run a mutation, you first call `useUpdateProjectNameMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateProjectNameMutation` 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 [updateProjectNameMutation, { data, loading, error }] = useUpdateProjectNameMutation({
* variables: {
* projectID: // value for 'projectID'
* name: // value for 'name'
* },
* });
*/
export function useUpdateProjectNameMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateProjectNameMutation, UpdateProjectNameMutationVariables>) {
return ApolloReactHooks.useMutation<UpdateProjectNameMutation, UpdateProjectNameMutationVariables>(UpdateProjectNameDocument, baseOptions);
}
export type UpdateProjectNameMutationHookResult = ReturnType<typeof useUpdateProjectNameMutation>;
export type UpdateProjectNameMutationResult = ApolloReactCommon.MutationResult<UpdateProjectNameMutation>;
export type UpdateProjectNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateProjectNameMutation, UpdateProjectNameMutationVariables>;
export const UpdateTaskDescriptionDocument = gql`
mutation updateTaskDescription($taskID: UUID!, $description: String!) {
updateTaskDescription(input: {taskID: $taskID, description: $description}) {

View File

@ -6,6 +6,23 @@ mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
description
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id

View File

@ -14,13 +14,13 @@ query findProject($projectId: String!) {
labels {
id
createdDate
name
labelColor {
id
name
colorHex
position
}
name
}
taskGroups {
id
@ -31,6 +31,26 @@ query findProject($projectId: String!) {
name
position
description
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
firstName

View File

@ -7,6 +7,21 @@ query findTask($taskID: UUID!) {
taskGroup {
id
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
firstName

View File

@ -0,0 +1,29 @@
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
toggleTaskLabel(
input: {
taskID: $taskID,
projectLabelID: $projectLabelID
}
) {
active
task {
id
labels {
id
assignedDate
projectLabel {
id
createdDate
labelColor {
id
colorHex
name
position
}
name
}
}
}
}
}

View File

@ -0,0 +1,6 @@
mutation updateProjectName($projectID: UUID!, $name: String!) {
updateProjectName(input: {projectID: $projectID, name: $name}) {
id
name
}
}

View File

@ -3,9 +3,17 @@ import React from 'react';
type Props = {
size: number | string;
color: string;
vertical: boolean;
};
const Ellipsis = ({ size, color }: Props) => {
const Ellipsis = ({ size, color, vertical }: Props) => {
if (vertical) {
return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 192 512">
<path d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z" />
</svg>
);
}
return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 512 512">
<path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z" />
@ -16,6 +24,7 @@ const Ellipsis = ({ size, color }: Props) => {
Ellipsis.defaultProps = {
size: 16,
color: '#000',
vertical: false,
};
export default Ellipsis;

View File

@ -1,50 +0,0 @@
import produce from 'immer';
export const addTask = (currentState: BoardState, newTask: Task) => {
return produce(currentState, (draftState: BoardState) => {
draftState.tasks[newTask.taskID] = newTask;
});
};
export const deleteTask = (currentState: BoardState, taskID: string) => {
return produce(currentState, (draftState: BoardState) => {
delete draftState.tasks[taskID];
});
};
export const addTaskGroup = (currentState: BoardState, newTaskGroup: TaskGroup) => {
return produce(currentState, (draftState: BoardState) => {
draftState.columns[newTaskGroup.taskGroupID] = newTaskGroup;
});
};
export const updateTaskGroup = (currentState: BoardState, newTaskGroup: TaskGroup) => {
return produce(currentState, (draftState: BoardState) => {
draftState.columns[newTaskGroup.taskGroupID] = newTaskGroup;
});
};
export const updateTask = (currentState: BoardState, newTask: Task) => {
return produce(currentState, (draftState: BoardState) => {
draftState.tasks[newTask.taskID] = newTask;
});
};
export const deleteTaskGroup = (currentState: BoardState, deletedTaskGroupID: string) => {
return produce(currentState, (draftState: BoardState) => {
delete draftState.columns[deletedTaskGroupID];
const filteredTasks = Object.keys(currentState.tasks)
.filter(taskID => currentState.tasks[taskID].taskGroup.taskGroupID !== deletedTaskGroupID)
.reduce((obj: TaskState, key: string) => {
obj[key] = currentState.tasks[key];
return obj;
}, {});
draftState.tasks = filteredTasks;
});
};
export const updateTaskName = (currentState: BoardState, taskID: string, newName: string) => {
return produce(currentState, (draftState: BoardState) => {
draftState.tasks[taskID].name = newName;
});
};