Repository: inovex/scrumlr.io
Pull request #1621 (2022-04-26 13:56:15)

Various changes
## Description

Some last minute changes for bugs/issues we've discovered in our meeting.

## Changelist

- Unify sizes of icons in board settings
- In "non-cumulative voting" the added votes are displayed next to the permanently displayed add vote button
- The add vote button has been disabled once no votes are remaining
- Enabled cumulative votes has become the default state
- Fixed horizontal column scrolling
- Fix the color of the trash icon in the note dialog to also be coloured in the column specific accent color 
- Show only unique votes on cards within a stack on detail screen 


Patch:
diff --git a/src/assets/icon-share.svg b/src/assets/icon-share.svg
index 4e60166d..4e657f50 100644
--- a/src/assets/icon-share.svg
+++ b/src/assets/icon-share.svg
@@ -1,6 +1,9 @@
-<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
-<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
-<path transform="translate(4 15)" d="m0 1v2c1.3527e-16 1.1046 0.89543 2 2 2h12c1.1046 0 2-0.89543 2-2v-3" opacity=".5"/>
-<path transform="translate(4 3)" d="m9.3103 3.8182v-3.8182l8.6897 6.3636-8.6897 6.3636v-3.8182c-3.4448 0-6.6103 0.12727-9.3103 5.0909 0-3.15 0.31034-10.182 9.3103-10.182z" stroke-linejoin="round"/>
-</g>
+<svg fill="currentColor" height="32" width="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+    <rect id="BG" width="32" height="32" fill="none"/>
+    <g id="icon_share_24-grey" data-name="icon/share/24-grey" transform="translate(4 4)">
+    <g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
+        <path transform="translate(4 15)" d="m0 1v2c1.3527e-16 1.1046 0.89543 2 2 2h12c1.1046 0 2-0.89543 2-2v-3" opacity=".5"/>
+        <path transform="translate(4 3)" d="m9.3103 3.8182v-3.8182l8.6897 6.3636-8.6897 6.3636v-3.8182c-3.4448 0-6.6103 0.12727-9.3103 5.0909 0-3.15 0.31034-10.182 9.3103-10.182z" stroke-linejoin="round"/>
+    </g>
+    </g>
 </svg>
diff --git a/src/components/Board/Board.scss b/src/components/Board/Board.scss
index 781e4229..a685af0c 100644
--- a/src/components/Board/Board.scss
+++ b/src/components/Board/Board.scss
@@ -22,6 +22,7 @@
   scrollbar-width: none;
 
   overflow: hidden;
+  overflow-x: scroll;
 }
 .board::-webkit-scrollbar {
   display: none;
diff --git a/src/components/Note/Note.tsx b/src/components/Note/Note.tsx
index b2c14701..b1242140 100644
--- a/src/components/Note/Note.tsx
+++ b/src/components/Note/Note.tsx
@@ -128,7 +128,7 @@ export const Note = (props: NoteProps) => {
               <figcaption className="note__author-name">{author!.user.name}</figcaption>
             </figure>
           )}
-          <Votes tabIndex={props.tabIndex} noteId={props.noteId!} />
+          <Votes tabIndex={props.tabIndex} noteId={props.noteId!} aggregateVotes />
         </div>
         {showDialog && (
           <NoteDialog
diff --git a/src/components/NoteDialog/NoteDialogComponents/NoteDialogNoteComponents/NoteDialogOptions/NoteDialogDeleteNoteButton.scss b/src/components/NoteDialog/NoteDialogComponents/NoteDialogNoteComponents/NoteDialogOptions/NoteDialogDeleteNoteButton.scss
index 655cad9d..2b33c84a 100644
--- a/src/components/NoteDialog/NoteDialogComponents/NoteDialogNoteComponents/NoteDialogOptions/NoteDialogDeleteNoteButton.scss
+++ b/src/components/NoteDialog/NoteDialogComponents/NoteDialogNoteComponents/NoteDialogOptions/NoteDialogDeleteNoteButton.scss
@@ -5,7 +5,7 @@
   height: $option-height;
   width: $option-width;
   background-color: transparent;
-  color: rgba($color-backlog-blue, 0.4);
+  color: rgba(var(--accent-color-rgb), 0.4);
 }
 
 .note-dialog__note-option__remove-icon {
@@ -15,5 +15,5 @@
 
 .note-dialog__note-option__remove:focus,
 .note-dialog__note-option__remove:hover {
-  color: $color-backlog-blue;
+  color: var(--accent-color);
 }
diff --git a/src/components/Votes/VoteButtons/AddVoteButton.tsx b/src/components/Votes/VoteButtons/AddVoteButton.tsx
index 9d1fb2bc..0d1b66b7 100644
--- a/src/components/Votes/VoteButtons/AddVoteButton.tsx
+++ b/src/components/Votes/VoteButtons/AddVoteButton.tsx
@@ -1,11 +1,9 @@
-import {useAppSelector} from "store";
 import {Actions} from "store/action";
 import {DotButton} from "components/DotButton";
 import "./AddVoteButton.scss";
 import "./VoteButton.scss";
 import {ReactComponent as PlusIcon} from "assets/icon-add.svg";
 import {FC} from "react";
-import {ApplicationState} from "types";
 import {useDispatch} from "react-redux";
 
 type AddVoteProps = {
@@ -16,14 +14,9 @@ type AddVoteProps = {
 
 export const AddVoteButton: FC<AddVoteProps> = ({noteId, tabIndex, disabled}) => {
   const dispatch = useDispatch();
-  const state = useAppSelector((applicationState: ApplicationState) => ({
-    votingEnabled: !!applicationState.votings.open,
-  }));
 
   const addVote = () => {
-    if (state.votingEnabled) {
-      dispatch(Actions.addVote(noteId));
-    }
+    dispatch(Actions.addVote(noteId));
   };
 
   return (
diff --git a/src/components/Votes/VoteButtons/RemoveVoteButton.scss b/src/components/Votes/VoteButtons/RemoveVoteButton.scss
index 97501ef4..5e2ab40e 100644
--- a/src/components/Votes/VoteButtons/RemoveVoteButton.scss
+++ b/src/components/Votes/VoteButtons/RemoveVoteButton.scss
@@ -9,19 +9,19 @@
   color: var(--accent-color);
 }
 
-.vote-button-remove--own-vote {
+.vote-button-remove:not([disabled]) {
   background-color: var(--accent-color);
   color: $color-white;
 }
 
-.vote-button-remove.vote-button-remove--own-vote:not([disabled]):focus > .vote-button-remove__folded-corner,
-.vote-button-remove.vote-button-remove--own-vote:not([disabled]):hover > .vote-button-remove__folded-corner {
+.vote-button-remove:not([disabled]):focus > .vote-button-remove__folded-corner,
+.vote-button-remove:not([disabled]):hover > .vote-button-remove__folded-corner {
   border-width: 12px 0 0 12px;
 }
-.vote-button-remove:not(.vote-button-remove--own-vote):not([disabled]):active {
+.vote-button-remove:not([disabled]):active {
   transform: none;
 }
-.vote-button-remove:not(.vote-button-remove--own-vote) {
+.vote-button-remove:disabled {
   cursor: default;
 }
 
diff --git a/src/components/Votes/VoteButtons/RemoveVoteButton.tsx b/src/components/Votes/VoteButtons/RemoveVoteButton.tsx
index 6f31ccea..d164ac9a 100644
--- a/src/components/Votes/VoteButtons/RemoveVoteButton.tsx
+++ b/src/components/Votes/VoteButtons/RemoveVoteButton.tsx
@@ -1,19 +1,16 @@
+import {FC} from "react";
+import {useDispatch} from "react-redux";
 import {Actions} from "store/action";
 import {DotButton} from "components/DotButton";
 import "./RemoveVoteButton.scss";
-import classNames from "classnames";
-import {FC} from "react";
-import {useDispatch} from "react-redux";
 
 type RemoveVoteProps = {
   noteId: string;
-  activeVoting: boolean;
-  votes: number;
-  ownVotes: number;
   tabIndex: number;
+  disabled?: boolean;
 };
 
-export const RemoveVoteButton: FC<RemoveVoteProps> = ({noteId, activeVoting, votes, ownVotes, tabIndex}) => {
+export const RemoveVoteButton: FC<RemoveVoteProps> = ({noteId, tabIndex, disabled, children}) => {
   const dispatch = useDispatch();
 
   const deleteVote = () => {
@@ -21,9 +18,9 @@ export const RemoveVoteButton: FC<RemoveVoteProps> = ({noteId, activeVoting, vot
   };
 
   return (
-    <DotButton tabIndex={tabIndex} className={classNames("vote-button-remove", {"vote-button-remove--own-vote": ownVotes > 0})} disabled={!activeVoting} onClick={deleteVote}>
+    <DotButton tabIndex={tabIndex} className="vote-button-remove" disabled={disabled} onClick={deleteVote}>
       <span className="vote-button-remove__folded-corner" />
-      <span>{activeVoting ? ownVotes : votes}</span>
+      <span>{children}</span>
     </DotButton>
   );
 };
diff --git a/src/components/Votes/Votes.scss b/src/components/Votes/Votes.scss
index d3a34911..516b5326 100644
--- a/src/components/Votes/Votes.scss
+++ b/src/components/Votes/Votes.scss
@@ -3,6 +3,9 @@
 .votes {
   display: inline-flex;
   position: relative;
+  width: 60px;
+  height: 28px;
+  justify-content: end;
 }
 
 .votes > * + * {
diff --git a/src/components/Votes/Votes.tsx b/src/components/Votes/Votes.tsx
index 5733e9c7..525d8be8 100644
--- a/src/components/Votes/Votes.tsx
+++ b/src/components/Votes/Votes.tsx
@@ -1,37 +1,55 @@
-import {useAppSelector} from "store";
-import "./Votes.scss";
-import classNames from "classnames";
 import {VFC} from "react";
+import classNames from "classnames";
+import _ from "underscore";
 import {TabIndex} from "constants/tabIndex";
+import {useAppSelector} from "store";
 import {VoteButtons} from "./VoteButtons";
+import "./Votes.scss";
 
 type VotesProps = {
   className?: string;
   noteId: string;
+  // Aggregate the votes of the child notes
+  aggregateVotes?: boolean;
   tabIndex?: number;
 };
 
 export const Votes: VFC<VotesProps> = (props) => {
   const voting = useAppSelector((state) => state.votings.open);
-  const currentUserVotes = useAppSelector(
-    (state) => state.votes.filter((v) => v.voting === state.votings.open?.id || v.voting === state.board.data?.showVoting).filter((v) => v.note === props.noteId).length
+  const ongoingVotes = useAppSelector(
+    (state) => ({
+      note: state.votes.filter((v) => v.voting === state.votings.open?.id || v.voting === state.board.data?.showVoting).filter((v) => v.note === props.noteId).length,
+      total: state.votes.filter((v) => v.voting === state.votings.open?.id || v.voting === state.board.data?.showVoting).length,
+    }),
+    _.isEqual
   );
   const allPastVotes = useAppSelector(
     (state) =>
       (state.votings.past[0]?.votes?.votesPerNote[props.noteId]?.total ?? 0) +
-      state.notes.filter((n) => n.position.stack === props.noteId).reduce((sum, curr) => sum + (state.votings.past[0]?.votes?.votesPerNote[curr.id]?.total ?? 0), 0)
+      (props.aggregateVotes
+        ? state.notes.filter((n) => n.position.stack === props.noteId).reduce((sum, curr) => sum + (state.votings.past[0]?.votes?.votesPerNote[curr.id]?.total ?? 0), 0)
+        : 0)
   );
-  const showAddVoteButton = voting && (voting?.allowMultipleVotes || currentUserVotes === 0);
 
   return (
-    <div className={classNames("votes", props.className)}>
-      {voting && currentUserVotes > 0 && (
-        <VoteButtons.Remove {...props} activeVoting={!!voting} tabIndex={props.tabIndex ? props.tabIndex + 1 : TabIndex.default} ownVotes={currentUserVotes} votes={allPastVotes} />
-      )}
+    <div role="none" className={classNames("votes", props.className)} onClick={(e) => e.stopPropagation()}>
       {!voting && allPastVotes > 0 && (
-        <VoteButtons.Remove {...props} activeVoting={!!voting} tabIndex={props.tabIndex ? props.tabIndex + 1 : TabIndex.default} ownVotes={currentUserVotes} votes={allPastVotes} />
+        <VoteButtons.Remove noteId={props.noteId} tabIndex={props.tabIndex ? props.tabIndex + 1 : TabIndex.default} disabled>
+          {allPastVotes}
+        </VoteButtons.Remove>
+      )}
+      {voting && ongoingVotes.note > 0 && (
+        <VoteButtons.Remove noteId={props.noteId} tabIndex={props.tabIndex ? props.tabIndex + 1 : TabIndex.default}>
+          {ongoingVotes.note}
+        </VoteButtons.Remove>
+      )}
+      {voting && (
+        <VoteButtons.Add
+          noteId={props.noteId}
+          tabIndex={props.tabIndex ? props.tabIndex + 2 : TabIndex.default}
+          disabled={ongoingVotes.total === voting.voteLimit || (ongoingVotes.note > 0 && !voting.allowMultipleVotes)}
+        />
       )}
-      {showAddVoteButton && <VoteButtons.Add {...props} tabIndex={props.tabIndex ? props.tabIndex + 2 : TabIndex.default} disabled={currentUserVotes === voting?.voteLimit} />}
     </div>
   );
 };
diff --git a/src/components/VotingDialog/VotingDialog.tsx b/src/components/VotingDialog/VotingDialog.tsx
index 3573bfa1..28b2070a 100644
--- a/src/components/VotingDialog/VotingDialog.tsx
+++ b/src/components/VotingDialog/VotingDialog.tsx
@@ -14,7 +14,7 @@ export const VotingDialog: VFC = () => {
   const navigate = useNavigate();
   const isAdmin = useAppSelector((state) => state.participants?.self.role === "OWNER" || state.participants?.self.role === "MODERATOR");
   const voting = useAppSelector((state) => state.votings.open?.id);
-  const [allowCumulativeVoting, setAllowCumulativeVoting] = useState(false);
+  const [allowCumulativeVoting, setAllowCumulativeVoting] = useState(true);
   // const [anonymousVoting, setAnonymousVoting] = useState(false);
   const [numberOfVotes, setNumberOfVotes] = useState(5);
   const [startPositionX, setStartPositionX] = useState(0);


Test patch:
diff --git a/src/components/Note/__tests__/__snapshots__/Note.test.tsx.snap b/src/components/Note/__tests__/__snapshots__/Note.test.tsx.snap
index 4305f91b..ea7a0ce7 100644
--- a/src/components/Note/__tests__/__snapshots__/Note.test.tsx.snap
+++ b/src/components/Note/__tests__/__snapshots__/Note.test.tsx.snap
@@ -466,9 +466,10 @@ exports[`Note Test NoteDialog created/not created NoteDialog is not present: sna
         </figure>
         <div
           class="votes"
+          role="none"
         >
           <button
-            class="dot-button vote-button-remove vote-button-remove--own-vote"
+            class="dot-button vote-button-remove"
             tabindex="0"
           >
             <span
@@ -478,6 +479,17 @@ exports[`Note Test NoteDialog created/not created NoteDialog is not present: sna
               1
             </span>
           </button>
+          <button
+            class="dot-button vote-button-add"
+            disabled=""
+            tabindex="0"
+          >
+            <svg
+              class="vote-button-add__icon"
+            >
+              icon-add.svg
+            </svg>
+          </button>
         </div>
       </div>
     </div>
@@ -978,9 +990,10 @@ exports[`Note Test NoteDialog created/not created NoteDialog is present: snapsho
                     >
                       <div
                         class="votes"
+                        role="none"
                       >
                         <button
-                          class="dot-button vote-button-remove vote-button-remove--own-vote"
+                          class="dot-button vote-button-remove"
                           tabindex="0"
                         >
                           <span
@@ -990,6 +1003,17 @@ exports[`Note Test NoteDialog created/not created NoteDialog is present: snapsho
                             1
                           </span>
                         </button>
+                        <button
+                          class="dot-button vote-button-add"
+                          disabled=""
+                          tabindex="0"
+                        >
+                          <svg
+                            class="vote-button-add__icon"
+                          >
+                            icon-add.svg
+                          </svg>
+                        </button>
                       </div>
                       <ul
                         class="note-dialog__note-options"
@@ -1471,9 +1495,10 @@ exports[`Note Test NoteDialog created/not created NoteDialog is present: snapsho
         </figure>
         <div
           class="votes"
+          role="none"
         >
           <button
-            class="dot-button vote-button-remove vote-button-remove--own-vote"
+            class="dot-button vote-button-remove"
             tabindex="0"
           >
             <span
@@ -1483,6 +1508,17 @@ exports[`Note Test NoteDialog created/not created NoteDialog is present: snapsho
               1
             </span>
           </button>
+          <button
+            class="dot-button vote-button-add"
+            disabled=""
+            tabindex="0"
+          >
+            <svg
+              class="vote-button-add__icon"
+            >
+              icon-add.svg
+            </svg>
+          </button>
         </div>
       </div>
     </div>
@@ -1953,9 +1989,10 @@ exports[`Note should have correct style show note with correct style 1`] = `
       </figure>
       <div
         class="votes"
+        role="none"
       >
         <button
-          class="dot-button vote-button-remove vote-button-remove--own-vote"
+          class="dot-button vote-button-remove"
           tabindex="0"
         >
           <span
@@ -1965,6 +2002,17 @@ exports[`Note should have correct style show note with correct style 1`] = `
             1
           </span>
         </button>
+        <button
+          class="dot-button vote-button-add"
+          disabled=""
+          tabindex="0"
+        >
+          <svg
+            class="vote-button-add__icon"
+          >
+            icon-add.svg
+          </svg>
+        </button>
       </div>
     </div>
   </div>
diff --git a/src/components/NoteDialog/__tests__/__snapshots__/NoteDialog.test.tsx.snap b/src/components/NoteDialog/__tests__/__snapshots__/NoteDialog.test.tsx.snap
index 39c933e0..2073109a 100644
--- a/src/components/NoteDialog/__tests__/__snapshots__/NoteDialog.test.tsx.snap
+++ b/src/components/NoteDialog/__tests__/__snapshots__/NoteDialog.test.tsx.snap
@@ -499,6 +499,7 @@ exports[`<NoteDialog/> should render correctly note-dialog match snapshot 1`] =
           >
             <div
               class="votes"
+              role="none"
             >
               <button
                 class="dot-button vote-button-add"
@@ -926,6 +927,7 @@ exports[`<NoteDialog/> should render correctly note-dialog match snapshot 1`] =
           >
             <div
               class="votes"
+              role="none"
             >
               <button
                 class="dot-button vote-button-add"
@@ -1369,6 +1371,7 @@ exports[`<NoteDialog/> should render correctly note-dialog match snapshot 1`] =
           >
             <div
               class="votes"
+              role="none"
             >
               <button
                 class="dot-button vote-button-add"
diff --git a/src/components/Votes/__tests__/Votes.test.tsx b/src/components/Votes/__tests__/Votes.test.tsx
index 2785a57d..df0c4076 100644
--- a/src/components/Votes/__tests__/Votes.test.tsx
+++ b/src/components/Votes/__tests__/Votes.test.tsx
@@ -81,15 +81,4 @@ describe("Votes", () => {
       expect(mockDispatchFn).toHaveBeenCalledWith(Actions.deleteVote("test-notes-id-1"));
     });
   });
-
-  describe("Test allowMultipleVotesPerNote works correctly", () => {
-    test("allowMultipleVotesPerNote: false", () => {
-      const {container} = render(createVotes());
-      expect(container.querySelector(".votes")?.firstChild).toHaveClass("vote-button-remove");
-      expect(container.querySelector(".votes")?.firstChild).toHaveClass("vote-button-remove--own-vote");
-      expect(container.querySelector(".vote-button-add")).toBeNull();
-      expect(container.querySelector(".votes")?.childElementCount).toEqual(1);
-      expect(container.querySelector(".vote-button-remove")?.firstChild).toHaveClass("vote-button-remove__folded-corner");
-    });
-  });
 });
diff --git a/src/components/Votes/__tests__/__snapshots__/Votes.test.tsx.snap b/src/components/Votes/__tests__/__snapshots__/Votes.test.tsx.snap
index ee2af492..402d6436 100644
--- a/src/components/Votes/__tests__/__snapshots__/Votes.test.tsx.snap
+++ b/src/components/Votes/__tests__/__snapshots__/Votes.test.tsx.snap
@@ -4,6 +4,7 @@ exports[`Votes should render correctly with no votes and active voting 1`] = `
 <div>
   <div
     class="votes"
+    role="none"
   >
     <button
       class="dot-button vote-button-add"
@@ -23,6 +24,7 @@ exports[`Votes should render correctly with no votes and disabled voting 1`] = `
 <div>
   <div
     class="votes"
+    role="none"
   />
 </div>
 `;
@@ -31,9 +33,10 @@ exports[`Votes should render correctly with votes and active voting 1`] = `
 <div>
   <div
     class="votes"
+    role="none"
   >
     <button
-      class="dot-button vote-button-remove vote-button-remove--own-vote"
+      class="dot-button vote-button-remove"
       tabindex="0"
     >
       <span
@@ -43,6 +46,17 @@ exports[`Votes should render correctly with votes and active voting 1`] = `
         1
       </span>
     </button>
+    <button
+      class="dot-button vote-button-add"
+      disabled=""
+      tabindex="0"
+    >
+      <svg
+        class="vote-button-add__icon"
+      >
+        icon-add.svg
+      </svg>
+    </button>
   </div>
 </div>
 `;
@@ -51,6 +65,7 @@ exports[`Votes should render correctly with votes and disabled voting 1`] = `
 <div>
   <div
     class="votes"
+    role="none"
   >
     <button
       class="dot-button vote-button-remove"
diff --git a/src/components/VotingDialog/__tests__/__snapshots__/VotingDialog.test.tsx.snap b/src/components/VotingDialog/__tests__/__snapshots__/VotingDialog.test.tsx.snap
index 620919fd..0f5cd512 100644
--- a/src/components/VotingDialog/__tests__/__snapshots__/VotingDialog.test.tsx.snap
+++ b/src/components/VotingDialog/__tests__/__snapshots__/VotingDialog.test.tsx.snap
@@ -108,7 +108,7 @@ exports[`VotingDialog should match the snapshot with no active voting 1`] = `
                 Allow cumulative voting
               </label>
               <div
-                class="toggle voting-dialog__toggle"
+                class="toggle toggle--active voting-dialog__toggle"
               />
             </button>
             <div