feature(Project): add ability to create new task groups

This commit is contained in:
Jordan Knott 2020-04-10 21:22:58 -05:00
parent e022e7914a
commit bd73717deb
27 changed files with 847 additions and 248 deletions

View File

@ -7,12 +7,15 @@ import {
useCreateTaskMutation, useCreateTaskMutation,
useDeleteTaskMutation, useDeleteTaskMutation,
useUpdateTaskLocationMutation, useUpdateTaskLocationMutation,
useCreateTaskGroupMutation,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import Navbar from 'App/Navbar'; import Navbar from 'App/Navbar';
import TopNavbar from 'App/TopNavbar'; import TopNavbar from 'App/TopNavbar';
import Lists from 'shared/components/Lists'; import Lists from 'shared/components/Lists';
import QuickCardEditor from 'shared/components/QuickCardEditor'; import QuickCardEditor from 'shared/components/QuickCardEditor';
import PopupMenu from 'shared/components/PopupMenu';
import ListActions from 'shared/components/ListActions';
interface ColumnState { interface ColumnState {
[key: string]: TaskGroup; [key: string]: TaskGroup;
@ -65,13 +68,32 @@ interface ProjectParams {
} }
const initialState: State = { tasks: {}, columns: {} }; const initialState: State = { tasks: {}, columns: {} };
const initialPopupState = { left: 0, top: 0, isOpen: false, taskGroupID: '' };
const initialQuickCardEditorState: QuickCardEditorState = { isOpen: false, top: 0, left: 0 }; const initialQuickCardEditorState: QuickCardEditorState = { isOpen: false, top: 0, left: 0 };
const Project = () => { const Project = () => {
const { projectId } = useParams<ProjectParams>(); const { projectId } = useParams<ProjectParams>();
const [listsData, setListsData] = useState(initialState); const [listsData, setListsData] = useState(initialState);
const [popupData, setPopupData] = useState(initialPopupState);
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState); const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
const [updateTaskLocation] = useUpdateTaskLocationMutation(); const [updateTaskLocation] = useUpdateTaskLocationMutation();
const [createTaskGroup] = useCreateTaskGroupMutation({
onCompleted: newTaskGroupData => {
const newListsData = {
...listsData,
columns: {
...listsData.columns,
[newTaskGroupData.createTaskGroup.taskGroupID]: {
taskGroupID: newTaskGroupData.createTaskGroup.taskGroupID,
name: newTaskGroupData.createTaskGroup.name,
position: newTaskGroupData.createTaskGroup.position,
tasks: [],
},
},
};
setListsData(newListsData);
},
});
const [createTask] = useCreateTaskMutation({ const [createTask] = useCreateTaskMutation({
onCompleted: newTaskData => { onCompleted: newTaskData => {
const newListsData = { const newListsData = {
@ -79,7 +101,9 @@ const Project = () => {
tasks: { tasks: {
...listsData.tasks, ...listsData.tasks,
[newTaskData.createTask.taskID]: { [newTaskData.createTask.taskID]: {
taskGroupID: newTaskData.createTask.taskGroupID, taskGroup: {
taskGroupID: newTaskData.createTask.taskGroup.taskGroupID,
},
taskID: newTaskData.createTask.taskID, taskID: newTaskData.createTask.taskID,
name: newTaskData.createTask.name, name: newTaskData.createTask.name,
position: newTaskData.createTask.position, position: newTaskData.createTask.position,
@ -119,17 +143,19 @@ const Project = () => {
variables: { projectId }, variables: { projectId },
onCompleted: newData => { onCompleted: newData => {
const newListsData: State = { tasks: {}, columns: {} }; const newListsData: State = { tasks: {}, columns: {} };
newData.findProject.taskGroups.forEach((taskGroup: TaskGroup) => { newData.findProject.taskGroups.forEach(taskGroup => {
newListsData.columns[taskGroup.taskGroupID] = { newListsData.columns[taskGroup.taskGroupID] = {
taskGroupID: taskGroup.taskGroupID, taskGroupID: taskGroup.taskGroupID,
name: taskGroup.name, name: taskGroup.name,
position: taskGroup.position, position: taskGroup.position,
tasks: [], tasks: [],
}; };
taskGroup.tasks.forEach((task: Task) => { taskGroup.tasks.forEach(task => {
newListsData.tasks[task.taskID] = { newListsData.tasks[task.taskID] = {
taskID: task.taskID, taskID: task.taskID,
taskGroupID: taskGroup.taskGroupID, taskGroup: {
taskGroupID: taskGroup.taskGroupID,
},
name: task.name, name: task.name,
position: task.position, position: task.position,
labels: [], labels: [],
@ -163,7 +189,9 @@ const Project = () => {
setListsData(newState); setListsData(newState);
}; };
const onCardCreate = (taskGroupID: string, name: string) => { const onCardCreate = (taskGroupID: string, name: string) => {
const taskGroupTasks = Object.values(listsData.tasks).filter((task: Task) => task.taskGroupID === taskGroupID); const taskGroupTasks = Object.values(listsData.tasks).filter(
(task: Task) => task.taskGroup.taskGroupID === taskGroupID,
);
let position = 65535; let position = 65535;
if (taskGroupTasks.length !== 0) { if (taskGroupTasks.length !== 0) {
const [lastTask] = taskGroupTasks.sort((a: any, b: any) => a.position - b.position).slice(-1); const [lastTask] = taskGroupTasks.sort((a: any, b: any) => a.position - b.position).slice(-1);
@ -201,6 +229,16 @@ const Project = () => {
{...listsData} {...listsData}
onCardDrop={onCardDrop} onCardDrop={onCardDrop}
onListDrop={onListDrop} onListDrop={onListDrop}
onCreateList={listName => {
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;
}
createTaskGroup({ variables: { projectID: projectId, name: listName, position } });
}}
/> />
</Board> </Board>
</MainContent> </MainContent>
@ -208,7 +246,7 @@ const Project = () => {
<QuickCardEditor <QuickCardEditor
isOpen isOpen
taskID={quickCardEditor.task ? quickCardEditor.task.taskID : ''} taskID={quickCardEditor.task ? quickCardEditor.task.taskID : ''}
taskGroupID={quickCardEditor.task ? quickCardEditor.task.taskGroupID : ''} taskGroupID={quickCardEditor.task ? quickCardEditor.task.taskGroup.taskGroupID : ''}
cardTitle={quickCardEditor.task ? quickCardEditor.task.name : ''} cardTitle={quickCardEditor.task ? quickCardEditor.task.name : ''}
onCloseEditor={() => setQuickCardEditor(initialQuickCardEditorState)} onCloseEditor={() => setQuickCardEditor(initialQuickCardEditorState)}
onEditCard={(_listId: string, cardId: string, cardName: string) => { onEditCard={(_listId: string, cardId: string, cardName: string) => {
@ -221,6 +259,16 @@ const Project = () => {
left={quickCardEditor.left} left={quickCardEditor.left}
/> />
)} )}
{popupData.isOpen && (
<PopupMenu
title="List Actions"
top={popupData.top}
onClose={() => setPopupData(initialPopupState)}
left={popupData.left}
>
<ListActions taskGroupID={popupData.taskGroupID} />
</PopupMenu>
)}
</> </>
); );
} }

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

@ -5,9 +5,15 @@ type ContextMenuEvent = {
taskGroupID: string; taskGroupID: string;
}; };
type InnerTaskGroup = {
taskGroupID: string;
name?: string;
position?: number;
};
type Task = { type Task = {
taskID: string; taskID: string;
taskGroupID: string; taskGroup: InnerTaskGroup;
name: string; name: string;
position: number; position: number;
labels: Label[]; labels: Label[];
@ -18,7 +24,7 @@ type TaskGroup = {
taskGroupID: string; taskGroupID: string;
name: string; name: string;
position: number; position: number;
tasks: RemoteTask[]; tasks: Task[];
}; };
type Project = { type Project = {
@ -62,3 +68,15 @@ type LoginProps = {
setError: (field: string, eType: string, message: string) => void, setError: (field: string, eType: string, message: string) => void,
) => void; ) => void;
}; };
type ElementPosition = {
top: number;
left: number;
right: number;
bottom: number;
};
type ElementSize = {
width: number;
height: number;
};

View File

@ -0,0 +1,19 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import AddList from '.';
export default {
component: AddList,
title: 'AddList',
parameters: {
backgrounds: [
{ name: 'gray', value: '#262c49', default: true },
{ name: 'white', value: '#ffffff' },
],
},
};
export const Default = () => {
return <AddList onSave={action('on save')} />;
};

View File

@ -0,0 +1,98 @@
import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea/lib';
export const Wrapper = styled.div<{ editorOpen: boolean }>`
display: inline-block;
background-color: hsla(0, 0%, 100%, 0.24);
cursor: pointer;
border-radius: 3px;
height: auto;
min-height: 32px;
padding: 4px;
transition: background 85ms ease-in, opacity 40ms ease-in, border-color 85ms ease-in;
width: 272px;
margin: 0 4px;
margin-right: 8px;
${props =>
!props.editorOpen &&
css`
&:hover {
background-color: hsla(0, 0%, 100%, 0.32);
}
`}
${props =>
props.editorOpen &&
css`
background-color: #ebecf0;
border-radius: 3px;
height: auto;
min-height: 32px;
padding: 4px;
transition: background 85ms ease-in, opacity 40ms ease-in, border-color 85ms ease-in;
`}
`;
export const Placeholder = styled.span`
color: #c2c6dc;
display: flex;
align-items: center;
padding: 6px 8px;
transition: color 85ms ease-in;
`;
export const AddIconWrapper = styled.div`
color: #fff;
margin-right: 6px;
`;
export const ListNameEditorWrapper = styled.div`
display: flex;
`;
export const ListNameEditor = styled(TextareaAutosize)`
background: #fff;
border: none;
box-shadow: inset 0 0 0 2px #0079bf;
display: block;
margin: 0;
transition: margin 85ms ease-in, background 85ms ease-in;
width: 100%;
line-height: 20px;
padding: 8px 12px;
font-size: 14px;
outline: none;
`;
export const ListAddControls = styled.div`
height: 32px;
transition: margin 85ms ease-in, height 85ms ease-in;
overflow: hidden;
margin: 4px 0 0;
`;
export const AddListButton = styled.button`
background-color: #5aac44;
box-shadow: none;
border: none;
color: #fff;
float: left;
margin: 0 4px 0 0;
cursor: pointer;
display: inline-block;
font-weight: 400;
line-height: 20px;
padding: 6px 12px;
text-align: center;
border-radius: 3px;
font-size: 14px;
`;
export const CancelAdd = styled.div`
display: flex;
align-items: center;
justify-content: center;
height: 32px;
width: 32px;
cursor: pointer;
`;

View File

@ -0,0 +1,105 @@
import React, { useState, useRef, useEffect } from 'react';
import { Plus, Cross } from 'shared/icons';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import {
Wrapper,
Placeholder,
AddIconWrapper,
ListNameEditor,
ListAddControls,
CancelAdd,
AddListButton,
ListNameEditorWrapper,
} from './Styles';
type NameEditorProps = {
onSave: (listName: string) => void;
onCancel: () => void;
};
const NameEditor: React.FC<NameEditorProps> = ({ onSave, onCancel }) => {
const $editorRef = useRef<HTMLTextAreaElement>(null);
const [listName, setListName] = useState('');
useEffect(() => {
if ($editorRef && $editorRef.current) {
$editorRef.current.focus();
}
});
const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
onSave(listName);
setListName('');
if ($editorRef && $editorRef.current) {
$editorRef.current.focus();
}
}
};
return (
<>
<ListNameEditorWrapper>
<ListNameEditor
ref={$editorRef}
onKeyDown={onKeyDown}
value={listName}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setListName(e.currentTarget.value)}
/>
</ListNameEditorWrapper>
<ListAddControls>
<AddListButton
onClick={() => {
onSave(listName);
setListName('');
if ($editorRef && $editorRef.current) {
$editorRef.current.focus();
}
}}
>
Save
</AddListButton>
<CancelAdd onClick={() => onCancel()}>
<Cross />
</CancelAdd>
</ListAddControls>
</>
);
};
type AddListProps = {
onSave: (listName: string) => void;
};
const AddList: React.FC<AddListProps> = ({ onSave }) => {
const [editorOpen, setEditorOpen] = useState(false);
const $wrapperRef = useRef<HTMLDivElement>(null);
const onOutsideClick = () => {
setEditorOpen(false);
};
useOnOutsideClick($wrapperRef, editorOpen, onOutsideClick, null);
return (
<Wrapper
ref={$wrapperRef}
editorOpen={editorOpen}
onClick={() => {
if (!editorOpen) {
setEditorOpen(true);
}
}}
>
{editorOpen ? (
<NameEditor onCancel={() => setEditorOpen(false)} onSave={onSave} />
) : (
<Placeholder>
<AddIconWrapper>
<Plus size={12} color="#c2c6dc" />
</AddIconWrapper>
Add another list
</Placeholder>
)}
</Wrapper>
);
};
export default AddList;

View File

@ -25,20 +25,17 @@ type Props = {
const CardComposer = ({ isOpen, onCreateCard, onClose }: Props) => { const CardComposer = ({ isOpen, onCreateCard, onClose }: Props) => {
const [cardName, setCardName] = useState(''); const [cardName, setCardName] = useState('');
const $cardEditor: any = useRef(null); const $cardEditor: any = useRef(null);
const onClick = () => { const handleCreateCard = () => {
onCreateCard(cardName); onCreateCard(cardName);
setCardName('');
if ($cardEditor && $cardEditor.current) {
$cardEditor.current.focus();
}
}; };
const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
onCreateCard(cardName); handleCreateCard();
}
};
const onBlur = () => {
if (cardName === '') {
onClose();
} else {
onCreateCard(cardName);
} }
}; };
useOnEscapeKeyDown(isOpen, onClose); useOnEscapeKeyDown(isOpen, onClose);
@ -63,7 +60,7 @@ const CardComposer = ({ isOpen, onCreateCard, onClose }: Props) => {
</ListCard> </ListCard>
<ComposerControls> <ComposerControls>
<ComposerControlsSaveSection> <ComposerControlsSaveSection>
<AddCardButton onClick={onClick}>Add Card</AddCardButton> <AddCardButton onClick={handleCreateCard}>Add Card</AddCardButton>
<CancelIcon onClick={onClose} icon={faTimes} color="#42526e" /> <CancelIcon onClick={onClose} icon={faTimes} color="#42526e" />
</ComposerControlsSaveSection> </ComposerControlsSaveSection>
<ComposerControlsActionsSection /> <ComposerControlsActionsSection />

View File

@ -59,6 +59,7 @@ export const Default = () => {
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]} tasks={[]}
onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>
<CardComposer <CardComposer
@ -84,6 +85,7 @@ export const WithCardComposer = () => {
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]} tasks={[]}
onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>
<CardComposer <CardComposer
@ -110,6 +112,7 @@ export const WithCard = () => {
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]} tasks={[]}
onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>
<Card <Card
@ -148,6 +151,7 @@ export const WithCardAndComposer = () => {
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]} tasks={[]}
onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>
<Card <Card

View File

@ -117,3 +117,12 @@ export const ListCards = styled.div`
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
`; `;
export const ListExtraMenuButtonWrapper = styled.div`
cursor: pointer;
position: absolute;
right: 4px;
top: 4px;
z-index: 1;
padding: 6px;
`;

View File

@ -1,7 +1,6 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown'; import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Plus, Ellipsis } from 'shared/icons';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { import {
Container, Container,
@ -13,6 +12,7 @@ import {
AddCardButton, AddCardButton,
AddCardButtonText, AddCardButtonText,
ListCards, ListCards,
ListExtraMenuButtonWrapper,
} from './Styles'; } from './Styles';
type Props = { type Props = {
@ -26,20 +26,32 @@ type Props = {
wrapperProps?: any; wrapperProps?: any;
headerProps?: any; headerProps?: any;
index?: number; index?: number;
onExtraMenuOpen: (taskGroupID: string, pos: ElementPosition, size: ElementSize) => void;
}; };
const List = React.forwardRef( const List = React.forwardRef(
( (
{ id, name, onSaveName, isComposerOpen, onOpenComposer, children, wrapperProps, headerProps }: Props, {
id,
name,
onSaveName,
isComposerOpen,
onOpenComposer,
children,
wrapperProps,
headerProps,
onExtraMenuOpen,
}: Props,
$wrapperRef: any, $wrapperRef: any,
) => { ) => {
const [listName, setListName] = useState(name); const [listName, setListName] = useState(name);
const [isEditingTitle, setEditingTitle] = useState(false); const [isEditingTitle, setEditingTitle] = useState(false);
const $listNameRef: any = useRef<HTMLTextAreaElement>(); const $listNameRef = useRef<HTMLTextAreaElement>(null);
const $extraActionsRef = useRef<HTMLDivElement>(null);
const onClick = () => { const onClick = () => {
setEditingTitle(true); setEditingTitle(true);
if ($listNameRef) { if ($listNameRef && $listNameRef.current) {
$listNameRef.current.select(); $listNameRef.current.select();
} }
}; };
@ -48,7 +60,9 @@ const List = React.forwardRef(
onSaveName(listName); onSaveName(listName);
}; };
const onEscape = () => { const onEscape = () => {
$listNameRef.current.blur(); if ($listNameRef && $listNameRef.current) {
$listNameRef.current.blur();
}
}; };
const onChange = (event: React.FormEvent<HTMLTextAreaElement>): void => { const onChange = (event: React.FormEvent<HTMLTextAreaElement>): void => {
setListName(event.currentTarget.value); setListName(event.currentTarget.value);
@ -56,7 +70,28 @@ const List = React.forwardRef(
const onKeyDown = (e: React.KeyboardEvent) => { const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
$listNameRef.current.blur(); if ($listNameRef && $listNameRef.current) {
$listNameRef.current.blur();
}
}
};
const handleExtraMenuOpen = () => {
if ($extraActionsRef && $extraActionsRef.current) {
const pos = $extraActionsRef.current.getBoundingClientRect();
onExtraMenuOpen(
id,
{
top: pos.top,
left: pos.left,
right: pos.right,
bottom: pos.bottom,
},
{
width: pos.width,
height: pos.height,
},
);
} }
}; };
useOnEscapeKeyDown(isEditingTitle, onEscape); useOnEscapeKeyDown(isEditingTitle, onEscape);
@ -74,11 +109,14 @@ const List = React.forwardRef(
spellCheck={false} spellCheck={false}
value={listName} value={listName}
/> />
<ListExtraMenuButtonWrapper ref={$extraActionsRef} onClick={handleExtraMenuOpen}>
<Ellipsis size={16} color="#c2c6dc" />
</ListExtraMenuButtonWrapper>
</Header> </Header>
{children && children} {children && children}
<AddCardContainer hidden={isComposerOpen}> <AddCardContainer hidden={isComposerOpen}>
<AddCardButton onClick={() => onOpenComposer(id)}> <AddCardButton onClick={() => onOpenComposer(id)}>
<FontAwesomeIcon icon={faPlus} size="xs" color="#42526e" /> <Plus size={12} color="#42526e" />
<AddCardButtonText>Add another card</AddCardButtonText> <AddCardButtonText>Add another card</AddCardButtonText>
</AddCardButton> </AddCardButton>
</AddCardContainer> </AddCardContainer>

View File

@ -0,0 +1,17 @@
import React from 'react';
import ListActions from '.';
export default {
component: ListActions,
title: 'ListActions',
parameters: {
backgrounds: [
{ name: 'white', value: '#ffffff', default: true },
{ name: 'gray', value: '#f8f8f8' },
],
},
};
export const Default = () => {
return <ListActions taskGroupID="1" />;
};

View File

@ -0,0 +1,35 @@
import styled from 'styled-components';
export const ListActionsWrapper = styled.ul`
list-style-type: none;
margin: 0;
padding: 0;
`;
export const ListActionItemWrapper = styled.li`
margin: 0;
padding: 0;
`;
export const ListActionItem = styled.span`
cursor: pointer;
display: block;
font-size: 14px;
color: #172b4d;
font-weight: 400;
padding: 6px 12px;
position: relative;
margin: 0 -12px;
text-decoration: none;
&:hover {
background-color: rgba(9, 30, 66, 0.04);
}
`;
export const ListSeparator = styled.hr`
background-color: rgba(9, 30, 66, 0.13);
border: 0;
height: 1px;
margin: 8px 0;
padding: 0;
width: 100%;
`;

View File

@ -0,0 +1,48 @@
import React from 'react';
import { ListActionsWrapper, ListActionItemWrapper, ListActionItem, ListSeparator } from './Styles';
type Props = {
taskGroupID: string;
};
const LabelManager = ({ taskGroupID }: Props) => {
return (
<>
<ListActionsWrapper>
<ListActionItemWrapper>
<ListActionItem>Add card...</ListActionItem>
</ListActionItemWrapper>
<ListActionItemWrapper>
<ListActionItem>Copy List...</ListActionItem>
</ListActionItemWrapper>
<ListActionItemWrapper>
<ListActionItem>Move card...</ListActionItem>
</ListActionItemWrapper>
<ListActionItemWrapper>
<ListActionItem>Watch</ListActionItem>
</ListActionItemWrapper>
</ListActionsWrapper>
<ListSeparator />
<ListActionsWrapper>
<ListActionItemWrapper>
<ListActionItem>Sort By...</ListActionItem>
</ListActionItemWrapper>
</ListActionsWrapper>
<ListSeparator />
<ListActionsWrapper>
<ListActionItemWrapper>
<ListActionItem>Move All Cards in This List...</ListActionItem>
</ListActionItemWrapper>
<ListActionItemWrapper>
<ListActionItem>Archive All Cards in This List...</ListActionItem>
</ListActionItemWrapper>
</ListActionsWrapper>
<ListSeparator />
<ListActionsWrapper>
<ListActionItemWrapper>
<ListActionItem>Archive This List</ListActionItem>
</ListActionItemWrapper>
</ListActionsWrapper>
</>
);
};
export default LabelManager;

View File

@ -7,8 +7,8 @@ export default {
title: 'Lists', title: 'Lists',
parameters: { parameters: {
backgrounds: [ backgrounds: [
{ name: 'white', value: '#ffffff', default: true }, { name: 'gray', value: '#262c49', default: true },
{ name: 'gray', value: '#f8f8f8' }, { name: 'white', value: '#ffffff' },
], ],
}, },
}; };
@ -33,28 +33,28 @@ const initialListsData = {
tasks: { tasks: {
'task-1': { 'task-1': {
taskID: 'task-1', taskID: 'task-1',
taskGroupID: 'column-1', taskGroup: { taskGroupID: 'column-1' },
name: 'Create roadmap', name: 'Create roadmap',
position: 2, position: 2,
labels: [], labels: [],
}, },
'task-2': { 'task-2': {
taskID: 'task-2', taskID: 'task-2',
taskGroupID: 'column-1', taskGroup: { taskGroupID: 'column-1' },
position: 1, position: 1,
name: 'Create authentication', name: 'Create authentication',
labels: [], labels: [],
}, },
'task-3': { 'task-3': {
taskID: 'task-3', taskID: 'task-3',
taskGroupID: 'column-1', taskGroup: { taskGroupID: 'column-1' },
position: 3, position: 3,
name: 'Create login', name: 'Create login',
labels: [], labels: [],
}, },
'task-4': { 'task-4': {
taskID: 'task-4', taskID: 'task-4',
taskGroupID: 'column-1', taskGroup: { taskGroupID: 'column-1' },
position: 4, position: 4,
name: 'Create plugins', name: 'Create plugins',
labels: [], labels: [],
@ -93,6 +93,29 @@ export const Default = () => {
onCardDrop={onCardDrop} onCardDrop={onCardDrop}
onListDrop={onListDrop} onListDrop={onListDrop}
onCardCreate={action('card create')} 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);
}}
/> />
); );
}; };
@ -121,28 +144,28 @@ const initialListsDataLarge = {
tasks: { tasks: {
'task-1': { 'task-1': {
taskID: 'task-1', taskID: 'task-1',
taskGroupID: 'column-1', taskGroup: { taskGroupID: 'column-1' },
name: 'Create roadmap', name: 'Create roadmap',
position: 2, position: 2,
labels: [], labels: [],
}, },
'task-2': { 'task-2': {
taskID: 'task-2', taskID: 'task-2',
taskGroupID: 'column-1', taskGroup: { taskGroupID: 'column-1' },
position: 1, position: 1,
name: 'Create authentication', name: 'Create authentication',
labels: [], labels: [],
}, },
'task-3': { 'task-3': {
taskID: 'task-3', taskID: 'task-3',
taskGroupID: 'column-1', taskGroup: { taskGroupID: 'column-1' },
position: 3, position: 3,
name: 'Create login', name: 'Create login',
labels: [], labels: [],
}, },
'task-4': { 'task-4': {
taskID: 'task-4', taskID: 'task-4',
taskGroupID: 'column-1', taskGroup: { taskGroupID: 'column-1' },
position: 4, position: 4,
name: 'Create plugins', name: 'Create plugins',
labels: [], labels: [],
@ -179,6 +202,7 @@ export const ListsWithManyList = () => {
onCardCreate={action('card create')} onCardCreate={action('card create')}
onCardDrop={onCardDrop} onCardDrop={onCardDrop}
onListDrop={onListDrop} onListDrop={onListDrop}
onCreateList={action('create list')}
/> />
); );
}; };

View File

@ -3,6 +3,7 @@ import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautif
import List, { ListCards } from 'shared/components/List'; import List, { ListCards } from 'shared/components/List';
import Card from 'shared/components/Card'; import Card from 'shared/components/Card';
import CardComposer from 'shared/components/CardComposer'; import CardComposer from 'shared/components/CardComposer';
import AddList from 'shared/components/AddList';
import { import {
isPositionChanged, isPositionChanged,
getSortedDraggables, getSortedDraggables,
@ -26,9 +27,10 @@ type Props = {
onListDrop: any; onListDrop: any;
onCardCreate: (taskGroupID: string, name: string) => void; onCardCreate: (taskGroupID: string, name: string) => void;
onQuickEditorOpen: (e: ContextMenuEvent) => void; onQuickEditorOpen: (e: ContextMenuEvent) => void;
onCreateList: (listName: string) => void;
}; };
const Lists = ({ columns, tasks, onCardDrop, onListDrop, onCardCreate, onQuickEditorOpen }: Props) => { const Lists = ({ columns, tasks, onCardDrop, onListDrop, onCardCreate, onQuickEditorOpen, onCreateList }: Props) => {
const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => { const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => {
if (typeof destination === 'undefined') return; if (typeof destination === 'undefined') return;
if (!isPositionChanged(source, destination)) return; if (!isPositionChanged(source, destination)) return;
@ -74,7 +76,7 @@ const Lists = ({ columns, tasks, onCardDrop, onListDrop, onCardCreate, onQuickEd
<Container {...provided.droppableProps} ref={provided.innerRef}> <Container {...provided.droppableProps} ref={provided.innerRef}>
{orderedColumns.map((column: TaskGroup, index: number) => { {orderedColumns.map((column: TaskGroup, index: number) => {
const columnCards = getSortedDraggables( const columnCards = getSortedDraggables(
Object.values(tasks).filter((t: any) => t.taskGroupID === column.taskGroupID), Object.values(tasks).filter((t: Task) => t.taskGroup.taskGroupID === column.taskGroupID),
); );
return ( return (
<Draggable draggableId={column.taskGroupID} key={column.taskGroupID} index={index}> <Draggable draggableId={column.taskGroupID} key={column.taskGroupID} index={index}>
@ -91,6 +93,7 @@ const Lists = ({ columns, tasks, onCardDrop, onListDrop, onCardCreate, onQuickEd
ref={columnDragProvided.innerRef} ref={columnDragProvided.innerRef}
wrapperProps={columnDragProvided.draggableProps} wrapperProps={columnDragProvided.draggableProps}
headerProps={columnDragProvided.dragHandleProps} headerProps={columnDragProvided.dragHandleProps}
onExtraMenuOpen={(taskGroupID, pos, size) => console.log(taskGroupID, pos, size)}
> >
<Droppable type="tasks" droppableId={column.taskGroupID}> <Droppable type="tasks" droppableId={column.taskGroupID}>
{columnDropProvided => ( {columnDropProvided => (
@ -127,7 +130,6 @@ const Lists = ({ columns, tasks, onCardDrop, onListDrop, onCardCreate, onQuickEd
setCurrentComposer(''); setCurrentComposer('');
}} }}
onCreateCard={name => { onCreateCard={name => {
setCurrentComposer('');
onCardCreate(column.taskGroupID, name); onCardCreate(column.taskGroupID, name);
}} }}
isOpen isOpen
@ -142,6 +144,7 @@ const Lists = ({ columns, tasks, onCardDrop, onListDrop, onCardCreate, onQuickEd
); );
})} })}
{provided.placeholder} {provided.placeholder}
<AddList onSave={onCreateList} />
</Container> </Container>
)} )}
</Droppable> </Droppable>

View File

@ -8,7 +8,7 @@ type Props = {
onLabelToggle: (labelId: string) => void; onLabelToggle: (labelId: string) => void;
onLabelEdit: (labelId: string, labelName: string, color: string) => void; onLabelEdit: (labelId: string, labelName: string, color: string) => void;
}; };
const LabelManager = ({ labels, onLabelToggle, onLabelEdit }: Props) => { const LabelManager: React.FC<Props> = ({ labels, onLabelToggle, onLabelEdit }) => {
const [currentLabel, setCurrentLabel] = useState(''); const [currentLabel, setCurrentLabel] = useState('');
return ( return (
<> <>

View File

@ -1,7 +1,10 @@
import React, { useState } from 'react'; import React, { useState, useRef } from 'react';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import LabelColors from 'shared/constants/labelColors'; import LabelColors from 'shared/constants/labelColors';
import MenuTypes from 'shared/constants/menuTypes'; import LabelManager from 'shared/components/PopupMenu/LabelManager';
import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
import ListActions from 'shared/components/ListActions';
import PopupMenu from '.'; import PopupMenu from '.';
export default { export default {
@ -34,16 +37,9 @@ export const LabelsPopup = () => {
return ( return (
<> <>
{isPopupOpen && ( {isPopupOpen && (
<PopupMenu <PopupMenu title="Label" top={10} onClose={() => setPopupOpen(false)} left={10}>
title="Label" <LabelManager labels={labelData} onLabelToggle={action('label toggle')} onLabelEdit={action('label edit')} />
menuType={MenuTypes.LABEL_MANAGER} </PopupMenu>
top={10}
onClose={() => setPopupOpen(false)}
left={10}
onLabelEdit={action('label edit')}
onLabelToggle={action('label toggle')}
labels={labelData}
/>
)} )}
<button type="submit" onClick={() => setPopupOpen(true)}> <button type="submit" onClick={() => setPopupOpen(true)}>
Open Open
@ -57,16 +53,9 @@ export const LabelsLabelEditor = () => {
return ( return (
<> <>
{isPopupOpen && ( {isPopupOpen && (
<PopupMenu <PopupMenu title="Change Label" top={10} onClose={() => setPopupOpen(false)} left={10}>
title="Change Label" <LabelEditor label={labelData[0]} onLabelEdit={action('label edit')} />
menuType={MenuTypes.LABEL_EDITOR} </PopupMenu>
top={10}
onClose={() => setPopupOpen(false)}
left={10}
onLabelEdit={action('label edit')}
onLabelToggle={action('label toggle')}
labels={labelData}
/>
)} )}
<button type="submit" onClick={() => setPopupOpen(true)}> <button type="submit" onClick={() => setPopupOpen(true)}>
Open Open
@ -74,3 +63,39 @@ export const LabelsLabelEditor = () => {
</> </>
); );
}; };
const initalState = { left: 0, top: 0, isOpen: false };
export const ListActionsPopup = () => {
const $buttonRef = useRef<HTMLButtonElement>(null);
const [popupData, setPopupData] = useState(initalState);
return (
<>
{popupData.isOpen && (
<PopupMenu
title="List Actions"
top={popupData.top}
onClose={() => setPopupData(initalState)}
left={popupData.left}
>
<ListActions taskGroupID="1" />
</PopupMenu>
)}
<button
ref={$buttonRef}
type="submit"
onClick={() => {
if ($buttonRef && $buttonRef.current) {
const pos = $buttonRef.current.getBoundingClientRect();
setPopupData({
isOpen: true,
left: pos.left,
top: pos.top + pos.height + 10,
});
}
}}
>
Open
</button>
</>
);
};

View File

@ -1,25 +1,16 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { Cross } from 'shared/icons'; import { Cross } from 'shared/icons';
import useOnOutsideClick from 'shared/hooks/onOutsideClick'; import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import MenuTypes from 'shared/constants/menuTypes'; import { Container, Header, HeaderTitle, Content, CloseButton } from './Styles';
import LabelColors from 'shared/constants/labelColors';
import LabelManager from './LabelManager';
import LabelEditor from './LabelEditor';
import { Container, Header, HeaderTitle, Content, Label, CloseButton } from './Styles';
type Props = { type Props = {
title: string; title: string;
top: number; top: number;
left: number; left: number;
menuType: number;
labels?: Label[];
onClose: () => void; onClose: () => void;
onLabelToggle: (labelId: string) => void;
onLabelEdit: (labelId: string, labelName: string, color: string) => void;
}; };
const PopupMenu = ({ title, menuType, labels, top, left, onClose, onLabelToggle, onLabelEdit }: Props) => { const PopupMenu: React.FC<Props> = ({ title, top, left, onClose, children }) => {
const $containerRef = useRef(); const $containerRef = useRef();
useOnOutsideClick($containerRef, true, onClose, null); useOnOutsideClick($containerRef, true, onClose, null);
@ -31,17 +22,7 @@ const PopupMenu = ({ title, menuType, labels, top, left, onClose, onLabelToggle,
<Cross /> <Cross />
</CloseButton> </CloseButton>
</Header> </Header>
<Content> <Content>{children}</Content>
{menuType === MenuTypes.LABEL_MANAGER && (
<LabelManager onLabelEdit={onLabelEdit} onLabelToggle={onLabelToggle} labels={labels} />
)}
{menuType === MenuTypes.LABEL_EDITOR && (
<LabelEditor
onLabelEdit={onLabelEdit}
label={{ active: false, color: LabelColors.GREEN, name: 'General', labelId: 'general' }}
/>
)}
</Content>
</Container> </Container>
); );
}; };

View File

@ -61,6 +61,7 @@ export const Default = () => {
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]} tasks={[]}
onExtraMenuOpen={(taskGroupID, pos, size) => console.log(taskGroupID, pos, size)}
> >
<ListCards> <ListCards>
<Card <Card

View File

@ -30,13 +30,15 @@ export const Default = () => {
<TaskDetails <TaskDetails
task={{ task={{
taskID: '1', taskID: '1',
taskGroupID: '1', taskGroup: { taskGroupID: '1' },
name: 'Hello, world', name: 'Hello, world',
position: 1, position: 1,
labels: [], labels: [],
description, description,
}} }}
onTaskDescriptionChange={(_task, desc) => setDescription(desc)} onTaskDescriptionChange={(_task, desc) => setDescription(desc)}
onDeleteTask={action('delete task')}
onCloseModal={action('close modal')}
/> />
); );
}} }}

View File

@ -68,7 +68,7 @@ const DetailsEditor: React.FC<DetailsEditorProps> = ({
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.currentTarget.value)} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.currentTarget.value)}
/> />
<TaskDetailsControls> <TaskDetailsControls>
<ConfirmSave>Save</ConfirmSave> <ConfirmSave onClick={handleOutsideClick}>Save</ConfirmSave>
<CancelEdit onClick={onCancel}> <CancelEdit onClick={onCancel}>
<Cross size={16} /> <Cross size={16} />
</CancelEdit> </CancelEdit>
@ -80,22 +80,27 @@ const DetailsEditor: React.FC<DetailsEditorProps> = ({
type TaskDetailsProps = { type TaskDetailsProps = {
task: Task; task: Task;
onTaskDescriptionChange: (task: Task, newDescription: string) => void; onTaskDescriptionChange: (task: Task, newDescription: string) => void;
onDeleteTask: (task: Task) => void;
onCloseModal: () => void;
}; };
const TaskDetails: React.FC<TaskDetailsProps> = ({ task, onTaskDescriptionChange }) => { const TaskDetails: React.FC<TaskDetailsProps> = ({ task, onTaskDescriptionChange, onDeleteTask, onCloseModal }) => {
const [editorOpen, setEditorOpen] = useState(false); const [editorOpen, setEditorOpen] = useState(false);
const handleClick = () => { const handleClick = () => {
setEditorOpen(!editorOpen); setEditorOpen(!editorOpen);
}; };
const handleDeleteTask = () => {
onDeleteTask(task);
};
return ( return (
<> <>
<TaskHeader> <TaskHeader>
<TaskMeta /> <TaskMeta />
<TaskActions> <TaskActions>
<TaskAction> <TaskAction onClick={handleDeleteTask}>
<Bin size={20} /> <Bin size={20} />
</TaskAction> </TaskAction>
<TaskAction> <TaskAction onClick={onCloseModal}>
<Cross size={20} /> <Cross size={20} />
</TaskAction> </TaskAction>
</TaskActions> </TaskActions>

View File

@ -1,6 +1,7 @@
const MenuTypes = { const MenuTypes = {
LABEL_MANAGER: 1, LABEL_MANAGER: 1,
LABEL_EDITOR: 2, LABEL_EDITOR: 2,
LIST_ACTIONS: 3,
}; };
export default MenuTypes; export default MenuTypes;

View File

@ -13,8 +13,10 @@ export type Scalars = {
UUID: string; UUID: string;
}; };
export type RefreshToken = { export type RefreshToken = {
__typename?: 'RefreshToken'; __typename?: 'RefreshToken';
tokenId: Scalars['ID']; tokenId: Scalars['ID'];
userId: Scalars['UUID']; userId: Scalars['UUID'];
expiresAt: Scalars['Time']; expiresAt: Scalars['Time'];
@ -22,7 +24,7 @@ export type RefreshToken = {
}; };
export type UserAccount = { export type UserAccount = {
__typename?: 'UserAccount'; __typename?: 'UserAccount';
userID: Scalars['ID']; userID: Scalars['ID'];
email: Scalars['String']; email: Scalars['String'];
createdAt: Scalars['Time']; createdAt: Scalars['Time'];
@ -31,7 +33,7 @@ export type UserAccount = {
}; };
export type Organization = { export type Organization = {
__typename?: 'Organization'; __typename?: 'Organization';
organizationID: Scalars['ID']; organizationID: Scalars['ID'];
createdAt: Scalars['Time']; createdAt: Scalars['Time'];
name: Scalars['String']; name: Scalars['String'];
@ -39,7 +41,7 @@ export type Organization = {
}; };
export type Team = { export type Team = {
__typename?: 'Team'; __typename?: 'Team';
teamID: Scalars['ID']; teamID: Scalars['ID'];
createdAt: Scalars['Time']; createdAt: Scalars['Time'];
name: Scalars['String']; name: Scalars['String'];
@ -47,7 +49,7 @@ export type Team = {
}; };
export type Project = { export type Project = {
__typename?: 'Project'; __typename?: 'Project';
projectID: Scalars['ID']; projectID: Scalars['ID'];
teamID: Scalars['String']; teamID: Scalars['String'];
createdAt: Scalars['Time']; createdAt: Scalars['Time'];
@ -56,7 +58,7 @@ export type Project = {
}; };
export type TaskGroup = { export type TaskGroup = {
__typename?: 'TaskGroup'; __typename?: 'TaskGroup';
taskGroupID: Scalars['ID']; taskGroupID: Scalars['ID'];
projectID: Scalars['String']; projectID: Scalars['String'];
createdAt: Scalars['Time']; createdAt: Scalars['Time'];
@ -66,9 +68,9 @@ export type TaskGroup = {
}; };
export type Task = { export type Task = {
__typename?: 'Task'; __typename?: 'Task';
taskID: Scalars['ID']; taskID: Scalars['ID'];
taskGroupID: Scalars['String']; taskGroup: TaskGroup;
createdAt: Scalars['Time']; createdAt: Scalars['Time'];
name: Scalars['String']; name: Scalars['String'];
position: Scalars['Float']; position: Scalars['Float'];
@ -87,7 +89,7 @@ export type FindProject = {
}; };
export type Query = { export type Query = {
__typename?: 'Query'; __typename?: 'Query';
organizations: Array<Organization>; organizations: Array<Organization>;
users: Array<UserAccount>; users: Array<UserAccount>;
findUser: UserAccount; findUser: UserAccount;
@ -97,14 +99,17 @@ export type Query = {
taskGroups: Array<TaskGroup>; taskGroups: Array<TaskGroup>;
}; };
export type QueryFindUserArgs = { export type QueryFindUserArgs = {
input: FindUser; input: FindUser;
}; };
export type QueryFindProjectArgs = { export type QueryFindProjectArgs = {
input: FindProject; input: FindProject;
}; };
export type QueryProjectsArgs = { export type QueryProjectsArgs = {
input?: Maybe<ProjectsFilter>; input?: Maybe<ProjectsFilter>;
}; };
@ -161,7 +166,7 @@ export type DeleteTaskInput = {
}; };
export type DeleteTaskPayload = { export type DeleteTaskPayload = {
__typename?: 'DeleteTaskPayload'; __typename?: 'DeleteTaskPayload';
taskID: Scalars['String']; taskID: Scalars['String'];
}; };
@ -171,7 +176,7 @@ export type UpdateTaskName = {
}; };
export type Mutation = { export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
createRefreshToken: RefreshToken; createRefreshToken: RefreshToken;
createUserAccount: UserAccount; createUserAccount: UserAccount;
createOrganization: Organization; createOrganization: Organization;
@ -185,46 +190,57 @@ export type Mutation = {
deleteTask: DeleteTaskPayload; deleteTask: DeleteTaskPayload;
}; };
export type MutationCreateRefreshTokenArgs = { export type MutationCreateRefreshTokenArgs = {
input: NewRefreshToken; input: NewRefreshToken;
}; };
export type MutationCreateUserAccountArgs = { export type MutationCreateUserAccountArgs = {
input: NewUserAccount; input: NewUserAccount;
}; };
export type MutationCreateOrganizationArgs = { export type MutationCreateOrganizationArgs = {
input: NewOrganization; input: NewOrganization;
}; };
export type MutationCreateTeamArgs = { export type MutationCreateTeamArgs = {
input: NewTeam; input: NewTeam;
}; };
export type MutationCreateProjectArgs = { export type MutationCreateProjectArgs = {
input: NewProject; input: NewProject;
}; };
export type MutationCreateTaskGroupArgs = { export type MutationCreateTaskGroupArgs = {
input: NewTaskGroup; input: NewTaskGroup;
}; };
export type MutationCreateTaskArgs = { export type MutationCreateTaskArgs = {
input: NewTask; input: NewTask;
}; };
export type MutationUpdateTaskLocationArgs = { export type MutationUpdateTaskLocationArgs = {
input: NewTaskLocation; input: NewTaskLocation;
}; };
export type MutationLogoutUserArgs = { export type MutationLogoutUserArgs = {
input: LogoutUser; input: LogoutUser;
}; };
export type MutationUpdateTaskNameArgs = { export type MutationUpdateTaskNameArgs = {
input: UpdateTaskName; input: UpdateTaskName;
}; };
export type MutationDeleteTaskArgs = { export type MutationDeleteTaskArgs = {
input: DeleteTaskInput; input: DeleteTaskInput;
}; };
@ -235,45 +251,86 @@ export type CreateTaskMutationVariables = {
position: Scalars['Float']; position: Scalars['Float'];
}; };
export type CreateTaskMutation = { __typename?: 'Mutation' } & {
createTask: { __typename?: 'Task' } & Pick<Task, 'taskID' | 'taskGroupID' | 'name' | 'position'>; export type CreateTaskMutation = (
{ __typename?: 'Mutation' }
& { createTask: (
{ __typename?: 'Task' }
& Pick<Task, 'taskID' | 'name' | 'position'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'taskGroupID'>
) }
) }
);
export type CreateTaskGroupMutationVariables = {
projectID: Scalars['String'];
name: Scalars['String'];
position: Scalars['Float'];
}; };
export type CreateTaskGroupMutation = (
{ __typename?: 'Mutation' }
& { createTaskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'taskGroupID' | 'name' | 'position'>
) }
);
export type DeleteTaskMutationVariables = { export type DeleteTaskMutationVariables = {
taskID: Scalars['String']; taskID: Scalars['String'];
}; };
export type DeleteTaskMutation = { __typename?: 'Mutation' } & {
deleteTask: { __typename?: 'DeleteTaskPayload' } & Pick<DeleteTaskPayload, 'taskID'>; export type DeleteTaskMutation = (
}; { __typename?: 'Mutation' }
& { deleteTask: (
{ __typename?: 'DeleteTaskPayload' }
& Pick<DeleteTaskPayload, 'taskID'>
) }
);
export type FindProjectQueryVariables = { export type FindProjectQueryVariables = {
projectId: Scalars['String']; projectId: Scalars['String'];
}; };
export type FindProjectQuery = { __typename?: 'Query' } & {
findProject: { __typename?: 'Project' } & Pick<Project, 'name'> & { export type FindProjectQuery = (
taskGroups: Array< { __typename?: 'Query' }
{ __typename?: 'TaskGroup' } & Pick<TaskGroup, 'taskGroupID' | 'name' | 'position'> & { & { findProject: (
tasks: Array<{ __typename?: 'Task' } & Pick<Task, 'taskID' | 'name' | 'position'>>; { __typename?: 'Project' }
} & Pick<Project, 'name'>
>; & { taskGroups: Array<(
}; { __typename?: 'TaskGroup' }
}; & Pick<TaskGroup, 'taskGroupID' | 'name' | 'position'>
& { tasks: Array<(
{ __typename?: 'Task' }
& Pick<Task, 'taskID' | 'name' | 'position'>
)> }
)> }
) }
);
export type GetProjectsQueryVariables = {}; export type GetProjectsQueryVariables = {};
export type GetProjectsQuery = { __typename?: 'Query' } & {
organizations: Array< export type GetProjectsQuery = (
{ __typename?: 'Organization' } & Pick<Organization, 'name'> & { { __typename?: 'Query' }
teams: Array< & { organizations: Array<(
{ __typename?: 'Team' } & Pick<Team, 'name'> & { { __typename?: 'Organization' }
projects: Array<{ __typename?: 'Project' } & Pick<Project, 'name' | 'projectID'>>; & Pick<Organization, 'name'>
} & { teams: Array<(
>; { __typename?: 'Team' }
} & Pick<Team, 'name'>
>; & { projects: Array<(
}; { __typename?: 'Project' }
& Pick<Project, 'name' | 'projectID'>
)> }
)> }
)> }
);
export type UpdateTaskLocationMutationVariables = { export type UpdateTaskLocationMutationVariables = {
taskID: Scalars['String']; taskID: Scalars['String'];
@ -281,29 +338,42 @@ export type UpdateTaskLocationMutationVariables = {
position: Scalars['Float']; position: Scalars['Float'];
}; };
export type UpdateTaskLocationMutation = { __typename?: 'Mutation' } & {
updateTaskLocation: { __typename?: 'Task' } & Pick<Task, 'taskID' | 'createdAt' | 'name' | 'position'>; export type UpdateTaskLocationMutation = (
}; { __typename?: 'Mutation' }
& { updateTaskLocation: (
{ __typename?: 'Task' }
& Pick<Task, 'taskID' | 'createdAt' | 'name' | 'position'>
) }
);
export type UpdateTaskNameMutationVariables = { export type UpdateTaskNameMutationVariables = {
taskID: Scalars['String']; taskID: Scalars['String'];
name: Scalars['String']; name: Scalars['String'];
}; };
export type UpdateTaskNameMutation = { __typename?: 'Mutation' } & {
updateTaskName: { __typename?: 'Task' } & Pick<Task, 'taskID' | 'name' | 'position'>; export type UpdateTaskNameMutation = (
}; { __typename?: 'Mutation' }
& { updateTaskName: (
{ __typename?: 'Task' }
& Pick<Task, 'taskID' | 'name' | 'position'>
) }
);
export const CreateTaskDocument = gql` export const CreateTaskDocument = gql`
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) { mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: { taskGroupID: $taskGroupID, name: $name, position: $position }) { createTask(input: {taskGroupID: $taskGroupID, name: $name, position: $position}) {
taskID taskID
taskGroup {
taskGroupID taskGroupID
name
position
} }
name
position
} }
`; }
`;
export type CreateTaskMutationFn = ApolloReactCommon.MutationFunction<CreateTaskMutation, CreateTaskMutationVariables>; export type CreateTaskMutationFn = ApolloReactCommon.MutationFunction<CreateTaskMutation, CreateTaskMutationVariables>;
/** /**
@ -325,24 +395,55 @@ export type CreateTaskMutationFn = ApolloReactCommon.MutationFunction<CreateTask
* }, * },
* }); * });
*/ */
export function useCreateTaskMutation( export function useCreateTaskMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskMutation, CreateTaskMutationVariables>) {
baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskMutation, CreateTaskMutationVariables>, return ApolloReactHooks.useMutation<CreateTaskMutation, CreateTaskMutationVariables>(CreateTaskDocument, baseOptions);
) { }
return ApolloReactHooks.useMutation<CreateTaskMutation, CreateTaskMutationVariables>(CreateTaskDocument, baseOptions);
}
export type CreateTaskMutationHookResult = ReturnType<typeof useCreateTaskMutation>; export type CreateTaskMutationHookResult = ReturnType<typeof useCreateTaskMutation>;
export type CreateTaskMutationResult = ApolloReactCommon.MutationResult<CreateTaskMutation>; export type CreateTaskMutationResult = ApolloReactCommon.MutationResult<CreateTaskMutation>;
export type CreateTaskMutationOptions = ApolloReactCommon.BaseMutationOptions< export type CreateTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskMutation, CreateTaskMutationVariables>;
CreateTaskMutation, export const CreateTaskGroupDocument = gql`
CreateTaskMutationVariables mutation createTaskGroup($projectID: String!, $name: String!, $position: Float!) {
>; createTaskGroup(input: {projectID: $projectID, name: $name, position: $position}) {
export const DeleteTaskDocument = gql` taskGroupID
mutation deleteTask($taskID: String!) { name
deleteTask(input: { taskID: $taskID }) { position
taskID
}
} }
`; }
`;
export type CreateTaskGroupMutationFn = ApolloReactCommon.MutationFunction<CreateTaskGroupMutation, CreateTaskGroupMutationVariables>;
/**
* __useCreateTaskGroupMutation__
*
* To run a mutation, you first call `useCreateTaskGroupMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateTaskGroupMutation` 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 [createTaskGroupMutation, { data, loading, error }] = useCreateTaskGroupMutation({
* variables: {
* projectID: // value for 'projectID'
* name: // value for 'name'
* position: // value for 'position'
* },
* });
*/
export function useCreateTaskGroupMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskGroupMutation, CreateTaskGroupMutationVariables>) {
return ApolloReactHooks.useMutation<CreateTaskGroupMutation, CreateTaskGroupMutationVariables>(CreateTaskGroupDocument, baseOptions);
}
export type CreateTaskGroupMutationHookResult = ReturnType<typeof useCreateTaskGroupMutation>;
export type CreateTaskGroupMutationResult = ApolloReactCommon.MutationResult<CreateTaskGroupMutation>;
export type CreateTaskGroupMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskGroupMutation, CreateTaskGroupMutationVariables>;
export const DeleteTaskDocument = gql`
mutation deleteTask($taskID: String!) {
deleteTask(input: {taskID: $taskID}) {
taskID
}
}
`;
export type DeleteTaskMutationFn = ApolloReactCommon.MutationFunction<DeleteTaskMutation, DeleteTaskMutationVariables>; export type DeleteTaskMutationFn = ApolloReactCommon.MutationFunction<DeleteTaskMutation, DeleteTaskMutationVariables>;
/** /**
@ -362,34 +463,29 @@ export type DeleteTaskMutationFn = ApolloReactCommon.MutationFunction<DeleteTask
* }, * },
* }); * });
*/ */
export function useDeleteTaskMutation( export function useDeleteTaskMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteTaskMutation, DeleteTaskMutationVariables>) {
baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteTaskMutation, DeleteTaskMutationVariables>, return ApolloReactHooks.useMutation<DeleteTaskMutation, DeleteTaskMutationVariables>(DeleteTaskDocument, baseOptions);
) { }
return ApolloReactHooks.useMutation<DeleteTaskMutation, DeleteTaskMutationVariables>(DeleteTaskDocument, baseOptions);
}
export type DeleteTaskMutationHookResult = ReturnType<typeof useDeleteTaskMutation>; export type DeleteTaskMutationHookResult = ReturnType<typeof useDeleteTaskMutation>;
export type DeleteTaskMutationResult = ApolloReactCommon.MutationResult<DeleteTaskMutation>; export type DeleteTaskMutationResult = ApolloReactCommon.MutationResult<DeleteTaskMutation>;
export type DeleteTaskMutationOptions = ApolloReactCommon.BaseMutationOptions< export type DeleteTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteTaskMutation, DeleteTaskMutationVariables>;
DeleteTaskMutation,
DeleteTaskMutationVariables
>;
export const FindProjectDocument = gql` export const FindProjectDocument = gql`
query findProject($projectId: String!) { query findProject($projectId: String!) {
findProject(input: { projectId: $projectId }) { findProject(input: {projectId: $projectId}) {
name
taskGroups {
taskGroupID
name name
taskGroups { position
taskGroupID tasks {
taskID
name name
position position
tasks {
taskID
name
position
}
} }
} }
} }
`; }
`;
/** /**
* __useFindProjectQuery__ * __useFindProjectQuery__
@ -407,33 +503,29 @@ export const FindProjectDocument = gql`
* }, * },
* }); * });
*/ */
export function useFindProjectQuery( export function useFindProjectQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<FindProjectQuery, FindProjectQueryVariables>) {
baseOptions?: ApolloReactHooks.QueryHookOptions<FindProjectQuery, FindProjectQueryVariables>, return ApolloReactHooks.useQuery<FindProjectQuery, FindProjectQueryVariables>(FindProjectDocument, baseOptions);
) { }
return ApolloReactHooks.useQuery<FindProjectQuery, FindProjectQueryVariables>(FindProjectDocument, baseOptions); export function useFindProjectLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<FindProjectQuery, FindProjectQueryVariables>) {
} return ApolloReactHooks.useLazyQuery<FindProjectQuery, FindProjectQueryVariables>(FindProjectDocument, baseOptions);
export function useFindProjectLazyQuery( }
baseOptions?: ApolloReactHooks.LazyQueryHookOptions<FindProjectQuery, FindProjectQueryVariables>,
) {
return ApolloReactHooks.useLazyQuery<FindProjectQuery, FindProjectQueryVariables>(FindProjectDocument, baseOptions);
}
export type FindProjectQueryHookResult = ReturnType<typeof useFindProjectQuery>; export type FindProjectQueryHookResult = ReturnType<typeof useFindProjectQuery>;
export type FindProjectLazyQueryHookResult = ReturnType<typeof useFindProjectLazyQuery>; export type FindProjectLazyQueryHookResult = ReturnType<typeof useFindProjectLazyQuery>;
export type FindProjectQueryResult = ApolloReactCommon.QueryResult<FindProjectQuery, FindProjectQueryVariables>; export type FindProjectQueryResult = ApolloReactCommon.QueryResult<FindProjectQuery, FindProjectQueryVariables>;
export const GetProjectsDocument = gql` export const GetProjectsDocument = gql`
query getProjects { query getProjects {
organizations { organizations {
name
teams {
name name
teams { projects {
name name
projects { projectID
name
projectID
}
} }
} }
} }
`; }
`;
/** /**
* __useGetProjectsQuery__ * __useGetProjectsQuery__
@ -450,33 +542,26 @@ export const GetProjectsDocument = gql`
* }, * },
* }); * });
*/ */
export function useGetProjectsQuery( export function useGetProjectsQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<GetProjectsQuery, GetProjectsQueryVariables>) {
baseOptions?: ApolloReactHooks.QueryHookOptions<GetProjectsQuery, GetProjectsQueryVariables>, return ApolloReactHooks.useQuery<GetProjectsQuery, GetProjectsQueryVariables>(GetProjectsDocument, baseOptions);
) { }
return ApolloReactHooks.useQuery<GetProjectsQuery, GetProjectsQueryVariables>(GetProjectsDocument, baseOptions); export function useGetProjectsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<GetProjectsQuery, GetProjectsQueryVariables>) {
} return ApolloReactHooks.useLazyQuery<GetProjectsQuery, GetProjectsQueryVariables>(GetProjectsDocument, baseOptions);
export function useGetProjectsLazyQuery( }
baseOptions?: ApolloReactHooks.LazyQueryHookOptions<GetProjectsQuery, GetProjectsQueryVariables>,
) {
return ApolloReactHooks.useLazyQuery<GetProjectsQuery, GetProjectsQueryVariables>(GetProjectsDocument, baseOptions);
}
export type GetProjectsQueryHookResult = ReturnType<typeof useGetProjectsQuery>; export type GetProjectsQueryHookResult = ReturnType<typeof useGetProjectsQuery>;
export type GetProjectsLazyQueryHookResult = ReturnType<typeof useGetProjectsLazyQuery>; export type GetProjectsLazyQueryHookResult = ReturnType<typeof useGetProjectsLazyQuery>;
export type GetProjectsQueryResult = ApolloReactCommon.QueryResult<GetProjectsQuery, GetProjectsQueryVariables>; export type GetProjectsQueryResult = ApolloReactCommon.QueryResult<GetProjectsQuery, GetProjectsQueryVariables>;
export const UpdateTaskLocationDocument = gql` export const UpdateTaskLocationDocument = gql`
mutation updateTaskLocation($taskID: String!, $taskGroupID: String!, $position: Float!) { mutation updateTaskLocation($taskID: String!, $taskGroupID: String!, $position: Float!) {
updateTaskLocation(input: { taskID: $taskID, taskGroupID: $taskGroupID, position: $position }) { updateTaskLocation(input: {taskID: $taskID, taskGroupID: $taskGroupID, position: $position}) {
taskID taskID
createdAt createdAt
name name
position position
}
} }
`; }
export type UpdateTaskLocationMutationFn = ApolloReactCommon.MutationFunction< `;
UpdateTaskLocationMutation, export type UpdateTaskLocationMutationFn = ApolloReactCommon.MutationFunction<UpdateTaskLocationMutation, UpdateTaskLocationMutationVariables>;
UpdateTaskLocationMutationVariables
>;
/** /**
* __useUpdateTaskLocationMutation__ * __useUpdateTaskLocationMutation__
@ -497,33 +582,22 @@ export type UpdateTaskLocationMutationFn = ApolloReactCommon.MutationFunction<
* }, * },
* }); * });
*/ */
export function useUpdateTaskLocationMutation( export function useUpdateTaskLocationMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskLocationMutation, UpdateTaskLocationMutationVariables>) {
baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskLocationMutation, UpdateTaskLocationMutationVariables>, return ApolloReactHooks.useMutation<UpdateTaskLocationMutation, UpdateTaskLocationMutationVariables>(UpdateTaskLocationDocument, baseOptions);
) { }
return ApolloReactHooks.useMutation<UpdateTaskLocationMutation, UpdateTaskLocationMutationVariables>(
UpdateTaskLocationDocument,
baseOptions,
);
}
export type UpdateTaskLocationMutationHookResult = ReturnType<typeof useUpdateTaskLocationMutation>; export type UpdateTaskLocationMutationHookResult = ReturnType<typeof useUpdateTaskLocationMutation>;
export type UpdateTaskLocationMutationResult = ApolloReactCommon.MutationResult<UpdateTaskLocationMutation>; export type UpdateTaskLocationMutationResult = ApolloReactCommon.MutationResult<UpdateTaskLocationMutation>;
export type UpdateTaskLocationMutationOptions = ApolloReactCommon.BaseMutationOptions< export type UpdateTaskLocationMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskLocationMutation, UpdateTaskLocationMutationVariables>;
UpdateTaskLocationMutation,
UpdateTaskLocationMutationVariables
>;
export const UpdateTaskNameDocument = gql` export const UpdateTaskNameDocument = gql`
mutation updateTaskName($taskID: String!, $name: String!) { mutation updateTaskName($taskID: String!, $name: String!) {
updateTaskName(input: { taskID: $taskID, name: $name }) { updateTaskName(input: {taskID: $taskID, name: $name}) {
taskID taskID
name name
position position
}
} }
`; }
export type UpdateTaskNameMutationFn = ApolloReactCommon.MutationFunction< `;
UpdateTaskNameMutation, export type UpdateTaskNameMutationFn = ApolloReactCommon.MutationFunction<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>;
UpdateTaskNameMutationVariables
>;
/** /**
* __useUpdateTaskNameMutation__ * __useUpdateTaskNameMutation__
@ -543,17 +617,9 @@ export type UpdateTaskNameMutationFn = ApolloReactCommon.MutationFunction<
* }, * },
* }); * });
*/ */
export function useUpdateTaskNameMutation( export function useUpdateTaskNameMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>) {
baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>, return ApolloReactHooks.useMutation<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>(UpdateTaskNameDocument, baseOptions);
) { }
return ApolloReactHooks.useMutation<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>(
UpdateTaskNameDocument,
baseOptions,
);
}
export type UpdateTaskNameMutationHookResult = ReturnType<typeof useUpdateTaskNameMutation>; export type UpdateTaskNameMutationHookResult = ReturnType<typeof useUpdateTaskNameMutation>;
export type UpdateTaskNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskNameMutation>; export type UpdateTaskNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskNameMutation>;
export type UpdateTaskNameMutationOptions = ApolloReactCommon.BaseMutationOptions< export type UpdateTaskNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>;
UpdateTaskNameMutation,
UpdateTaskNameMutationVariables
>;

View File

@ -1,7 +1,9 @@
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) { mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: { taskGroupID: $taskGroupID, name: $name, position: $position }) { createTask(input: { taskGroupID: $taskGroupID, name: $name, position: $position }) {
taskID taskID
taskGroupID taskGroup {
taskGroupID
}
name name
position position
} }

View File

@ -0,0 +1,9 @@
mutation createTaskGroup( $projectID: String!, $name: String!, $position: Float! ) {
createTaskGroup(
input: { projectID: $projectID, name: $name, position: $position }
) {
taskGroupID
name
position
}
}

View File

@ -0,0 +1,21 @@
import React from 'react';
type Props = {
size: number | string;
color: string;
};
const Ellipsis = ({ size, color }: Props) => {
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" />
</svg>
);
};
Ellipsis.defaultProps = {
size: 16,
color: '#000',
};
export default Ellipsis;

View File

@ -0,0 +1,21 @@
import React from 'react';
type Props = {
size: number | string;
color: string;
};
const Plus = ({ size, color }: Props) => {
return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 16 16">
<path d="M15.5 6h-5.5v-5.5c0-0.276-0.224-0.5-0.5-0.5h-3c-0.276 0-0.5 0.224-0.5 0.5v5.5h-5.5c-0.276 0-0.5 0.224-0.5 0.5v3c0 0.276 0.224 0.5 0.5 0.5h5.5v5.5c0 0.276 0.224 0.5 0.5 0.5h3c0.276 0 0.5-0.224 0.5-0.5v-5.5h5.5c0.276 0 0.5-0.224 0.5-0.5v-3c0-0.276-0.224-0.5-0.5-0.5z" />
</svg>
);
};
Plus.defaultProps = {
size: 16,
color: '#000',
};
export default Plus;

View File

@ -1,4 +1,5 @@
import Cross from './Cross'; import Cross from './Cross';
import Plus from './Plus';
import Bell from './Bell'; import Bell from './Bell';
import Bin from './Bin'; import Bin from './Bin';
import Pencil from './Pencil'; import Pencil from './Pencil';
@ -11,5 +12,6 @@ import Home from './Home';
import Stack from './Stack'; import Stack from './Stack';
import Question from './Question'; import Question from './Question';
import Exit from './Exit'; import Exit from './Exit';
import Ellipsis from './Ellipsis';
export { Cross, Bell, Bin, Exit, Pencil, Stack, Question, Home, Citadel, Checkmark, User, Users, Lock }; export { Cross, Plus, Bell, Ellipsis, Bin, Exit, Pencil, Stack, Question, Home, Citadel, Checkmark, User, Users, Lock };