feat: add filter by list (status) in list view
Adds a per-list filter to the board action bar, visible only in List View. Mirrors the existing transient label/member filter pattern: filterLists is a many-relation on the Board ORM model, toggled via entry actions and reset on board navigation. Empty selection keeps the current behavior; non-empty selection shows only cards whose listId is in the chosen set. Filter UI is hidden in Kanban/Grid views where columns already convey the same info. Closes #1524
This commit is contained in:
@@ -201,6 +201,22 @@ const handleListDelete = (list, cards) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addListToBoardFilter = (id, boardId) => ({
|
||||||
|
type: ActionTypes.LIST_TO_BOARD_FILTER_ADD,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
boardId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const removeListFromBoardFilter = (id, boardId) => ({
|
||||||
|
type: ActionTypes.LIST_FROM_BOARD_FILTER_REMOVE,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
boardId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createList,
|
createList,
|
||||||
handleListCreate,
|
handleListCreate,
|
||||||
@@ -212,4 +228,6 @@ export default {
|
|||||||
handleListClear,
|
handleListClear,
|
||||||
deleteList,
|
deleteList,
|
||||||
handleListDelete,
|
handleListDelete,
|
||||||
|
addListToBoardFilter,
|
||||||
|
removeListFromBoardFilter,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -16,17 +17,45 @@ import { Input } from '../../../lib/custom-ui';
|
|||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import entryActions from '../../../entry-actions';
|
import entryActions from '../../../entry-actions';
|
||||||
import { useNestedRef } from '../../../hooks';
|
import { useNestedRef } from '../../../hooks';
|
||||||
|
import { BoardViews } from '../../../constants/Enums';
|
||||||
import UserAvatar from '../../users/UserAvatar';
|
import UserAvatar from '../../users/UserAvatar';
|
||||||
import BoardMembershipsStep from '../../board-memberships/BoardMembershipsStep';
|
import BoardMembershipsStep from '../../board-memberships/BoardMembershipsStep';
|
||||||
import LabelChip from '../../labels/LabelChip';
|
import LabelChip from '../../labels/LabelChip';
|
||||||
import LabelsStep from '../../labels/LabelsStep';
|
import LabelsStep from '../../labels/LabelsStep';
|
||||||
|
import ListsFilterStep from '../../lists/ListsFilterStep';
|
||||||
|
|
||||||
import styles from './Filters.module.scss';
|
import styles from './Filters.module.scss';
|
||||||
|
|
||||||
|
const FilterListChip = React.memo(({ id, onClick }) => {
|
||||||
|
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
|
||||||
|
const list = useSelector((state) => selectListById(state, id));
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
onClick(id);
|
||||||
|
}, [id, onClick]);
|
||||||
|
|
||||||
|
if (!list) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button type="button" className={styles.filterButton} onClick={handleClick}>
|
||||||
|
<span className={styles.filterLabel}>{list.name || t(`common.${list.type}`)}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
FilterListChip.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
const Filters = React.memo(() => {
|
const Filters = React.memo(() => {
|
||||||
const board = useSelector(selectors.selectCurrentBoard);
|
const board = useSelector(selectors.selectCurrentBoard);
|
||||||
const userIds = useSelector(selectors.selectFilterUserIdsForCurrentBoard);
|
const userIds = useSelector(selectors.selectFilterUserIdsForCurrentBoard);
|
||||||
const labelIds = useSelector(selectors.selectFilterLabelIdsForCurrentBoard);
|
const labelIds = useSelector(selectors.selectFilterLabelIdsForCurrentBoard);
|
||||||
|
const listIds = useSelector(selectors.selectFilterListIdsForCurrentBoard);
|
||||||
const currentUserId = useSelector(selectors.selectCurrentUserId);
|
const currentUserId = useSelector(selectors.selectCurrentUserId);
|
||||||
|
|
||||||
const withCurrentUserSelector = useSelector(
|
const withCurrentUserSelector = useSelector(
|
||||||
@@ -98,6 +127,20 @@ const Filters = React.memo(() => {
|
|||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleListSelect = useCallback(
|
||||||
|
(listId) => {
|
||||||
|
dispatch(entryActions.addListToFilterInCurrentBoard(listId));
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleListDeselect = useCallback(
|
||||||
|
(listId) => {
|
||||||
|
dispatch(entryActions.removeListFromFilterInCurrentBoard(listId));
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
const handleLabelClick = useCallback(
|
const handleLabelClick = useCallback(
|
||||||
({
|
({
|
||||||
currentTarget: {
|
currentTarget: {
|
||||||
@@ -144,8 +187,10 @@ const Filters = React.memo(() => {
|
|||||||
|
|
||||||
const BoardMembershipsPopup = usePopup(BoardMembershipsStep);
|
const BoardMembershipsPopup = usePopup(BoardMembershipsStep);
|
||||||
const LabelsPopup = usePopup(LabelsStep);
|
const LabelsPopup = usePopup(LabelsStep);
|
||||||
|
const ListsFilterPopup = usePopup(ListsFilterStep);
|
||||||
|
|
||||||
const isSearchActive = search || isSearchFocused;
|
const isSearchActive = search || isSearchFocused;
|
||||||
|
const isListView = board.view === BoardViews.LIST;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -192,6 +237,26 @@ const Filters = React.memo(() => {
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
|
{isListView && (
|
||||||
|
<span className={styles.filter}>
|
||||||
|
<ListsFilterPopup
|
||||||
|
currentIds={listIds}
|
||||||
|
title="common.filterByLists"
|
||||||
|
onSelect={handleListSelect}
|
||||||
|
onDeselect={handleListDeselect}
|
||||||
|
>
|
||||||
|
<button type="button" className={styles.filterButton}>
|
||||||
|
<span className={styles.filterTitle}>{`${t('common.lists')}:`}</span>
|
||||||
|
{listIds.length === 0 && (
|
||||||
|
<span className={styles.filterLabel}>{t('common.all')}</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</ListsFilterPopup>
|
||||||
|
{listIds.map((listId) => (
|
||||||
|
<FilterListChip key={listId} id={listId} onClick={handleListDeselect} />
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<span className={styles.filter}>
|
<span className={styles.filter}>
|
||||||
<Input
|
<Input
|
||||||
ref={handleSearchFieldRef}
|
ref={handleSearchFieldRef}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Icon } from 'semantic-ui-react';
|
||||||
|
|
||||||
|
import selectors from '../../../selectors';
|
||||||
|
import { ListTypes } from '../../../constants/Enums';
|
||||||
|
import { ListTypeIcons } from '../../../constants/Icons';
|
||||||
|
|
||||||
|
import styles from './Item.module.scss';
|
||||||
|
|
||||||
|
const Item = React.memo(({ id, isActive, onSelect, onDeselect }) => {
|
||||||
|
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
|
||||||
|
|
||||||
|
const list = useSelector((state) => selectListById(state, id));
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
const handleToggleClick = useCallback(() => {
|
||||||
|
if (!list.isPersisted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
onDeselect(id);
|
||||||
|
} else {
|
||||||
|
onSelect(id);
|
||||||
|
}
|
||||||
|
}, [id, isActive, onSelect, onDeselect, list.isPersisted]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
||||||
|
jsx-a11y/no-static-element-interactions */}
|
||||||
|
<span
|
||||||
|
className={classNames(styles.name, isActive && styles.nameActive)}
|
||||||
|
onClick={handleToggleClick}
|
||||||
|
>
|
||||||
|
{list.type !== ListTypes.ACTIVE && (
|
||||||
|
<Icon name={ListTypeIcons[list.type]} className={styles.nameIcon} />
|
||||||
|
)}
|
||||||
|
{list.name || t(`common.${list.type}`)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Item.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
onDeselect: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Item;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
:global(#app) {
|
||||||
|
.name {
|
||||||
|
background: rgba(9, 30, 66, 0.04);
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #17394d;
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 8px 32px 8px 10px;
|
||||||
|
position: relative;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(9, 30, 66, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameActive {
|
||||||
|
opacity: 0.45;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
bottom: 1px;
|
||||||
|
content: "Г";
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 36px;
|
||||||
|
position: absolute;
|
||||||
|
right: 2px;
|
||||||
|
text-align: center;
|
||||||
|
transform: rotate(-135deg);
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameIcon {
|
||||||
|
color: rgba(9, 30, 66, 0.24);
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 0 8px 0 0;
|
||||||
|
width: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
max-width: 280px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Input, Popup } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
|
import selectors from '../../../selectors';
|
||||||
|
import { useField, useNestedRef } from '../../../hooks';
|
||||||
|
import Item from './Item';
|
||||||
|
|
||||||
|
import styles from './ListsFilterStep.module.scss';
|
||||||
|
|
||||||
|
const ListsFilterStep = React.memo(({ currentIds, title, onSelect, onDeselect, onBack }) => {
|
||||||
|
const lists = useSelector(selectors.selectAvailableListsForCurrentBoard);
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const [search, handleSearchChange] = useField('');
|
||||||
|
const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);
|
||||||
|
|
||||||
|
const filteredLists = useMemo(
|
||||||
|
() =>
|
||||||
|
lists.filter((list) =>
|
||||||
|
(list.name ? list.name.toLowerCase() : list.type).includes(cleanSearch),
|
||||||
|
),
|
||||||
|
[lists, cleanSearch],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
searchFieldRef.current.focus({
|
||||||
|
preventScroll: true,
|
||||||
|
});
|
||||||
|
}, [searchFieldRef]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Popup.Header onBack={onBack}>
|
||||||
|
{t(title, {
|
||||||
|
context: 'title',
|
||||||
|
})}
|
||||||
|
</Popup.Header>
|
||||||
|
<Popup.Content>
|
||||||
|
<Input
|
||||||
|
fluid
|
||||||
|
ref={handleSearchFieldRef}
|
||||||
|
value={search}
|
||||||
|
placeholder={t('common.searchLists')}
|
||||||
|
maxLength={128}
|
||||||
|
icon="search"
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
{filteredLists.length > 0 && (
|
||||||
|
<div className={styles.items}>
|
||||||
|
{filteredLists.map((list) => (
|
||||||
|
<Item
|
||||||
|
key={list.id}
|
||||||
|
id={list.id}
|
||||||
|
isActive={currentIds.includes(list.id)}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onDeselect={onDeselect}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Popup.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ListsFilterStep.propTypes = {
|
||||||
|
currentIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
title: PropTypes.string,
|
||||||
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
onDeselect: PropTypes.func.isRequired,
|
||||||
|
onBack: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
ListsFilterStep.defaultProps = {
|
||||||
|
title: 'common.filterByLists',
|
||||||
|
onBack: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListsFilterStep;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
:global(#app) {
|
||||||
|
.items {
|
||||||
|
margin-top: 8px;
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
@supports (-moz-appearance: none) {
|
||||||
|
scrollbar-color: rgba(0, 0, 0, 0.32) transparent;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-clip: padding-box;
|
||||||
|
border-left: 0.25em transparent solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ListsFilterStep from './ListsFilterStep';
|
||||||
|
|
||||||
|
export default ListsFilterStep;
|
||||||
@@ -272,6 +272,8 @@ export default {
|
|||||||
LIST_DELETE__SUCCESS: 'LIST_DELETE__SUCCESS',
|
LIST_DELETE__SUCCESS: 'LIST_DELETE__SUCCESS',
|
||||||
LIST_DELETE__FAILURE: 'LIST_DELETE__FAILURE',
|
LIST_DELETE__FAILURE: 'LIST_DELETE__FAILURE',
|
||||||
LIST_DELETE_HANDLE: 'LIST_DELETE_HANDLE',
|
LIST_DELETE_HANDLE: 'LIST_DELETE_HANDLE',
|
||||||
|
LIST_TO_BOARD_FILTER_ADD: 'LIST_TO_BOARD_FILTER_ADD',
|
||||||
|
LIST_FROM_BOARD_FILTER_REMOVE: 'LIST_FROM_BOARD_FILTER_REMOVE',
|
||||||
|
|
||||||
/* Cards */
|
/* Cards */
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ export default {
|
|||||||
LIST_CLEAR_HANDLE: `${PREFIX}/LIST_CLEAR_HANDLE`,
|
LIST_CLEAR_HANDLE: `${PREFIX}/LIST_CLEAR_HANDLE`,
|
||||||
LIST_DELETE: `${PREFIX}/LIST_DELETE`,
|
LIST_DELETE: `${PREFIX}/LIST_DELETE`,
|
||||||
LIST_DELETE_HANDLE: `${PREFIX}/LIST_DELETE_HANDLE`,
|
LIST_DELETE_HANDLE: `${PREFIX}/LIST_DELETE_HANDLE`,
|
||||||
|
LIST_TO_FILTER_IN_CURRENT_BOARD_ADD: `${PREFIX}/LIST_TO_FILTER_IN_CURRENT_BOARD_ADD`,
|
||||||
|
LIST_FROM_FILTER_IN_CURRENT_BOARD_REMOVE: `${PREFIX}/LIST_FROM_FILTER_IN_CURRENT_BOARD_REMOVE`,
|
||||||
|
|
||||||
/* Cards */
|
/* Cards */
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,20 @@ const handleListDelete = (list, cards) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addListToFilterInCurrentBoard = (id) => ({
|
||||||
|
type: EntryActionTypes.LIST_TO_FILTER_IN_CURRENT_BOARD_ADD,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const removeListFromFilterInCurrentBoard = (id) => ({
|
||||||
|
type: EntryActionTypes.LIST_FROM_FILTER_IN_CURRENT_BOARD_REMOVE,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createListInCurrentBoard,
|
createListInCurrentBoard,
|
||||||
handleListCreate,
|
handleListCreate,
|
||||||
@@ -108,4 +122,6 @@ export default {
|
|||||||
handleListClear,
|
handleListClear,
|
||||||
deleteList,
|
deleteList,
|
||||||
handleListDelete,
|
handleListDelete,
|
||||||
|
addListToFilterInCurrentBoard,
|
||||||
|
removeListFromFilterInCurrentBoard,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ export default {
|
|||||||
excludedEvents: 'Excluded events',
|
excludedEvents: 'Excluded events',
|
||||||
expandTaskListsByDefault: 'Expand task lists by default',
|
expandTaskListsByDefault: 'Expand task lists by default',
|
||||||
filterByLabels_title: 'Filter By Labels',
|
filterByLabels_title: 'Filter By Labels',
|
||||||
|
filterByLists_title: 'Filter By Lists',
|
||||||
filterByMembers_title: 'Filter By Members',
|
filterByMembers_title: 'Filter By Members',
|
||||||
forPersonalProjects: 'For personal projects.',
|
forPersonalProjects: 'For personal projects.',
|
||||||
forTeamBasedProjects: 'For team-based projects.',
|
forTeamBasedProjects: 'For team-based projects.',
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export default class extends BaseModel {
|
|||||||
}),
|
}),
|
||||||
filterUsers: many('User', 'filterBoards'),
|
filterUsers: many('User', 'filterBoards'),
|
||||||
filterLabels: many('Label', 'filterBoards'),
|
filterLabels: many('Label', 'filterBoards'),
|
||||||
|
filterLists: many('List', 'filterBoards'),
|
||||||
};
|
};
|
||||||
|
|
||||||
static reducer({ type, payload }, Board) {
|
static reducer({ type, payload }, Board) {
|
||||||
@@ -253,6 +254,14 @@ export default class extends BaseModel {
|
|||||||
case ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE:
|
case ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE:
|
||||||
Board.withId(payload.boardId).filterLabels.remove(payload.id);
|
Board.withId(payload.boardId).filterLabels.remove(payload.id);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ActionTypes.LIST_TO_BOARD_FILTER_ADD:
|
||||||
|
Board.withId(payload.boardId).filterLists.add(payload.id);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ActionTypes.LIST_FROM_BOARD_FILTER_REMOVE:
|
||||||
|
Board.withId(payload.boardId).filterLists.remove(payload.id);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ActionTypes.ACTIVITIES_IN_BOARD_FETCH:
|
case ActionTypes.ACTIVITIES_IN_BOARD_FETCH:
|
||||||
Board.withId(payload.boardId).update({
|
Board.withId(payload.boardId).update({
|
||||||
@@ -389,6 +398,12 @@ export default class extends BaseModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filterListIds = this.filterLists.toRefArray().map((list) => list.id);
|
||||||
|
|
||||||
|
if (filterListIds.length > 0) {
|
||||||
|
cardModels = cardModels.filter((cardModel) => filterListIds.includes(cardModel.listId));
|
||||||
|
}
|
||||||
|
|
||||||
return cardModels;
|
return cardModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,6 +459,7 @@ export default class extends BaseModel {
|
|||||||
deleteClearable() {
|
deleteClearable() {
|
||||||
this.filterUsers.clear();
|
this.filterUsers.clear();
|
||||||
this.filterLabels.clear();
|
this.filterLabels.clear();
|
||||||
|
this.filterLists.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRelated(exceptMemberUserId, soft) {
|
deleteRelated(exceptMemberUserId, soft) {
|
||||||
|
|||||||
@@ -443,6 +443,13 @@ export default class extends BaseModel {
|
|||||||
|
|
||||||
deleteWithRelated(soft) {
|
deleteWithRelated(soft) {
|
||||||
this.deleteRelated(soft);
|
this.deleteRelated(soft);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.board.filterLists.remove(this.id);
|
||||||
|
} catch {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
|
||||||
this.delete();
|
this.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,6 +261,26 @@ export function* handleListDelete(list, cards) {
|
|||||||
yield put(actions.handleListDelete(list, cards));
|
yield put(actions.handleListDelete(list, cards));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* addListToBoardFilter(id, boardId) {
|
||||||
|
yield put(actions.addListToBoardFilter(id, boardId));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* addListToFilterInCurrentBoard(id) {
|
||||||
|
const { boardId } = yield select(selectors.selectPath);
|
||||||
|
|
||||||
|
yield call(addListToBoardFilter, id, boardId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* removeListFromBoardFilter(id, boardId) {
|
||||||
|
yield put(actions.removeListFromBoardFilter(id, boardId));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* removeListFromFilterInCurrentBoard(id) {
|
||||||
|
const { boardId } = yield select(selectors.selectPath);
|
||||||
|
|
||||||
|
yield call(removeListFromBoardFilter, id, boardId);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createList,
|
createList,
|
||||||
createListInCurrentBoard,
|
createListInCurrentBoard,
|
||||||
@@ -275,4 +295,8 @@ export default {
|
|||||||
handleListClear,
|
handleListClear,
|
||||||
deleteList,
|
deleteList,
|
||||||
handleListDelete,
|
handleListDelete,
|
||||||
|
addListToBoardFilter,
|
||||||
|
addListToFilterInCurrentBoard,
|
||||||
|
removeListFromBoardFilter,
|
||||||
|
removeListFromFilterInCurrentBoard,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,5 +44,11 @@ export default function* listsWatchers() {
|
|||||||
takeEvery(EntryActionTypes.LIST_DELETE_HANDLE, ({ payload: { list, cards } }) =>
|
takeEvery(EntryActionTypes.LIST_DELETE_HANDLE, ({ payload: { list, cards } }) =>
|
||||||
services.handleListDelete(list, cards),
|
services.handleListDelete(list, cards),
|
||||||
),
|
),
|
||||||
|
takeEvery(EntryActionTypes.LIST_TO_FILTER_IN_CURRENT_BOARD_ADD, ({ payload: { id } }) =>
|
||||||
|
services.addListToFilterInCurrentBoard(id),
|
||||||
|
),
|
||||||
|
takeEvery(EntryActionTypes.LIST_FROM_FILTER_IN_CURRENT_BOARD_REMOVE, ({ payload: { id } }) =>
|
||||||
|
services.removeListFromFilterInCurrentBoard(id),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -459,6 +459,24 @@ export const selectFilterLabelIdsForCurrentBoard = createSelector(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectFilterListIdsForCurrentBoard = createSelector(
|
||||||
|
orm,
|
||||||
|
(state) => selectPath(state).boardId,
|
||||||
|
({ Board }, id) => {
|
||||||
|
if (!id) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boardModel = Board.withId(id);
|
||||||
|
|
||||||
|
if (!boardModel) {
|
||||||
|
return boardModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return boardModel.filterLists.toRefArray().map((list) => list.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const selectIsBoardWithIdExists = createSelector(
|
export const selectIsBoardWithIdExists = createSelector(
|
||||||
orm,
|
orm,
|
||||||
(_, id) => id,
|
(_, id) => id,
|
||||||
@@ -491,5 +509,6 @@ export default {
|
|||||||
selectActivityIdsForCurrentBoard,
|
selectActivityIdsForCurrentBoard,
|
||||||
selectFilterUserIdsForCurrentBoard,
|
selectFilterUserIdsForCurrentBoard,
|
||||||
selectFilterLabelIdsForCurrentBoard,
|
selectFilterLabelIdsForCurrentBoard,
|
||||||
|
selectFilterListIdsForCurrentBoard,
|
||||||
selectIsBoardWithIdExists,
|
selectIsBoardWithIdExists,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user