diff --git a/client/app/components/Backlog/BacklogActive.js b/client/app/components/Backlog/BacklogActive.js index be10e4fb..c1f71ec9 100644 --- a/client/app/components/Backlog/BacklogActive.js +++ b/client/app/components/Backlog/BacklogActive.js @@ -6,15 +6,15 @@ import {useDrop} from 'react-dnd'; import {trashStories, setSortOrder} from '../../state/actions/commandActions'; import {getActiveStories, getSelectedStoryId} from '../../state/stories/storiesSelectors'; import {L10nContext} from '../../services/l10n'; -import {manualSorting} from './backlogSortings'; +import {manualSorting} from '../common/storySortings'; import StoryEditForm from './StoryEditForm'; import Story from './Story'; import BacklogToolbar from './BacklogToolbar'; import {DRAG_ITEM_TYPES} from '../Room/Board'; import BacklogFileDropWrapper from './BacklogFileDropWrapper'; +import useStorySortingAndFiltering from './useStorySortingAndFiltering'; import {StyledStories, StyledBacklogInfoText, StyledBacklogActive} from './_styled'; -import useStorySortingAndFiltering from '../common/useStorySortingAndFiltering'; /** * diff --git a/client/app/components/Backlog/BacklogToolbar.js b/client/app/components/Backlog/BacklogToolbar.js index f314e849..5234e981 100644 --- a/client/app/components/Backlog/BacklogToolbar.js +++ b/client/app/components/Backlog/BacklogToolbar.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import {L10nContext} from '../../services/l10n'; import useOutsideClick from '../common/useOutsideClick'; -import sortings from './backlogSortings'; +import sortings from '../common/storySortings'; import {OpenFileDialogContext} from './BacklogFileDropWrapper'; import {StyledDropdown} from '../common/_styled'; diff --git a/client/app/components/common/useStorySortingAndFiltering.js b/client/app/components/Backlog/useStorySortingAndFiltering.js similarity index 94% rename from client/app/components/common/useStorySortingAndFiltering.js rename to client/app/components/Backlog/useStorySortingAndFiltering.js index e9eafe87..6a46cd90 100644 --- a/client/app/components/common/useStorySortingAndFiltering.js +++ b/client/app/components/Backlog/useStorySortingAndFiltering.js @@ -1,5 +1,5 @@ import {useState, useEffect} from 'react'; -import {defaultSorting} from '../Backlog/backlogSortings'; +import {defaultSorting} from '../common/storySortings'; /** * diff --git a/client/app/components/EstimationMatrix/EstimationMatrix.js b/client/app/components/EstimationMatrix/EstimationMatrix.js index 044c5233..dab91840 100644 --- a/client/app/components/EstimationMatrix/EstimationMatrix.js +++ b/client/app/components/EstimationMatrix/EstimationMatrix.js @@ -1,4 +1,4 @@ -import React, {useContext, useState, useEffect} from 'react'; +import React, {useContext, useState, useEffect, useRef} from 'react'; import {connect} from 'react-redux'; import PropTypes from 'prop-types'; @@ -13,13 +13,20 @@ import {L10nContext} from '../../services/l10n'; import {toggleMatrixIncludeTrashed} from '../../state/actions/uiStateActions'; import {setStoryValue} from '../../state/actions/commandActions'; import EstimationMatrixColumn from './EstimationMatrixColumn'; - -import {StyledEMColumnsContainer, StyledEstimationMatrix, StyledNoStoriesHint} from './_styled'; -import {StyledStoryTitle} from '../_styled'; import {getStoriesWithPendingSetValueCommand} from '../../state/commandTracking/commandTrackingSelectors'; import EstimationMatrixColumnUnestimated from './EstimationMatrixColumnUnestimated'; -import useStorySortingAndFiltering from '../common/useStorySortingAndFiltering'; -import {defaultSorting} from '../Backlog/backlogSortings'; +import {defaultSorting, matrixSortings} from '../common/storySortings'; +import useOutsideClick from '../common/useOutsideClick'; + +import { + StyledEMColumnsContainer, + StyledEstimationMatrix, + StyledMatrixHeader, + StyledMatrixTools, + StyledNoStoriesHint +} from './_styled'; +import {StyledDropdown} from '../common/_styled'; +import {StyledSortDropdownItem} from '../Backlog/_styled'; /** * Displays a table with all estimated stories (all stories with consensus), ordered by estimation value @@ -36,24 +43,56 @@ const EstimationMatrix = ({ const columnWidth = getColWidth(cardConfig.length + 1); const {t} = useContext(L10nContext); const [groupedStories, setGroupedStories] = useState({}); // grouped by consensus value + const [sortedUnestimatedStories, setSortedUnestimatedStories] = useState(unestimatedStories); + const [extendedSort, setExtendedSort] = useState(false); + const [sorting, setSorting] = useState(defaultSorting); - const {sortedStories: sortedUnestimatedStories} = useStorySortingAndFiltering(unestimatedStories); + useEffect(() => { + setGroupedStories(deriveGroupedStories(estimatedStories, pendingSetValueStories, sorting)); + }, [estimatedStories, pendingSetValueStories, sorting]); useEffect(() => { - setGroupedStories( - deriveGroupedStories(estimatedStories, pendingSetValueStories, defaultSorting) - ); - }, [estimatedStories, pendingSetValueStories]); + setSortedUnestimatedStories(unestimatedStories.sort(sorting.comp)); + }, [unestimatedStories, sorting, setSortedUnestimatedStories]); + + const sortDropdownRef = useRef(null); + useOutsideClick(sortDropdownRef, () => setExtendedSort(false)); return ( - - {t('matrix')} - - {' '} - {t('matrixIncludeTrashed')} - - + +

{t('matrix')}

+ + + + {' '} + {t('matrixIncludeTrashed')} + + +
+ setExtendedSort(!extendedSort)} + className="clickable icon-exchange" + title={t('sort')} + data-testid="sortButton" + /> + {extendedSort && ( + + {matrixSortings.map((sortingItem) => ( + onSortingOptionClicked(sortingItem)} + > + {t(sortingItem.labelKey)} + + ))} + + )} +
+
+
); + + function onSortingOptionClicked(sortingItem) { + setExtendedSort(false); + setSorting(sortingItem); + } }; EstimationMatrix.propTypes = { @@ -134,9 +178,9 @@ function deriveGroupedStories(estimatedStories, pendingSetValueStories, sorting) // now sort each story-array const groupedStoriesSorted = {}; - Object.entries(groupedStoriesUnsorted).forEach(([consensus, stories]) => { - groupedStoriesSorted[consensus] = stories.sort(sorting.comp); - }); + Object.entries(groupedStoriesUnsorted).forEach( + ([consensus, stories]) => (groupedStoriesSorted[consensus] = stories.sort(sorting.comp)) + ); return groupedStoriesSorted; } diff --git a/client/app/components/EstimationMatrix/_styled.js b/client/app/components/EstimationMatrix/_styled.js index 5eb4312b..4d56c95d 100644 --- a/client/app/components/EstimationMatrix/_styled.js +++ b/client/app/components/EstimationMatrix/_styled.js @@ -94,3 +94,30 @@ export const StyledNoStoriesHint = styled.div` justify-content: center; align-items: center; `; + +export const StyledMatrixHeader = styled.div` + position: relative; + display: flex; + justify-content: space-between; + + > h4 { + margin-top: 8px; + margin-bottom: 0; + line-height: 24px; + overflow-x: hidden; + } +`; + +export const StyledMatrixTools = styled.div` + display: flex; + + > div { + margin-left: 8px; + } + + i.icon-exchange { + margin-left: 4px; + margin-right: 8px; + transform: rotate(90deg); + } +`; diff --git a/client/app/components/Backlog/backlogSortings.js b/client/app/components/common/storySortings.js similarity index 93% rename from client/app/components/Backlog/backlogSortings.js rename to client/app/components/common/storySortings.js index 178c680b..5a73048e 100644 --- a/client/app/components/Backlog/backlogSortings.js +++ b/client/app/components/common/storySortings.js @@ -74,3 +74,5 @@ export default sortings; export const defaultSorting = sortings[0]; export const manualSorting = sortings[6]; + +export const matrixSortings = sortings.slice(0, 4); // in the estimation matrix, we want to provide only a reduced set of sorting options