From 370a64bc5e67fbf6498ccbd18143d3dae7b41521 Mon Sep 17 00:00:00 2001
From: Roland Bernard <rolbernard@unibz.it>
Date: Tue, 22 Jun 2021 15:42:25 +0200
Subject: [PATCH] Added the ability to quickly change the task status

---
 .../src/components/ui/EditableTag/index.tsx   | 43 ++++++++++++++++
 client/src/components/ui/EditableTag/tag.scss | 50 +++++++++++++++++++
 client/src/components/ui/Tag/index.tsx        |  3 +-
 client/src/index.scss                         |  2 +-
 client/src/pages/Tasks/TaskDetail/index.tsx   | 26 ++++++++--
 5 files changed, 117 insertions(+), 7 deletions(-)
 create mode 100644 client/src/components/ui/EditableTag/index.tsx
 create mode 100644 client/src/components/ui/EditableTag/tag.scss

diff --git a/client/src/components/ui/EditableTag/index.tsx b/client/src/components/ui/EditableTag/index.tsx
new file mode 100644
index 0000000..8e07650
--- /dev/null
+++ b/client/src/components/ui/EditableTag/index.tsx
@@ -0,0 +1,43 @@
+
+import { useEffect, useState } from 'react';
+import Tag from '../Tag';
+
+import './tag.scss';
+
+interface Props<Tag> {
+    label: Tag;
+    icon?: string;
+    color?: string;
+    possible: Tag[];
+    onChange: (value: Tag) => any;
+}
+
+export default function EditableTag<Tag extends string>({ label, icon, color, possible, onChange }: Props<Tag>) {
+    const [open, setOpen] = useState(false)
+
+    useEffect(() => {
+        if (open) {
+            const onClick = () => setOpen(false);
+            document.addEventListener('click', onClick);
+            return () => {
+                document.removeEventListener('click', onClick);
+            }
+        }
+    }, [open]);
+
+    return (
+        <span className={'tag-wrapper' + (open ? ' open' : '')} onClick={() => setOpen(!open)}>
+            <Tag label={label} icon={icon} color={color} />
+            <div className="tag-dropdown">
+                {
+                    possible.filter(item => item !== label).map(item => (
+                        <span className="tag-item" onClick={() => onChange(item)}>
+                            {item}
+                        </span>
+                    ))
+                }
+            </div>
+        </span>
+    );
+}
+
diff --git a/client/src/components/ui/EditableTag/tag.scss b/client/src/components/ui/EditableTag/tag.scss
new file mode 100644
index 0000000..7550256
--- /dev/null
+++ b/client/src/components/ui/EditableTag/tag.scss
@@ -0,0 +1,50 @@
+
+@use 'styles/settings.scss' as s;
+
+.tag-wrapper {
+    display: inline-flex;
+    flex-flow: column;
+    align-items: flex-start;
+    cursor: pointer;
+    position: relative;
+    z-index: 2000;
+
+    &.open {
+        .tag-dropdown {
+            opacity: 1;
+            transform: translateY(80%);
+            visibility: visible;
+        }
+
+        .icon {
+            transform: rotate(180deg);
+        }
+    }
+
+    .tag-dropdown {
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        transform: translateY(75%);
+        background: s.$background-white;
+        z-index: 2000;
+        border-radius: 5px;
+        white-space: nowrap;
+        box-shadow: 0 0 5px rgba(s.$black, 0.05);
+        visibility: hidden;
+        opacity: 0;
+
+        .tag-item {
+            padding: 10px 20px;
+            display: block;
+            color: s.$text;
+            border-radius: 5px;
+            min-width: 100px;
+
+            &:hover {
+                box-shadow: 0 0 10px rgba(s.$black, 0.1);
+            }
+        }
+    }
+}
+
diff --git a/client/src/components/ui/Tag/index.tsx b/client/src/components/ui/Tag/index.tsx
index 9aa37e4..65ad2e3 100644
--- a/client/src/components/ui/Tag/index.tsx
+++ b/client/src/components/ui/Tag/index.tsx
@@ -17,7 +17,6 @@ export default function Tag({ label, icon, color }: Props) {
             )}
             {label}
         </span>
-
     );
-
 }
+
diff --git a/client/src/index.scss b/client/src/index.scss
index 56c028d..0a26121 100644
--- a/client/src/index.scss
+++ b/client/src/index.scss
@@ -75,7 +75,7 @@
 
 * {
     transition: s.$transition;
-    transition-property: transform, opacity, color, background, box-shadow, stroke;
+    transition-property: transform, opacity, color, background, box-shadow, stroke, visibility;
     font-family: s.$body-font;
     margin: 0;
     padding: 0;
diff --git a/client/src/pages/Tasks/TaskDetail/index.tsx b/client/src/pages/Tasks/TaskDetail/index.tsx
index b3a8336..220f81a 100644
--- a/client/src/pages/Tasks/TaskDetail/index.tsx
+++ b/client/src/pages/Tasks/TaskDetail/index.tsx
@@ -5,23 +5,23 @@ import { useHistory, useParams } from 'react-router';
 
 import { getTeam } from 'adapters/team';
 import { AssignedUser } from 'adapters/user';
-import { StatusColors } from 'adapters/common';
+import { StatusColors, Status } from 'adapters/common';
 import { getLoggedInUser } from 'adapters/auth';
 import { getProject, Project } from 'adapters/project';
 import { getTask, getTaskAssignees, Task, updateTask } from 'adapters/task';
 
-import Tag from 'components/ui/Tag';
 import LongText from 'components/ui/LongText';
 import Tabs from 'components/navigation/Tabs';
 import DetailGrid from 'components/layout/DetailGrid';
 import LoadingScreen from 'components/ui/LoadingScreen';
 import ButtonLink from 'components/navigation/ButtonLink';
+import AssignForm from 'components/forms/AssignForm';
+import EditableTag from 'components/ui/EditableTag';
 
 import TaskAssignees from './TaskAssignees';
 import TaskComments from './TaskComments';
 
 import './task-detail.scss';
-import AssignForm from 'components/forms/AssignForm';
 
 export interface Params {
     taskId: string;
@@ -72,6 +72,19 @@ export default function TaskDetail() {
         }
     }, [taskId, userId, assignment]);
 
+    const onStatusChange = useCallback((status: Status) => {
+        if (task) {
+            updateTask(task.id, {
+                status: status,
+            }).then(() => {
+                setTask({
+                    ...task,
+                    status: status
+                });
+            });
+        }
+    }, [task]);
+
     if (task) {
         return (
             <div className={'tasks-detail-page theme-' + task.color}>
@@ -79,7 +92,12 @@ export default function TaskDetail() {
                     arrow_back
                 </Link>
                 <div className="content-container">
-                    <Tag label={task.status} color={StatusColors.get(task.status)} />
+                    <EditableTag
+                        label={task.status}
+                        color={StatusColors.get(task.status)}
+                        possible={Object.values(Status)}
+                        onChange={onStatusChange}
+                    />
                     <h1>{task.name}</h1>
                     <div className="description-container">
                         <LongText text={task.text} />
-- 
GitLab