Compare commits
	
		
			2 Commits
		
	
	
		
			refactor/c
			...
			feat/list-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					0eb9c0db94 | ||
| 
						 | 
					cdcccbfb4a | 
@@ -3,11 +3,10 @@ repos:
 | 
				
			|||||||
  hooks:
 | 
					  hooks:
 | 
				
			||||||
    - id: eslint
 | 
					    - id: eslint
 | 
				
			||||||
      name: eslint
 | 
					      name: eslint
 | 
				
			||||||
      entry: go run cmd/mage/main.go frontend:lint
 | 
					      entry: scripts/lint.sh
 | 
				
			||||||
      language: system
 | 
					      language: system
 | 
				
			||||||
      files: \.[jt]sx?$  # *.js, *.jsx, *.ts and *.tsx
 | 
					      files: \.[jt]sx?$  # *.js, *.jsx, *.ts and *.tsx
 | 
				
			||||||
      types: [file]
 | 
					      types: [file]
 | 
				
			||||||
      pass_filenames: false
 | 
					 | 
				
			||||||
- hooks:
 | 
					- hooks:
 | 
				
			||||||
  - id: check-yaml
 | 
					  - id: check-yaml
 | 
				
			||||||
  - id: end-of-file-fixer
 | 
					  - id: end-of-file-fixer
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import React, { useState } from 'react';
 | 
					import React, { useState } from 'react';
 | 
				
			||||||
import styled from 'styled-components';
 | 
					import styled from 'styled-components';
 | 
				
			||||||
import { TaskSorting, TaskSortingType, TaskSortingDirection } from 'shared/components/Lists';
 | 
					import { TaskSorting, TaskSortingType, TaskSortingDirection } from 'shared/utils/sorting';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ActionsList = styled.ul`
 | 
					export const ActionsList = styled.ul`
 | 
				
			||||||
  margin: 0;
 | 
					  margin: 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import {
 | 
				
			|||||||
  useSetTaskCompleteMutation,
 | 
					  useSetTaskCompleteMutation,
 | 
				
			||||||
  useToggleTaskLabelMutation,
 | 
					  useToggleTaskLabelMutation,
 | 
				
			||||||
  useFindProjectQuery,
 | 
					  useFindProjectQuery,
 | 
				
			||||||
 | 
					  useSortTaskGroupMutation,
 | 
				
			||||||
  useUpdateTaskGroupNameMutation,
 | 
					  useUpdateTaskGroupNameMutation,
 | 
				
			||||||
  useUpdateTaskNameMutation,
 | 
					  useUpdateTaskNameMutation,
 | 
				
			||||||
  useCreateTaskMutation,
 | 
					  useCreateTaskMutation,
 | 
				
			||||||
@@ -21,6 +22,10 @@ import {
 | 
				
			|||||||
  useUnassignTaskMutation,
 | 
					  useUnassignTaskMutation,
 | 
				
			||||||
  useUpdateTaskDueDateMutation,
 | 
					  useUpdateTaskDueDateMutation,
 | 
				
			||||||
  FindProjectQuery,
 | 
					  FindProjectQuery,
 | 
				
			||||||
 | 
					  useDuplicateTaskGroupMutation,
 | 
				
			||||||
 | 
					  DuplicateTaskGroupMutation,
 | 
				
			||||||
 | 
					  DuplicateTaskGroupDocument,
 | 
				
			||||||
 | 
					  useDeleteTaskGroupTasksMutation,
 | 
				
			||||||
} from 'shared/generated/graphql';
 | 
					} from 'shared/generated/graphql';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import QuickCardEditor from 'shared/components/QuickCardEditor';
 | 
					import QuickCardEditor from 'shared/components/QuickCardEditor';
 | 
				
			||||||
@@ -33,10 +38,8 @@ import SimpleLists, {
 | 
				
			|||||||
  TaskMeta,
 | 
					  TaskMeta,
 | 
				
			||||||
  TaskMetaMatch,
 | 
					  TaskMetaMatch,
 | 
				
			||||||
  TaskMetaFilters,
 | 
					  TaskMetaFilters,
 | 
				
			||||||
  TaskSorting,
 | 
					 | 
				
			||||||
  TaskSortingType,
 | 
					 | 
				
			||||||
  TaskSortingDirection,
 | 
					 | 
				
			||||||
} from 'shared/components/Lists';
 | 
					} from 'shared/components/Lists';
 | 
				
			||||||
 | 
					import { TaskSorting, TaskSortingType, TaskSortingDirection, sortTasks } from 'shared/utils/sorting';
 | 
				
			||||||
import produce from 'immer';
 | 
					import produce from 'immer';
 | 
				
			||||||
import MiniProfile from 'shared/components/MiniProfile';
 | 
					import MiniProfile from 'shared/components/MiniProfile';
 | 
				
			||||||
import DueDateManager from 'shared/components/DueDateManager';
 | 
					import DueDateManager from 'shared/components/DueDateManager';
 | 
				
			||||||
@@ -44,6 +47,7 @@ import EmptyBoard from 'shared/components/EmptyBoard';
 | 
				
			|||||||
import NOOP from 'shared/utils/noop';
 | 
					import NOOP from 'shared/utils/noop';
 | 
				
			||||||
import LabelManagerEditor from 'Projects/Project/LabelManagerEditor';
 | 
					import LabelManagerEditor from 'Projects/Project/LabelManagerEditor';
 | 
				
			||||||
import Chip from 'shared/components/Chip';
 | 
					import Chip from 'shared/components/Chip';
 | 
				
			||||||
 | 
					import { toast } from 'react-toastify';
 | 
				
			||||||
import { useCurrentUser } from 'App/context';
 | 
					import { useCurrentUser } from 'App/context';
 | 
				
			||||||
import FilterStatus from './FilterStatus';
 | 
					import FilterStatus from './FilterStatus';
 | 
				
			||||||
import FilterMeta from './FilterMeta';
 | 
					import FilterMeta from './FilterMeta';
 | 
				
			||||||
@@ -263,6 +267,11 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
				
			|||||||
  const [taskMetaFilters, setTaskMetaFilters] = useState(initTaskMetaFilters);
 | 
					  const [taskMetaFilters, setTaskMetaFilters] = useState(initTaskMetaFilters);
 | 
				
			||||||
  const [taskSorting, setTaskSorting] = useState(initTaskSorting);
 | 
					  const [taskSorting, setTaskSorting] = useState(initTaskSorting);
 | 
				
			||||||
  const history = useHistory();
 | 
					  const history = useHistory();
 | 
				
			||||||
 | 
					  const [sortTaskGroup] = useSortTaskGroupMutation({
 | 
				
			||||||
 | 
					    onCompleted: () => {
 | 
				
			||||||
 | 
					      toast('List was sorted');
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
  const [deleteTaskGroup] = useDeleteTaskGroupMutation({
 | 
					  const [deleteTaskGroup] = useDeleteTaskGroupMutation({
 | 
				
			||||||
    update: (client, deletedTaskGroupData) => {
 | 
					    update: (client, deletedTaskGroupData) => {
 | 
				
			||||||
      updateApolloCache<FindProjectQuery>(
 | 
					      updateApolloCache<FindProjectQuery>(
 | 
				
			||||||
@@ -315,6 +324,36 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
				
			|||||||
  const { loading, data } = useFindProjectQuery({
 | 
					  const { loading, data } = useFindProjectQuery({
 | 
				
			||||||
    variables: { projectID },
 | 
					    variables: { projectID },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					  const [deleteTaskGroupTasks] = useDeleteTaskGroupTasksMutation({
 | 
				
			||||||
 | 
					    update: (client, resp) =>
 | 
				
			||||||
 | 
					      updateApolloCache<FindProjectQuery>(
 | 
				
			||||||
 | 
					        client,
 | 
				
			||||||
 | 
					        FindProjectDocument,
 | 
				
			||||||
 | 
					        cache =>
 | 
				
			||||||
 | 
					          produce(cache, draftCache => {
 | 
				
			||||||
 | 
					            const idx = cache.findProject.taskGroups.findIndex(
 | 
				
			||||||
 | 
					              t => t.id === resp.data.deleteTaskGroupTasks.taskGroupID,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            if (idx !== -1) {
 | 
				
			||||||
 | 
					              draftCache.findProject.taskGroups[idx].tasks = [];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					        { projectID },
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const [duplicateTaskGroup] = useDuplicateTaskGroupMutation({
 | 
				
			||||||
 | 
					    update: (client, resp) => {
 | 
				
			||||||
 | 
					      updateApolloCache<FindProjectQuery>(
 | 
				
			||||||
 | 
					        client,
 | 
				
			||||||
 | 
					        FindProjectDocument,
 | 
				
			||||||
 | 
					        cache =>
 | 
				
			||||||
 | 
					          produce(cache, draftCache => {
 | 
				
			||||||
 | 
					            draftCache.findProject.taskGroups.push(resp.data.duplicateTaskGroup.taskGroup);
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					        { projectID },
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [updateTaskDueDate] = useUpdateTaskDueDateMutation();
 | 
					  const [updateTaskDueDate] = useUpdateTaskDueDateMutation();
 | 
				
			||||||
  const [setTaskComplete] = useSetTaskCompleteMutation();
 | 
					  const [setTaskComplete] = useSetTaskCompleteMutation();
 | 
				
			||||||
@@ -624,15 +663,44 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
				
			|||||||
          onExtraMenuOpen={(taskGroupID: string, $targetRef: any) => {
 | 
					          onExtraMenuOpen={(taskGroupID: string, $targetRef: any) => {
 | 
				
			||||||
            showPopup(
 | 
					            showPopup(
 | 
				
			||||||
              $targetRef,
 | 
					              $targetRef,
 | 
				
			||||||
              <Popup title="List actions" tab={0} onClose={() => hidePopup()}>
 | 
					              <ListActions
 | 
				
			||||||
                <ListActions
 | 
					                taskGroupID={taskGroupID}
 | 
				
			||||||
                  taskGroupID={taskGroupID}
 | 
					                onDeleteTaskGroupTasks={() => {
 | 
				
			||||||
                  onArchiveTaskGroup={tgID => {
 | 
					                  deleteTaskGroupTasks({ variables: { taskGroupID } });
 | 
				
			||||||
                    deleteTaskGroup({ variables: { taskGroupID: tgID } });
 | 
					                  hidePopup();
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                onSortTaskGroup={taskSort => {
 | 
				
			||||||
 | 
					                  const taskGroup = data.findProject.taskGroups.find(t => t.id === taskGroupID);
 | 
				
			||||||
 | 
					                  if (taskGroup) {
 | 
				
			||||||
 | 
					                    const tasks: Array<{ taskID: string; position: number }> = taskGroup.tasks
 | 
				
			||||||
 | 
					                      .sort((a, b) => sortTasks(a, b, taskSort))
 | 
				
			||||||
 | 
					                      .reduce((prevTasks: Array<{ taskID: string; position: number }>, t, idx) => {
 | 
				
			||||||
 | 
					                        prevTasks.push({ taskID: t.id, position: (idx + 1) * 2048 });
 | 
				
			||||||
 | 
					                        return tasks;
 | 
				
			||||||
 | 
					                      }, []);
 | 
				
			||||||
 | 
					                    sortTaskGroup({ variables: { taskGroupID, tasks } });
 | 
				
			||||||
                    hidePopup();
 | 
					                    hidePopup();
 | 
				
			||||||
                  }}
 | 
					                  }
 | 
				
			||||||
                />
 | 
					                }}
 | 
				
			||||||
              </Popup>,
 | 
					                onDuplicateTaskGroup={newName => {
 | 
				
			||||||
 | 
					                  const idx = data.findProject.taskGroups.findIndex(t => t.id === taskGroupID);
 | 
				
			||||||
 | 
					                  if (idx !== -1) {
 | 
				
			||||||
 | 
					                    const taskGroups = data.findProject.taskGroups.sort((a, b) => a.position - b.position);
 | 
				
			||||||
 | 
					                    const prevPos = taskGroups[idx].position;
 | 
				
			||||||
 | 
					                    const next = taskGroups[idx + 1];
 | 
				
			||||||
 | 
					                    let newPos = prevPos * 2;
 | 
				
			||||||
 | 
					                    if (next) {
 | 
				
			||||||
 | 
					                      newPos = (prevPos + next.position) / 2.0;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    duplicateTaskGroup({ variables: { projectID, taskGroupID, name: newName, position: newPos } });
 | 
				
			||||||
 | 
					                    hidePopup();
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                onArchiveTaskGroup={tgID => {
 | 
				
			||||||
 | 
					                  deleteTaskGroup({ variables: { taskGroupID: tgID } });
 | 
				
			||||||
 | 
					                  hidePopup();
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					              />,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,11 +16,12 @@ import {
 | 
				
			|||||||
} from './Styles';
 | 
					} from './Styles';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type NameEditorProps = {
 | 
					type NameEditorProps = {
 | 
				
			||||||
 | 
					  buttonLabel?: string;
 | 
				
			||||||
  onSave: (listName: string) => void;
 | 
					  onSave: (listName: string) => void;
 | 
				
			||||||
  onCancel: () => void;
 | 
					  onCancel: () => void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const NameEditor: React.FC<NameEditorProps> = ({ onSave, onCancel }) => {
 | 
					export const NameEditor: React.FC<NameEditorProps> = ({ onSave: handleSave, onCancel, buttonLabel = 'Save' }) => {
 | 
				
			||||||
  const $editorRef = useRef<HTMLTextAreaElement>(null);
 | 
					  const $editorRef = useRef<HTMLTextAreaElement>(null);
 | 
				
			||||||
  const [listName, setListName] = useState('');
 | 
					  const [listName, setListName] = useState('');
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
@@ -28,6 +29,11 @@ const NameEditor: React.FC<NameEditorProps> = ({ onSave, onCancel }) => {
 | 
				
			|||||||
      $editorRef.current.focus();
 | 
					      $editorRef.current.focus();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					  const onSave = (newName: string) => {
 | 
				
			||||||
 | 
					    if (newName.replace(/\s+/g, '') !== '') {
 | 
				
			||||||
 | 
					      handleSave(newName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
  const onKeyDown = (e: React.KeyboardEvent) => {
 | 
					  const onKeyDown = (e: React.KeyboardEvent) => {
 | 
				
			||||||
    if (e.key === 'Enter') {
 | 
					    if (e.key === 'Enter') {
 | 
				
			||||||
      e.preventDefault();
 | 
					      e.preventDefault();
 | 
				
			||||||
@@ -60,7 +66,7 @@ const NameEditor: React.FC<NameEditorProps> = ({ onSave, onCancel }) => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          Save
 | 
					          {buttonLabel}
 | 
				
			||||||
        </AddListButton>
 | 
					        </AddListButton>
 | 
				
			||||||
        <CancelAdd onClick={() => onCancel()}>
 | 
					        <CancelAdd onClick={() => onCancel()}>
 | 
				
			||||||
          <Cross width={16} height={16} />
 | 
					          <Cross width={16} height={16} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
import React from 'react';
 | 
					 | 
				
			||||||
import { action } from '@storybook/addon-actions';
 | 
					 | 
				
			||||||
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" onArchiveTaskGroup={action('on archive task group')} />;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,50 +1,100 @@
 | 
				
			|||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import { usePopup, Popup } from 'shared/components/PopupMenu';
 | 
				
			||||||
 | 
					import { NameEditor } from 'shared/components/AddList';
 | 
				
			||||||
 | 
					import NOOP from 'shared/utils/noop';
 | 
				
			||||||
 | 
					import styled from 'styled-components';
 | 
				
			||||||
 | 
					import { TaskSorting, TaskSortingDirection, TaskSortingType } from 'shared/utils/sorting';
 | 
				
			||||||
import { InnerContent, ListActionsWrapper, ListActionItemWrapper, ListActionItem, ListSeparator } from './Styles';
 | 
					import { InnerContent, ListActionsWrapper, ListActionItemWrapper, ListActionItem, ListSeparator } from './Styles';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CopyWrapper = styled.div`
 | 
				
			||||||
 | 
					  margin: 0 12px;
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
  taskGroupID: string;
 | 
					  taskGroupID: string;
 | 
				
			||||||
 | 
					  onDuplicateTaskGroup: (newTaskGroupName: string) => void;
 | 
				
			||||||
 | 
					  onDeleteTaskGroupTasks: () => void;
 | 
				
			||||||
  onArchiveTaskGroup: (taskGroupID: string) => void;
 | 
					  onArchiveTaskGroup: (taskGroupID: string) => void;
 | 
				
			||||||
 | 
					  onSortTaskGroup: (taskSorting: TaskSorting) => void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
const LabelManager: React.FC<Props> = ({ taskGroupID, onArchiveTaskGroup }) => {
 | 
					
 | 
				
			||||||
 | 
					const LabelManager: React.FC<Props> = ({
 | 
				
			||||||
 | 
					  taskGroupID,
 | 
				
			||||||
 | 
					  onDeleteTaskGroupTasks,
 | 
				
			||||||
 | 
					  onDuplicateTaskGroup,
 | 
				
			||||||
 | 
					  onArchiveTaskGroup,
 | 
				
			||||||
 | 
					  onSortTaskGroup,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const { setTab } = usePopup();
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <InnerContent>
 | 
					    <>
 | 
				
			||||||
      <ListActionsWrapper>
 | 
					      <Popup tab={0} title={null}>
 | 
				
			||||||
        <ListActionItemWrapper>
 | 
					        <InnerContent>
 | 
				
			||||||
          <ListActionItem>Add card...</ListActionItem>
 | 
					          <ListActionsWrapper>
 | 
				
			||||||
        </ListActionItemWrapper>
 | 
					            <ListActionItemWrapper onClick={() => setTab(1)}>
 | 
				
			||||||
        <ListActionItemWrapper>
 | 
					              <ListActionItem>Duplicate</ListActionItem>
 | 
				
			||||||
          <ListActionItem>Copy List...</ListActionItem>
 | 
					            </ListActionItemWrapper>
 | 
				
			||||||
        </ListActionItemWrapper>
 | 
					            <ListActionItemWrapper onClick={() => setTab(2)}>
 | 
				
			||||||
        <ListActionItemWrapper>
 | 
					              <ListActionItem>Sort</ListActionItem>
 | 
				
			||||||
          <ListActionItem>Move card...</ListActionItem>
 | 
					            </ListActionItemWrapper>
 | 
				
			||||||
        </ListActionItemWrapper>
 | 
					          </ListActionsWrapper>
 | 
				
			||||||
        <ListActionItemWrapper>
 | 
					          <ListSeparator />
 | 
				
			||||||
          <ListActionItem>Watch</ListActionItem>
 | 
					          <ListActionsWrapper>
 | 
				
			||||||
        </ListActionItemWrapper>
 | 
					            <ListActionItemWrapper onClick={() => onDeleteTaskGroupTasks()}>
 | 
				
			||||||
      </ListActionsWrapper>
 | 
					              <ListActionItem>Delete All Tasks</ListActionItem>
 | 
				
			||||||
      <ListSeparator />
 | 
					            </ListActionItemWrapper>
 | 
				
			||||||
      <ListActionsWrapper>
 | 
					          </ListActionsWrapper>
 | 
				
			||||||
        <ListActionItemWrapper>
 | 
					          <ListSeparator />
 | 
				
			||||||
          <ListActionItem>Sort By...</ListActionItem>
 | 
					          <ListActionsWrapper>
 | 
				
			||||||
        </ListActionItemWrapper>
 | 
					            <ListActionItemWrapper onClick={() => onArchiveTaskGroup(taskGroupID)}>
 | 
				
			||||||
      </ListActionsWrapper>
 | 
					              <ListActionItem>Delete</ListActionItem>
 | 
				
			||||||
      <ListSeparator />
 | 
					            </ListActionItemWrapper>
 | 
				
			||||||
      <ListActionsWrapper>
 | 
					          </ListActionsWrapper>
 | 
				
			||||||
        <ListActionItemWrapper>
 | 
					        </InnerContent>
 | 
				
			||||||
          <ListActionItem>Move All Cards in This List...</ListActionItem>
 | 
					      </Popup>
 | 
				
			||||||
        </ListActionItemWrapper>
 | 
					      <Popup tab={1} title="Copy list" onClose={NOOP}>
 | 
				
			||||||
        <ListActionItemWrapper>
 | 
					        <CopyWrapper>
 | 
				
			||||||
          <ListActionItem>Archive All Cards in This List...</ListActionItem>
 | 
					          <NameEditor
 | 
				
			||||||
        </ListActionItemWrapper>
 | 
					            onCancel={NOOP}
 | 
				
			||||||
      </ListActionsWrapper>
 | 
					            onSave={listName => {
 | 
				
			||||||
      <ListSeparator />
 | 
					              onDuplicateTaskGroup(listName);
 | 
				
			||||||
      <ListActionsWrapper>
 | 
					            }}
 | 
				
			||||||
        <ListActionItemWrapper onClick={() => onArchiveTaskGroup(taskGroupID)}>
 | 
					            buttonLabel="Duplicate"
 | 
				
			||||||
          <ListActionItem>Archive This List</ListActionItem>
 | 
					          />
 | 
				
			||||||
        </ListActionItemWrapper>
 | 
					        </CopyWrapper>
 | 
				
			||||||
      </ListActionsWrapper>
 | 
					      </Popup>
 | 
				
			||||||
    </InnerContent>
 | 
					      <Popup tab={2} title="Sort list" onClose={NOOP}>
 | 
				
			||||||
 | 
					        <InnerContent>
 | 
				
			||||||
 | 
					          <ListActionsWrapper>
 | 
				
			||||||
 | 
					            <ListActionItemWrapper
 | 
				
			||||||
 | 
					              onClick={() => onSortTaskGroup({ type: TaskSortingType.TASK_TITLE, direction: TaskSortingDirection.ASC })}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <ListActionItem>Task title</ListActionItem>
 | 
				
			||||||
 | 
					            </ListActionItemWrapper>
 | 
				
			||||||
 | 
					            <ListActionItemWrapper
 | 
				
			||||||
 | 
					              onClick={() => onSortTaskGroup({ type: TaskSortingType.TASK_TITLE, direction: TaskSortingDirection.ASC })}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <ListActionItem>Due date</ListActionItem>
 | 
				
			||||||
 | 
					            </ListActionItemWrapper>
 | 
				
			||||||
 | 
					            <ListActionItemWrapper
 | 
				
			||||||
 | 
					              onClick={() => onSortTaskGroup({ type: TaskSortingType.COMPLETE, direction: TaskSortingDirection.ASC })}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <ListActionItem>Complete</ListActionItem>
 | 
				
			||||||
 | 
					            </ListActionItemWrapper>
 | 
				
			||||||
 | 
					            <ListActionItemWrapper
 | 
				
			||||||
 | 
					              onClick={() => onSortTaskGroup({ type: TaskSortingType.LABELS, direction: TaskSortingDirection.ASC })}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <ListActionItem>Labels</ListActionItem>
 | 
				
			||||||
 | 
					            </ListActionItemWrapper>
 | 
				
			||||||
 | 
					            <ListActionItemWrapper
 | 
				
			||||||
 | 
					              onClick={() => onSortTaskGroup({ type: TaskSortingType.MEMBERS, direction: TaskSortingDirection.ASC })}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <ListActionItem>Members</ListActionItem>
 | 
				
			||||||
 | 
					            </ListActionItemWrapper>
 | 
				
			||||||
 | 
					          </ListActionsWrapper>
 | 
				
			||||||
 | 
					        </InnerContent>
 | 
				
			||||||
 | 
					      </Popup>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
export default LabelManager;
 | 
					export default LabelManager;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ import {
 | 
				
			|||||||
  getAfterDropDraggableList,
 | 
					  getAfterDropDraggableList,
 | 
				
			||||||
} from 'shared/utils/draggables';
 | 
					} from 'shared/utils/draggables';
 | 
				
			||||||
import moment from 'moment';
 | 
					import moment from 'moment';
 | 
				
			||||||
 | 
					import { TaskSorting, TaskSortingType, TaskSortingDirection, sortTasks } from 'shared/utils/sorting';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Container, BoardContainer, BoardWrapper } from './Styles';
 | 
					import { Container, BoardContainer, BoardWrapper } from './Styles';
 | 
				
			||||||
import shouldMetaFilter from './metaFilter';
 | 
					import shouldMetaFilter from './metaFilter';
 | 
				
			||||||
@@ -94,127 +95,6 @@ export type TaskMetaFilters = {
 | 
				
			|||||||
  labels: Array<LabelMetaFilter>;
 | 
					  labels: Array<LabelMetaFilter>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum TaskSortingType {
 | 
					 | 
				
			||||||
  NONE,
 | 
					 | 
				
			||||||
  DUE_DATE,
 | 
					 | 
				
			||||||
  MEMBERS,
 | 
					 | 
				
			||||||
  LABELS,
 | 
					 | 
				
			||||||
  TASK_TITLE,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export enum TaskSortingDirection {
 | 
					 | 
				
			||||||
  ASC,
 | 
					 | 
				
			||||||
  DESC,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type TaskSorting = {
 | 
					 | 
				
			||||||
  type: TaskSortingType;
 | 
					 | 
				
			||||||
  direction: TaskSortingDirection;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function sortString(a: string, b: string) {
 | 
					 | 
				
			||||||
  if (a < b) {
 | 
					 | 
				
			||||||
    return -1;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (a > b) {
 | 
					 | 
				
			||||||
    return 1;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function sortTasks(a: Task, b: Task, taskSorting: TaskSorting) {
 | 
					 | 
				
			||||||
  if (taskSorting.type === TaskSortingType.TASK_TITLE) {
 | 
					 | 
				
			||||||
    if (a.name < b.name) {
 | 
					 | 
				
			||||||
      return -1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (a.name > b.name) {
 | 
					 | 
				
			||||||
      return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (taskSorting.type === TaskSortingType.DUE_DATE) {
 | 
					 | 
				
			||||||
    if (a.dueDate && !b.dueDate) {
 | 
					 | 
				
			||||||
      return -1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (b.dueDate && !a.dueDate) {
 | 
					 | 
				
			||||||
      return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return moment(a.dueDate).diff(moment(b.dueDate));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (taskSorting.type === TaskSortingType.LABELS) {
 | 
					 | 
				
			||||||
    // sorts non-empty labels by name, then by empty label color name
 | 
					 | 
				
			||||||
    let aLabels = [];
 | 
					 | 
				
			||||||
    let bLabels = [];
 | 
					 | 
				
			||||||
    let aLabelsEmpty = [];
 | 
					 | 
				
			||||||
    let bLabelsEmpty = [];
 | 
					 | 
				
			||||||
    if (a.labels) {
 | 
					 | 
				
			||||||
      for (const aLabel of a.labels) {
 | 
					 | 
				
			||||||
        if (aLabel.projectLabel.name && aLabel.projectLabel.name !== '') {
 | 
					 | 
				
			||||||
          aLabels.push(aLabel.projectLabel.name);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          aLabelsEmpty.push(aLabel.projectLabel.labelColor.name);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (b.labels) {
 | 
					 | 
				
			||||||
      for (const bLabel of b.labels) {
 | 
					 | 
				
			||||||
        if (bLabel.projectLabel.name && bLabel.projectLabel.name !== '') {
 | 
					 | 
				
			||||||
          bLabels.push(bLabel.projectLabel.name);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          bLabelsEmpty.push(bLabel.projectLabel.labelColor.name);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    aLabels = aLabels.sort((aLabel, bLabel) => sortString(aLabel, bLabel));
 | 
					 | 
				
			||||||
    bLabels = bLabels.sort((aLabel, bLabel) => sortString(aLabel, bLabel));
 | 
					 | 
				
			||||||
    aLabelsEmpty = aLabelsEmpty.sort((aLabel, bLabel) => sortString(aLabel, bLabel));
 | 
					 | 
				
			||||||
    bLabelsEmpty = bLabelsEmpty.sort((aLabel, bLabel) => sortString(aLabel, bLabel));
 | 
					 | 
				
			||||||
    if (aLabelsEmpty.length !== 0 || bLabelsEmpty.length !== 0) {
 | 
					 | 
				
			||||||
      if (aLabelsEmpty.length > bLabelsEmpty.length) {
 | 
					 | 
				
			||||||
        if (bLabels.length !== 0) {
 | 
					 | 
				
			||||||
          return 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return -1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (aLabels.length < bLabels.length) {
 | 
					 | 
				
			||||||
      return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (aLabels.length > bLabels.length) {
 | 
					 | 
				
			||||||
      return -1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (taskSorting.type === TaskSortingType.MEMBERS) {
 | 
					 | 
				
			||||||
    let aMembers = [];
 | 
					 | 
				
			||||||
    let bMembers = [];
 | 
					 | 
				
			||||||
    if (a.assigned) {
 | 
					 | 
				
			||||||
      for (const aMember of a.assigned) {
 | 
					 | 
				
			||||||
        if (aMember.fullName) {
 | 
					 | 
				
			||||||
          aMembers.push(aMember.fullName);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (b.assigned) {
 | 
					 | 
				
			||||||
      for (const bMember of b.assigned) {
 | 
					 | 
				
			||||||
        if (bMember.fullName) {
 | 
					 | 
				
			||||||
          bMembers.push(bMember.fullName);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    aMembers = aMembers.sort((aMember, bMember) => sortString(aMember, bMember));
 | 
					 | 
				
			||||||
    bMembers = bMembers.sort((aMember, bMember) => sortString(aMember, bMember));
 | 
					 | 
				
			||||||
    if (aMembers.length < bMembers.length) {
 | 
					 | 
				
			||||||
      return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (aMembers.length > bMembers.length) {
 | 
					 | 
				
			||||||
      return -1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function shouldStatusFilter(task: Task, filter: TaskStatusFilter) {
 | 
					function shouldStatusFilter(task: Task, filter: TaskStatusFilter) {
 | 
				
			||||||
  if (filter.status === TaskStatus.ALL) {
 | 
					  if (filter.status === TaskStatus.ALL) {
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,13 +275,16 @@ export type Mutation = {
 | 
				
			|||||||
  deleteTaskChecklist: DeleteTaskChecklistPayload;
 | 
					  deleteTaskChecklist: DeleteTaskChecklistPayload;
 | 
				
			||||||
  deleteTaskChecklistItem: DeleteTaskChecklistItemPayload;
 | 
					  deleteTaskChecklistItem: DeleteTaskChecklistItemPayload;
 | 
				
			||||||
  deleteTaskGroup: DeleteTaskGroupPayload;
 | 
					  deleteTaskGroup: DeleteTaskGroupPayload;
 | 
				
			||||||
 | 
					  deleteTaskGroupTasks: DeleteTaskGroupTasksPayload;
 | 
				
			||||||
  deleteTeam: DeleteTeamPayload;
 | 
					  deleteTeam: DeleteTeamPayload;
 | 
				
			||||||
  deleteTeamMember: DeleteTeamMemberPayload;
 | 
					  deleteTeamMember: DeleteTeamMemberPayload;
 | 
				
			||||||
  deleteUserAccount: DeleteUserAccountPayload;
 | 
					  deleteUserAccount: DeleteUserAccountPayload;
 | 
				
			||||||
 | 
					  duplicateTaskGroup: DuplicateTaskGroupPayload;
 | 
				
			||||||
  logoutUser: Scalars['Boolean'];
 | 
					  logoutUser: Scalars['Boolean'];
 | 
				
			||||||
  removeTaskLabel: Task;
 | 
					  removeTaskLabel: Task;
 | 
				
			||||||
  setTaskChecklistItemComplete: TaskChecklistItem;
 | 
					  setTaskChecklistItemComplete: TaskChecklistItem;
 | 
				
			||||||
  setTaskComplete: Task;
 | 
					  setTaskComplete: Task;
 | 
				
			||||||
 | 
					  sortTaskGroup: SortTaskGroupPayload;
 | 
				
			||||||
  toggleTaskLabel: ToggleTaskLabelPayload;
 | 
					  toggleTaskLabel: ToggleTaskLabelPayload;
 | 
				
			||||||
  unassignTask: Task;
 | 
					  unassignTask: Task;
 | 
				
			||||||
  updateProjectLabel: ProjectLabel;
 | 
					  updateProjectLabel: ProjectLabel;
 | 
				
			||||||
@@ -405,6 +408,11 @@ export type MutationDeleteTaskGroupArgs = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MutationDeleteTaskGroupTasksArgs = {
 | 
				
			||||||
 | 
					  input: DeleteTaskGroupTasks;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type MutationDeleteTeamArgs = {
 | 
					export type MutationDeleteTeamArgs = {
 | 
				
			||||||
  input: DeleteTeam;
 | 
					  input: DeleteTeam;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -420,6 +428,11 @@ export type MutationDeleteUserAccountArgs = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MutationDuplicateTaskGroupArgs = {
 | 
				
			||||||
 | 
					  input: DuplicateTaskGroup;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type MutationLogoutUserArgs = {
 | 
					export type MutationLogoutUserArgs = {
 | 
				
			||||||
  input: LogoutUser;
 | 
					  input: LogoutUser;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -440,6 +453,11 @@ export type MutationSetTaskCompleteArgs = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MutationSortTaskGroupArgs = {
 | 
				
			||||||
 | 
					  input: SortTaskGroup;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type MutationToggleTaskLabelArgs = {
 | 
					export type MutationToggleTaskLabelArgs = {
 | 
				
			||||||
  input: ToggleTaskLabelInput;
 | 
					  input: ToggleTaskLabelInput;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -823,6 +841,44 @@ export type DeleteTaskChecklistPayload = {
 | 
				
			|||||||
  taskChecklist: TaskChecklist;
 | 
					  taskChecklist: TaskChecklist;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DeleteTaskGroupTasks = {
 | 
				
			||||||
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DeleteTaskGroupTasksPayload = {
 | 
				
			||||||
 | 
					   __typename?: 'DeleteTaskGroupTasksPayload';
 | 
				
			||||||
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
 | 
					  tasks: Array<Scalars['UUID']>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TaskPositionUpdate = {
 | 
				
			||||||
 | 
					  taskID: Scalars['UUID'];
 | 
				
			||||||
 | 
					  position: Scalars['Float'];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SortTaskGroupPayload = {
 | 
				
			||||||
 | 
					   __typename?: 'SortTaskGroupPayload';
 | 
				
			||||||
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
 | 
					  tasks: Array<Task>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SortTaskGroup = {
 | 
				
			||||||
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
 | 
					  tasks: Array<TaskPositionUpdate>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DuplicateTaskGroup = {
 | 
				
			||||||
 | 
					  projectID: Scalars['UUID'];
 | 
				
			||||||
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
 | 
					  name: Scalars['String'];
 | 
				
			||||||
 | 
					  position: Scalars['Float'];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DuplicateTaskGroupPayload = {
 | 
				
			||||||
 | 
					   __typename?: 'DuplicateTaskGroupPayload';
 | 
				
			||||||
 | 
					  taskGroup: TaskGroup;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type NewTaskGroupLocation = {
 | 
					export type NewTaskGroupLocation = {
 | 
				
			||||||
  taskGroupID: Scalars['UUID'];
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
  position: Scalars['Float'];
 | 
					  position: Scalars['Float'];
 | 
				
			||||||
@@ -1575,6 +1631,60 @@ export type UpdateTaskChecklistNameMutation = (
 | 
				
			|||||||
  ) }
 | 
					  ) }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DeleteTaskGroupTasksMutationVariables = {
 | 
				
			||||||
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DeleteTaskGroupTasksMutation = (
 | 
				
			||||||
 | 
					  { __typename?: 'Mutation' }
 | 
				
			||||||
 | 
					  & { deleteTaskGroupTasks: (
 | 
				
			||||||
 | 
					    { __typename?: 'DeleteTaskGroupTasksPayload' }
 | 
				
			||||||
 | 
					    & Pick<DeleteTaskGroupTasksPayload, 'tasks' | 'taskGroupID'>
 | 
				
			||||||
 | 
					  ) }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DuplicateTaskGroupMutationVariables = {
 | 
				
			||||||
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
 | 
					  name: Scalars['String'];
 | 
				
			||||||
 | 
					  position: Scalars['Float'];
 | 
				
			||||||
 | 
					  projectID: Scalars['UUID'];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DuplicateTaskGroupMutation = (
 | 
				
			||||||
 | 
					  { __typename?: 'Mutation' }
 | 
				
			||||||
 | 
					  & { duplicateTaskGroup: (
 | 
				
			||||||
 | 
					    { __typename?: 'DuplicateTaskGroupPayload' }
 | 
				
			||||||
 | 
					    & { taskGroup: (
 | 
				
			||||||
 | 
					      { __typename?: 'TaskGroup' }
 | 
				
			||||||
 | 
					      & Pick<TaskGroup, 'id' | 'name' | 'position'>
 | 
				
			||||||
 | 
					      & { tasks: Array<(
 | 
				
			||||||
 | 
					        { __typename?: 'Task' }
 | 
				
			||||||
 | 
					        & TaskFieldsFragment
 | 
				
			||||||
 | 
					      )> }
 | 
				
			||||||
 | 
					    ) }
 | 
				
			||||||
 | 
					  ) }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SortTaskGroupMutationVariables = {
 | 
				
			||||||
 | 
					  tasks: Array<TaskPositionUpdate>;
 | 
				
			||||||
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SortTaskGroupMutation = (
 | 
				
			||||||
 | 
					  { __typename?: 'Mutation' }
 | 
				
			||||||
 | 
					  & { sortTaskGroup: (
 | 
				
			||||||
 | 
					    { __typename?: 'SortTaskGroupPayload' }
 | 
				
			||||||
 | 
					    & Pick<SortTaskGroupPayload, 'taskGroupID'>
 | 
				
			||||||
 | 
					    & { tasks: Array<(
 | 
				
			||||||
 | 
					      { __typename?: 'Task' }
 | 
				
			||||||
 | 
					      & Pick<Task, 'id' | 'position'>
 | 
				
			||||||
 | 
					    )> }
 | 
				
			||||||
 | 
					  ) }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type UpdateTaskGroupNameMutationVariables = {
 | 
					export type UpdateTaskGroupNameMutationVariables = {
 | 
				
			||||||
  taskGroupID: Scalars['UUID'];
 | 
					  taskGroupID: Scalars['UUID'];
 | 
				
			||||||
  name: Scalars['String'];
 | 
					  name: Scalars['String'];
 | 
				
			||||||
@@ -3292,6 +3402,118 @@ export function useUpdateTaskChecklistNameMutation(baseOptions?: ApolloReactHook
 | 
				
			|||||||
export type UpdateTaskChecklistNameMutationHookResult = ReturnType<typeof useUpdateTaskChecklistNameMutation>;
 | 
					export type UpdateTaskChecklistNameMutationHookResult = ReturnType<typeof useUpdateTaskChecklistNameMutation>;
 | 
				
			||||||
export type UpdateTaskChecklistNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskChecklistNameMutation>;
 | 
					export type UpdateTaskChecklistNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskChecklistNameMutation>;
 | 
				
			||||||
export type UpdateTaskChecklistNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>;
 | 
					export type UpdateTaskChecklistNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>;
 | 
				
			||||||
 | 
					export const DeleteTaskGroupTasksDocument = gql`
 | 
				
			||||||
 | 
					    mutation deleteTaskGroupTasks($taskGroupID: UUID!) {
 | 
				
			||||||
 | 
					  deleteTaskGroupTasks(input: {taskGroupID: $taskGroupID}) {
 | 
				
			||||||
 | 
					    tasks
 | 
				
			||||||
 | 
					    taskGroupID
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					export type DeleteTaskGroupTasksMutationFn = ApolloReactCommon.MutationFunction<DeleteTaskGroupTasksMutation, DeleteTaskGroupTasksMutationVariables>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * __useDeleteTaskGroupTasksMutation__
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * To run a mutation, you first call `useDeleteTaskGroupTasksMutation` within a React component and pass it any options that fit your needs.
 | 
				
			||||||
 | 
					 * When your component renders, `useDeleteTaskGroupTasksMutation` 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 [deleteTaskGroupTasksMutation, { data, loading, error }] = useDeleteTaskGroupTasksMutation({
 | 
				
			||||||
 | 
					 *   variables: {
 | 
				
			||||||
 | 
					 *      taskGroupID: // value for 'taskGroupID'
 | 
				
			||||||
 | 
					 *   },
 | 
				
			||||||
 | 
					 * });
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function useDeleteTaskGroupTasksMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteTaskGroupTasksMutation, DeleteTaskGroupTasksMutationVariables>) {
 | 
				
			||||||
 | 
					        return ApolloReactHooks.useMutation<DeleteTaskGroupTasksMutation, DeleteTaskGroupTasksMutationVariables>(DeleteTaskGroupTasksDocument, baseOptions);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					export type DeleteTaskGroupTasksMutationHookResult = ReturnType<typeof useDeleteTaskGroupTasksMutation>;
 | 
				
			||||||
 | 
					export type DeleteTaskGroupTasksMutationResult = ApolloReactCommon.MutationResult<DeleteTaskGroupTasksMutation>;
 | 
				
			||||||
 | 
					export type DeleteTaskGroupTasksMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteTaskGroupTasksMutation, DeleteTaskGroupTasksMutationVariables>;
 | 
				
			||||||
 | 
					export const DuplicateTaskGroupDocument = gql`
 | 
				
			||||||
 | 
					    mutation duplicateTaskGroup($taskGroupID: UUID!, $name: String!, $position: Float!, $projectID: UUID!) {
 | 
				
			||||||
 | 
					  duplicateTaskGroup(input: {projectID: $projectID, taskGroupID: $taskGroupID, name: $name, position: $position}) {
 | 
				
			||||||
 | 
					    taskGroup {
 | 
				
			||||||
 | 
					      id
 | 
				
			||||||
 | 
					      name
 | 
				
			||||||
 | 
					      position
 | 
				
			||||||
 | 
					      tasks {
 | 
				
			||||||
 | 
					        ...TaskFields
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					    ${TaskFieldsFragmentDoc}`;
 | 
				
			||||||
 | 
					export type DuplicateTaskGroupMutationFn = ApolloReactCommon.MutationFunction<DuplicateTaskGroupMutation, DuplicateTaskGroupMutationVariables>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * __useDuplicateTaskGroupMutation__
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * To run a mutation, you first call `useDuplicateTaskGroupMutation` within a React component and pass it any options that fit your needs.
 | 
				
			||||||
 | 
					 * When your component renders, `useDuplicateTaskGroupMutation` 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 [duplicateTaskGroupMutation, { data, loading, error }] = useDuplicateTaskGroupMutation({
 | 
				
			||||||
 | 
					 *   variables: {
 | 
				
			||||||
 | 
					 *      taskGroupID: // value for 'taskGroupID'
 | 
				
			||||||
 | 
					 *      name: // value for 'name'
 | 
				
			||||||
 | 
					 *      position: // value for 'position'
 | 
				
			||||||
 | 
					 *      projectID: // value for 'projectID'
 | 
				
			||||||
 | 
					 *   },
 | 
				
			||||||
 | 
					 * });
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function useDuplicateTaskGroupMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DuplicateTaskGroupMutation, DuplicateTaskGroupMutationVariables>) {
 | 
				
			||||||
 | 
					        return ApolloReactHooks.useMutation<DuplicateTaskGroupMutation, DuplicateTaskGroupMutationVariables>(DuplicateTaskGroupDocument, baseOptions);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					export type DuplicateTaskGroupMutationHookResult = ReturnType<typeof useDuplicateTaskGroupMutation>;
 | 
				
			||||||
 | 
					export type DuplicateTaskGroupMutationResult = ApolloReactCommon.MutationResult<DuplicateTaskGroupMutation>;
 | 
				
			||||||
 | 
					export type DuplicateTaskGroupMutationOptions = ApolloReactCommon.BaseMutationOptions<DuplicateTaskGroupMutation, DuplicateTaskGroupMutationVariables>;
 | 
				
			||||||
 | 
					export const SortTaskGroupDocument = gql`
 | 
				
			||||||
 | 
					    mutation sortTaskGroup($tasks: [TaskPositionUpdate!]!, $taskGroupID: UUID!) {
 | 
				
			||||||
 | 
					  sortTaskGroup(input: {taskGroupID: $taskGroupID, tasks: $tasks}) {
 | 
				
			||||||
 | 
					    taskGroupID
 | 
				
			||||||
 | 
					    tasks {
 | 
				
			||||||
 | 
					      id
 | 
				
			||||||
 | 
					      position
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					export type SortTaskGroupMutationFn = ApolloReactCommon.MutationFunction<SortTaskGroupMutation, SortTaskGroupMutationVariables>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * __useSortTaskGroupMutation__
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * To run a mutation, you first call `useSortTaskGroupMutation` within a React component and pass it any options that fit your needs.
 | 
				
			||||||
 | 
					 * When your component renders, `useSortTaskGroupMutation` 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 [sortTaskGroupMutation, { data, loading, error }] = useSortTaskGroupMutation({
 | 
				
			||||||
 | 
					 *   variables: {
 | 
				
			||||||
 | 
					 *      tasks: // value for 'tasks'
 | 
				
			||||||
 | 
					 *      taskGroupID: // value for 'taskGroupID'
 | 
				
			||||||
 | 
					 *   },
 | 
				
			||||||
 | 
					 * });
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function useSortTaskGroupMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<SortTaskGroupMutation, SortTaskGroupMutationVariables>) {
 | 
				
			||||||
 | 
					        return ApolloReactHooks.useMutation<SortTaskGroupMutation, SortTaskGroupMutationVariables>(SortTaskGroupDocument, baseOptions);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					export type SortTaskGroupMutationHookResult = ReturnType<typeof useSortTaskGroupMutation>;
 | 
				
			||||||
 | 
					export type SortTaskGroupMutationResult = ApolloReactCommon.MutationResult<SortTaskGroupMutation>;
 | 
				
			||||||
 | 
					export type SortTaskGroupMutationOptions = ApolloReactCommon.BaseMutationOptions<SortTaskGroupMutation, SortTaskGroupMutationVariables>;
 | 
				
			||||||
export const UpdateTaskGroupNameDocument = gql`
 | 
					export const UpdateTaskGroupNameDocument = gql`
 | 
				
			||||||
    mutation updateTaskGroupName($taskGroupID: UUID!, $name: String!) {
 | 
					    mutation updateTaskGroupName($taskGroupID: UUID!, $name: String!) {
 | 
				
			||||||
  updateTaskGroupName(input: {taskGroupID: $taskGroupID, name: $name}) {
 | 
					  updateTaskGroupName(input: {taskGroupID: $taskGroupID, name: $name}) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					import gql from 'graphql-tag';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DELETE_TASK_GROUP_TASKS_MUTATION = gql`
 | 
				
			||||||
 | 
					  mutation deleteTaskGroupTasks($taskGroupID: UUID!) {
 | 
				
			||||||
 | 
					    deleteTaskGroupTasks(input: { taskGroupID: $taskGroupID }) {
 | 
				
			||||||
 | 
					      tasks
 | 
				
			||||||
 | 
					      taskGroupID
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
							
								
								
									
										26
									
								
								frontend/src/shared/graphql/taskGroup/duplicateTaskGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								frontend/src/shared/graphql/taskGroup/duplicateTaskGroup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					import gql from 'graphql-tag';
 | 
				
			||||||
 | 
					import TASK_FRAGMENT from '../fragments/task';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DUPLICATE_TASK_GROUP_MUTATION = gql`
 | 
				
			||||||
 | 
					mutation duplicateTaskGroup($taskGroupID: UUID!, $name: String!, $position: Float!, $projectID: UUID!) {
 | 
				
			||||||
 | 
					  duplicateTaskGroup(
 | 
				
			||||||
 | 
					  input: {
 | 
				
			||||||
 | 
					    projectID: $projectID
 | 
				
			||||||
 | 
					    taskGroupID: $taskGroupID
 | 
				
			||||||
 | 
					    name: $name
 | 
				
			||||||
 | 
					    position: $position
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    taskGroup {
 | 
				
			||||||
 | 
					      id
 | 
				
			||||||
 | 
					      name
 | 
				
			||||||
 | 
					      position
 | 
				
			||||||
 | 
					      tasks {
 | 
				
			||||||
 | 
					        ...TaskFields
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ${TASK_FRAGMENT}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
							
								
								
									
										13
									
								
								frontend/src/shared/graphql/taskGroup/sortTaskGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/src/shared/graphql/taskGroup/sortTaskGroup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import gql from 'graphql-tag';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SORT_TASK_GROUP_MUTATION = gql`
 | 
				
			||||||
 | 
					  mutation sortTaskGroup($tasks: [TaskPositionUpdate!]!, $taskGroupID: UUID!) {
 | 
				
			||||||
 | 
					    sortTaskGroup(input: { taskGroupID: $taskGroupID, tasks: $tasks }) {
 | 
				
			||||||
 | 
					      taskGroupID
 | 
				
			||||||
 | 
					      tasks {
 | 
				
			||||||
 | 
					        id
 | 
				
			||||||
 | 
					        position
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
							
								
								
									
										132
									
								
								frontend/src/shared/utils/sorting.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								frontend/src/shared/utils/sorting.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					import moment from 'moment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum TaskSortingType {
 | 
				
			||||||
 | 
					  NONE,
 | 
				
			||||||
 | 
					  COMPLETE,
 | 
				
			||||||
 | 
					  DUE_DATE,
 | 
				
			||||||
 | 
					  MEMBERS,
 | 
				
			||||||
 | 
					  LABELS,
 | 
				
			||||||
 | 
					  TASK_TITLE,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum TaskSortingDirection {
 | 
				
			||||||
 | 
					  ASC,
 | 
				
			||||||
 | 
					  DESC,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TaskSorting = {
 | 
				
			||||||
 | 
					  type: TaskSortingType;
 | 
				
			||||||
 | 
					  direction: TaskSortingDirection;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function sortString(a: string, b: string) {
 | 
				
			||||||
 | 
					  if (a < b) {
 | 
				
			||||||
 | 
					    return -1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (a > b) {
 | 
				
			||||||
 | 
					    return 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function sortTasks(a: Task, b: Task, taskSorting: TaskSorting) {
 | 
				
			||||||
 | 
					  if (taskSorting.type === TaskSortingType.TASK_TITLE) {
 | 
				
			||||||
 | 
					    if (a.name < b.name) {
 | 
				
			||||||
 | 
					      return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (a.name > b.name) {
 | 
				
			||||||
 | 
					      return 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (taskSorting.type === TaskSortingType.DUE_DATE) {
 | 
				
			||||||
 | 
					    if (a.dueDate && !b.dueDate) {
 | 
				
			||||||
 | 
					      return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (b.dueDate && !a.dueDate) {
 | 
				
			||||||
 | 
					      return 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return moment(a.dueDate).diff(moment(b.dueDate));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (taskSorting.type === TaskSortingType.COMPLETE) {
 | 
				
			||||||
 | 
					    if (a.complete && !b.complete) {
 | 
				
			||||||
 | 
					      return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (b.complete && !a.complete) {
 | 
				
			||||||
 | 
					      return 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (taskSorting.type === TaskSortingType.LABELS) {
 | 
				
			||||||
 | 
					    // sorts non-empty labels by name, then by empty label color name
 | 
				
			||||||
 | 
					    let aLabels = [];
 | 
				
			||||||
 | 
					    let bLabels = [];
 | 
				
			||||||
 | 
					    let aLabelsEmpty = [];
 | 
				
			||||||
 | 
					    let bLabelsEmpty = [];
 | 
				
			||||||
 | 
					    if (a.labels) {
 | 
				
			||||||
 | 
					      for (const aLabel of a.labels) {
 | 
				
			||||||
 | 
					        if (aLabel.projectLabel.name && aLabel.projectLabel.name !== '') {
 | 
				
			||||||
 | 
					          aLabels.push(aLabel.projectLabel.name);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          aLabelsEmpty.push(aLabel.projectLabel.labelColor.name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (b.labels) {
 | 
				
			||||||
 | 
					      for (const bLabel of b.labels) {
 | 
				
			||||||
 | 
					        if (bLabel.projectLabel.name && bLabel.projectLabel.name !== '') {
 | 
				
			||||||
 | 
					          bLabels.push(bLabel.projectLabel.name);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          bLabelsEmpty.push(bLabel.projectLabel.labelColor.name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    aLabels = aLabels.sort((aLabel, bLabel) => sortString(aLabel, bLabel));
 | 
				
			||||||
 | 
					    bLabels = bLabels.sort((aLabel, bLabel) => sortString(aLabel, bLabel));
 | 
				
			||||||
 | 
					    aLabelsEmpty = aLabelsEmpty.sort((aLabel, bLabel) => sortString(aLabel, bLabel));
 | 
				
			||||||
 | 
					    bLabelsEmpty = bLabelsEmpty.sort((aLabel, bLabel) => sortString(aLabel, bLabel));
 | 
				
			||||||
 | 
					    if (aLabelsEmpty.length !== 0 || bLabelsEmpty.length !== 0) {
 | 
				
			||||||
 | 
					      if (aLabelsEmpty.length > bLabelsEmpty.length) {
 | 
				
			||||||
 | 
					        if (bLabels.length !== 0) {
 | 
				
			||||||
 | 
					          return 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return -1;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (aLabels.length < bLabels.length) {
 | 
				
			||||||
 | 
					      return 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (aLabels.length > bLabels.length) {
 | 
				
			||||||
 | 
					      return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (taskSorting.type === TaskSortingType.MEMBERS) {
 | 
				
			||||||
 | 
					    let aMembers = [];
 | 
				
			||||||
 | 
					    let bMembers = [];
 | 
				
			||||||
 | 
					    if (a.assigned) {
 | 
				
			||||||
 | 
					      for (const aMember of a.assigned) {
 | 
				
			||||||
 | 
					        if (aMember.fullName) {
 | 
				
			||||||
 | 
					          aMembers.push(aMember.fullName);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (b.assigned) {
 | 
				
			||||||
 | 
					      for (const bMember of b.assigned) {
 | 
				
			||||||
 | 
					        if (bMember.fullName) {
 | 
				
			||||||
 | 
					          bMembers.push(bMember.fullName);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    aMembers = aMembers.sort((aMember, bMember) => sortString(aMember, bMember));
 | 
				
			||||||
 | 
					    bMembers = bMembers.sort((aMember, bMember) => sortString(aMember, bMember));
 | 
				
			||||||
 | 
					    if (aMembers.length < bMembers.length) {
 | 
				
			||||||
 | 
					      return 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (aMembers.length > bMembers.length) {
 | 
				
			||||||
 | 
					      return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -19,6 +19,7 @@ type Querier interface {
 | 
				
			|||||||
	CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
 | 
						CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
 | 
				
			||||||
	CreateSystemOption(ctx context.Context, arg CreateSystemOptionParams) (SystemOption, error)
 | 
						CreateSystemOption(ctx context.Context, arg CreateSystemOptionParams) (SystemOption, error)
 | 
				
			||||||
	CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
 | 
						CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
 | 
				
			||||||
 | 
						CreateTaskAll(ctx context.Context, arg CreateTaskAllParams) (Task, error)
 | 
				
			||||||
	CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
 | 
						CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
 | 
				
			||||||
	CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error)
 | 
						CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error)
 | 
				
			||||||
	CreateTaskChecklistItem(ctx context.Context, arg CreateTaskChecklistItemParams) (TaskChecklistItem, error)
 | 
						CreateTaskChecklistItem(ctx context.Context, arg CreateTaskChecklistItemParams) (TaskChecklistItem, error)
 | 
				
			||||||
@@ -111,6 +112,7 @@ type Querier interface {
 | 
				
			|||||||
	UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
 | 
						UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
 | 
				
			||||||
	UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)
 | 
						UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)
 | 
				
			||||||
	UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)
 | 
						UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)
 | 
				
			||||||
 | 
						UpdateTaskPosition(ctx context.Context, arg UpdateTaskPositionParams) (Task, error)
 | 
				
			||||||
	UpdateTeamMemberRole(ctx context.Context, arg UpdateTeamMemberRoleParams) (TeamMember, error)
 | 
						UpdateTeamMemberRole(ctx context.Context, arg UpdateTeamMemberRoleParams) (TeamMember, error)
 | 
				
			||||||
	UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
 | 
						UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
 | 
				
			||||||
	UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UserAccount, error)
 | 
						UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UserAccount, error)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,10 @@
 | 
				
			|||||||
INSERT INTO task (task_group_id, created_at, name, position)
 | 
					INSERT INTO task (task_group_id, created_at, name, position)
 | 
				
			||||||
  VALUES($1, $2, $3, $4) RETURNING *;
 | 
					  VALUES($1, $2, $3, $4) RETURNING *;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- name: CreateTaskAll :one
 | 
				
			||||||
 | 
					INSERT INTO task (task_group_id, created_at, name, position, description, complete, due_date)
 | 
				
			||||||
 | 
					  VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- name: UpdateTaskDescription :one
 | 
					-- name: UpdateTaskDescription :one
 | 
				
			||||||
UPDATE task SET description = $2 WHERE task_id = $1 RETURNING *;
 | 
					UPDATE task SET description = $2 WHERE task_id = $1 RETURNING *;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,6 +21,9 @@ SELECT * FROM task;
 | 
				
			|||||||
-- name: UpdateTaskLocation :one
 | 
					-- name: UpdateTaskLocation :one
 | 
				
			||||||
UPDATE task SET task_group_id = $2, position = $3 WHERE task_id = $1 RETURNING *;
 | 
					UPDATE task SET task_group_id = $2, position = $3 WHERE task_id = $1 RETURNING *;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- name: UpdateTaskPosition :one
 | 
				
			||||||
 | 
					UPDATE task SET position = $2 WHERE task_id = $1 RETURNING *;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- name: DeleteTaskByID :exec
 | 
					-- name: DeleteTaskByID :exec
 | 
				
			||||||
DELETE FROM task WHERE task_id = $1;
 | 
					DELETE FROM task WHERE task_id = $1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,6 +45,46 @@ func (q *Queries) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, e
 | 
				
			|||||||
	return i, err
 | 
						return i, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createTaskAll = `-- name: CreateTaskAll :one
 | 
				
			||||||
 | 
					INSERT INTO task (task_group_id, created_at, name, position, description, complete, due_date)
 | 
				
			||||||
 | 
					  VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CreateTaskAllParams struct {
 | 
				
			||||||
 | 
						TaskGroupID uuid.UUID      `json:"task_group_id"`
 | 
				
			||||||
 | 
						CreatedAt   time.Time      `json:"created_at"`
 | 
				
			||||||
 | 
						Name        string         `json:"name"`
 | 
				
			||||||
 | 
						Position    float64        `json:"position"`
 | 
				
			||||||
 | 
						Description sql.NullString `json:"description"`
 | 
				
			||||||
 | 
						Complete    bool           `json:"complete"`
 | 
				
			||||||
 | 
						DueDate     sql.NullTime   `json:"due_date"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *Queries) CreateTaskAll(ctx context.Context, arg CreateTaskAllParams) (Task, error) {
 | 
				
			||||||
 | 
						row := q.db.QueryRowContext(ctx, createTaskAll,
 | 
				
			||||||
 | 
							arg.TaskGroupID,
 | 
				
			||||||
 | 
							arg.CreatedAt,
 | 
				
			||||||
 | 
							arg.Name,
 | 
				
			||||||
 | 
							arg.Position,
 | 
				
			||||||
 | 
							arg.Description,
 | 
				
			||||||
 | 
							arg.Complete,
 | 
				
			||||||
 | 
							arg.DueDate,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						var i Task
 | 
				
			||||||
 | 
						err := row.Scan(
 | 
				
			||||||
 | 
							&i.TaskID,
 | 
				
			||||||
 | 
							&i.TaskGroupID,
 | 
				
			||||||
 | 
							&i.CreatedAt,
 | 
				
			||||||
 | 
							&i.Name,
 | 
				
			||||||
 | 
							&i.Position,
 | 
				
			||||||
 | 
							&i.Description,
 | 
				
			||||||
 | 
							&i.DueDate,
 | 
				
			||||||
 | 
							&i.Complete,
 | 
				
			||||||
 | 
							&i.CompletedAt,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						return i, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const deleteTaskByID = `-- name: DeleteTaskByID :exec
 | 
					const deleteTaskByID = `-- name: DeleteTaskByID :exec
 | 
				
			||||||
DELETE FROM task WHERE task_id = $1
 | 
					DELETE FROM task WHERE task_id = $1
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
@@ -305,3 +345,29 @@ func (q *Queries) UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams)
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
	return i, err
 | 
						return i, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateTaskPosition = `-- name: UpdateTaskPosition :one
 | 
				
			||||||
 | 
					UPDATE task SET position = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UpdateTaskPositionParams struct {
 | 
				
			||||||
 | 
						TaskID   uuid.UUID `json:"task_id"`
 | 
				
			||||||
 | 
						Position float64   `json:"position"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *Queries) UpdateTaskPosition(ctx context.Context, arg UpdateTaskPositionParams) (Task, error) {
 | 
				
			||||||
 | 
						row := q.db.QueryRowContext(ctx, updateTaskPosition, arg.TaskID, arg.Position)
 | 
				
			||||||
 | 
						var i Task
 | 
				
			||||||
 | 
						err := row.Scan(
 | 
				
			||||||
 | 
							&i.TaskID,
 | 
				
			||||||
 | 
							&i.TaskGroupID,
 | 
				
			||||||
 | 
							&i.CreatedAt,
 | 
				
			||||||
 | 
							&i.Name,
 | 
				
			||||||
 | 
							&i.Position,
 | 
				
			||||||
 | 
							&i.Description,
 | 
				
			||||||
 | 
							&i.DueDate,
 | 
				
			||||||
 | 
							&i.Complete,
 | 
				
			||||||
 | 
							&i.CompletedAt,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						return i, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -111,6 +111,15 @@ type DeleteTaskGroupPayload struct {
 | 
				
			|||||||
	TaskGroup    *db.TaskGroup `json:"taskGroup"`
 | 
						TaskGroup    *db.TaskGroup `json:"taskGroup"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DeleteTaskGroupTasks struct {
 | 
				
			||||||
 | 
						TaskGroupID uuid.UUID `json:"taskGroupID"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DeleteTaskGroupTasksPayload struct {
 | 
				
			||||||
 | 
						TaskGroupID uuid.UUID   `json:"taskGroupID"`
 | 
				
			||||||
 | 
						Tasks       []uuid.UUID `json:"tasks"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DeleteTaskInput struct {
 | 
					type DeleteTaskInput struct {
 | 
				
			||||||
	TaskID string `json:"taskID"`
 | 
						TaskID string `json:"taskID"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -151,6 +160,17 @@ type DeleteUserAccountPayload struct {
 | 
				
			|||||||
	UserAccount *db.UserAccount `json:"userAccount"`
 | 
						UserAccount *db.UserAccount `json:"userAccount"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DuplicateTaskGroup struct {
 | 
				
			||||||
 | 
						ProjectID   uuid.UUID `json:"projectID"`
 | 
				
			||||||
 | 
						TaskGroupID uuid.UUID `json:"taskGroupID"`
 | 
				
			||||||
 | 
						Name        string    `json:"name"`
 | 
				
			||||||
 | 
						Position    float64   `json:"position"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DuplicateTaskGroupPayload struct {
 | 
				
			||||||
 | 
						TaskGroup *db.TaskGroup `json:"taskGroup"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type FindProject struct {
 | 
					type FindProject struct {
 | 
				
			||||||
	ProjectID uuid.UUID `json:"projectID"`
 | 
						ProjectID uuid.UUID `json:"projectID"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -296,10 +316,25 @@ type SetTaskComplete struct {
 | 
				
			|||||||
	Complete bool      `json:"complete"`
 | 
						Complete bool      `json:"complete"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SortTaskGroup struct {
 | 
				
			||||||
 | 
						TaskGroupID uuid.UUID            `json:"taskGroupID"`
 | 
				
			||||||
 | 
						Tasks       []TaskPositionUpdate `json:"tasks"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SortTaskGroupPayload struct {
 | 
				
			||||||
 | 
						TaskGroupID uuid.UUID `json:"taskGroupID"`
 | 
				
			||||||
 | 
						Tasks       []db.Task `json:"tasks"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TaskBadges struct {
 | 
					type TaskBadges struct {
 | 
				
			||||||
	Checklist *ChecklistBadge `json:"checklist"`
 | 
						Checklist *ChecklistBadge `json:"checklist"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TaskPositionUpdate struct {
 | 
				
			||||||
 | 
						TaskID   uuid.UUID `json:"taskID"`
 | 
				
			||||||
 | 
						Position float64   `json:"position"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TeamRole struct {
 | 
					type TeamRole struct {
 | 
				
			||||||
	TeamID   uuid.UUID `json:"teamID"`
 | 
						TeamID   uuid.UUID `json:"teamID"`
 | 
				
			||||||
	RoleCode RoleCode  `json:"roleCode"`
 | 
						RoleCode RoleCode  `json:"roleCode"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -547,6 +547,47 @@ extend type Mutation {
 | 
				
			|||||||
    TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
					    TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
  deleteTaskGroup(input: DeleteTaskGroupInput!):
 | 
					  deleteTaskGroup(input: DeleteTaskGroupInput!):
 | 
				
			||||||
    DeleteTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
					    DeleteTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
 | 
					  duplicateTaskGroup(input: DuplicateTaskGroup!):
 | 
				
			||||||
 | 
					    DuplicateTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
 | 
					  sortTaskGroup(input: SortTaskGroup!):
 | 
				
			||||||
 | 
					    SortTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
 | 
					  deleteTaskGroupTasks(input: DeleteTaskGroupTasks!):
 | 
				
			||||||
 | 
					    DeleteTaskGroupTasksPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input DeleteTaskGroupTasks {
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DeleteTaskGroupTasksPayload {
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					  tasks: [UUID!]!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input TaskPositionUpdate {
 | 
				
			||||||
 | 
					  taskID: UUID!
 | 
				
			||||||
 | 
					  position: Float!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SortTaskGroupPayload {
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					  tasks: [Task!]!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input SortTaskGroup {
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					  tasks: [TaskPositionUpdate!]!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input DuplicateTaskGroup {
 | 
				
			||||||
 | 
					  projectID: UUID!
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					  name: String!
 | 
				
			||||||
 | 
					  position: Float!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DuplicateTaskGroupPayload {
 | 
				
			||||||
 | 
					  taskGroup: TaskGroup!
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
input NewTaskGroupLocation {
 | 
					input NewTaskGroupLocation {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -438,6 +438,111 @@ func (r *mutationResolver) DeleteTaskGroup(ctx context.Context, input DeleteTask
 | 
				
			|||||||
	return &DeleteTaskGroupPayload{true, int(deletedTasks + deletedTaskGroups), &taskGroup}, nil
 | 
						return &DeleteTaskGroupPayload{true, int(deletedTasks + deletedTaskGroups), &taskGroup}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *mutationResolver) DuplicateTaskGroup(ctx context.Context, input DuplicateTaskGroup) (*DuplicateTaskGroupPayload, error) {
 | 
				
			||||||
 | 
						createdAt := time.Now().UTC()
 | 
				
			||||||
 | 
						taskGroup, err := r.Repository.CreateTaskGroup(ctx, db.CreateTaskGroupParams{ProjectID: input.ProjectID, Position: input.Position, Name: input.Name, CreatedAt: createdAt})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						originalTasks, err := r.Repository.GetTasksForTaskGroupID(ctx, input.TaskGroupID)
 | 
				
			||||||
 | 
						if err != nil && err != sql.ErrNoRows {
 | 
				
			||||||
 | 
							return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, originalTask := range originalTasks {
 | 
				
			||||||
 | 
							task, err := r.Repository.CreateTaskAll(ctx, db.CreateTaskAllParams{
 | 
				
			||||||
 | 
								TaskGroupID: taskGroup.TaskGroupID, CreatedAt: createdAt, Name: originalTask.Name, Position: originalTask.Position,
 | 
				
			||||||
 | 
								Complete: originalTask.Complete, DueDate: originalTask.DueDate, Description: originalTask.Description})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							members, err := r.Repository.GetAssignedMembersForTask(ctx, originalTask.TaskID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, member := range members {
 | 
				
			||||||
 | 
								_, err := r.Repository.CreateTaskAssigned(ctx, db.CreateTaskAssignedParams{
 | 
				
			||||||
 | 
									TaskID: task.TaskID, UserID: member.UserID, AssignedDate: member.AssignedDate})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							labels, err := r.Repository.GetTaskLabelsForTaskID(ctx, originalTask.TaskID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, label := range labels {
 | 
				
			||||||
 | 
								_, err := r.Repository.CreateTaskLabelForTask(ctx, db.CreateTaskLabelForTaskParams{
 | 
				
			||||||
 | 
									TaskID: task.TaskID, ProjectLabelID: label.ProjectLabelID, AssignedDate: label.AssignedDate})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							checklists, err := r.Repository.GetTaskChecklistsForTask(ctx, originalTask.TaskID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, checklist := range checklists {
 | 
				
			||||||
 | 
								newChecklist, err := r.Repository.CreateTaskChecklist(ctx, db.CreateTaskChecklistParams{
 | 
				
			||||||
 | 
									TaskID: task.TaskID, Name: checklist.Name, CreatedAt: createdAt, Position: checklist.Position})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								checklistItems, err := r.Repository.GetTaskChecklistItemsForTaskChecklist(ctx, checklist.TaskChecklistID)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for _, checklistItem := range checklistItems {
 | 
				
			||||||
 | 
									item, err := r.Repository.CreateTaskChecklistItem(ctx, db.CreateTaskChecklistItemParams{
 | 
				
			||||||
 | 
										TaskChecklistID: newChecklist.TaskChecklistID,
 | 
				
			||||||
 | 
										CreatedAt:       createdAt,
 | 
				
			||||||
 | 
										Name:            checklistItem.Name,
 | 
				
			||||||
 | 
										Position:        checklist.Position,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									if checklistItem.Complete {
 | 
				
			||||||
 | 
										r.Repository.SetTaskChecklistItemComplete(ctx, db.SetTaskChecklistItemCompleteParams{TaskChecklistItemID: item.TaskChecklistItemID, Complete: true})
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return &DuplicateTaskGroupPayload{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &DuplicateTaskGroupPayload{TaskGroup: &taskGroup}, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *mutationResolver) SortTaskGroup(ctx context.Context, input SortTaskGroup) (*SortTaskGroupPayload, error) {
 | 
				
			||||||
 | 
						tasks := []db.Task{}
 | 
				
			||||||
 | 
						for _, task := range input.Tasks {
 | 
				
			||||||
 | 
							t, err := r.Repository.UpdateTaskPosition(ctx, db.UpdateTaskPositionParams{TaskID: task.TaskID, Position: task.Position})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return &SortTaskGroupPayload{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							tasks = append(tasks, t)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &SortTaskGroupPayload{Tasks: tasks, TaskGroupID: input.TaskGroupID}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *mutationResolver) DeleteTaskGroupTasks(ctx context.Context, input DeleteTaskGroupTasks) (*DeleteTaskGroupTasksPayload, error) {
 | 
				
			||||||
 | 
						tasks, err := r.Repository.GetTasksForTaskGroupID(ctx, input.TaskGroupID)
 | 
				
			||||||
 | 
						if err != nil && err != sql.ErrNoRows {
 | 
				
			||||||
 | 
							return &DeleteTaskGroupTasksPayload{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						removedTasks := []uuid.UUID{}
 | 
				
			||||||
 | 
						for _, task := range tasks {
 | 
				
			||||||
 | 
							err = r.Repository.DeleteTaskByID(ctx, task.TaskID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return &DeleteTaskGroupTasksPayload{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							removedTasks = append(removedTasks, task.TaskID)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &DeleteTaskGroupTasksPayload{TaskGroupID: input.TaskGroupID, Tasks: removedTasks}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *mutationResolver) AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*db.Task, error) {
 | 
					func (r *mutationResolver) AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*db.Task, error) {
 | 
				
			||||||
	assignedDate := time.Now().UTC()
 | 
						assignedDate := time.Now().UTC()
 | 
				
			||||||
	_, err := r.Repository.CreateTaskLabelForTask(ctx, db.CreateTaskLabelForTaskParams{input.TaskID, input.ProjectLabelID, assignedDate})
 | 
						_, err := r.Repository.CreateTaskLabelForTask(ctx, db.CreateTaskLabelForTaskParams{input.TaskID, input.ProjectLabelID, assignedDate})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,47 @@ extend type Mutation {
 | 
				
			|||||||
    TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
					    TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
  deleteTaskGroup(input: DeleteTaskGroupInput!):
 | 
					  deleteTaskGroup(input: DeleteTaskGroupInput!):
 | 
				
			||||||
    DeleteTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
					    DeleteTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
 | 
					  duplicateTaskGroup(input: DuplicateTaskGroup!):
 | 
				
			||||||
 | 
					    DuplicateTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
 | 
					  sortTaskGroup(input: SortTaskGroup!):
 | 
				
			||||||
 | 
					    SortTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
 | 
					  deleteTaskGroupTasks(input: DeleteTaskGroupTasks!):
 | 
				
			||||||
 | 
					    DeleteTaskGroupTasksPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input DeleteTaskGroupTasks {
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DeleteTaskGroupTasksPayload {
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					  tasks: [UUID!]!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input TaskPositionUpdate {
 | 
				
			||||||
 | 
					  taskID: UUID!
 | 
				
			||||||
 | 
					  position: Float!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SortTaskGroupPayload {
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					  tasks: [Task!]!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input SortTaskGroup {
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					  tasks: [TaskPositionUpdate!]!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input DuplicateTaskGroup {
 | 
				
			||||||
 | 
					  projectID: UUID!
 | 
				
			||||||
 | 
					  taskGroupID: UUID!
 | 
				
			||||||
 | 
					  name: String!
 | 
				
			||||||
 | 
					  position: Float!
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DuplicateTaskGroupPayload {
 | 
				
			||||||
 | 
					  taskGroup: TaskGroup!
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
input NewTaskGroupLocation {
 | 
					input NewTaskGroupLocation {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								scripts/lint.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								scripts/lint.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					yarn --cwd frontend eslint $(echo $1 | sed 's/frontend\///g')
 | 
				
			||||||
		Reference in New Issue
	
	Block a user