diff --git a/client/src/app.tsx b/client/src/app.tsx index 7c03b22..4784e15 100644 --- a/client/src/app.tsx +++ b/client/src/app.tsx @@ -46,6 +46,7 @@ import { getStatusFilter, getViewFilter, getViewStatuses, + hasNonViewFilters as doesHaveNonViewFilters, } from './filters'; type AppProps = { @@ -95,6 +96,7 @@ const App = ({rep, undoManager}: AppProps) => { const [createdFilter] = useCreatedFilterState(); const [creatorFilter] = useCreatorFilterState(); const [modifiedFilter] = useModifiedFilterState(); + const [hasNonViewFilters, setHasNonViewFilters] = useState(false); const [issueOrder, setIssueOrder] = useState(getIssueOrder(view, orderBy)); @@ -104,14 +106,21 @@ const App = ({rep, undoManager}: AppProps) => { const viewStatuses = getViewStatuses(view); const statuses = getStatuses(statusFilter); + const statusFilterFn = getStatusFilter(viewStatuses, statuses); const filterFns = [ - getStatusFilter(viewStatuses, statuses), + statusFilterFn, getPriorityFilter(getPriorities(priorityFilter)), getCreatorFilter(getCreators(creatorFilter)), getCreatedFilter(createdFilter), getModifiedFilter(modifiedFilter), ]; + const hasNonViewFilters = !!( + doesHaveNonViewFilters(viewStatuses, statuses) || + filterFns.filter(f => f !== null && f !== statusFilterFn).length > 0 + ); + setHasNonViewFilters(hasNonViewFilters); + let {stream} = source; for (const filter of filterFns) { if (filter !== null) { @@ -125,11 +134,11 @@ const App = ({rep, undoManager}: AppProps) => { }, [ view, issueOrder, - priorityFilter, - statusFilter, - createdFilter, - creatorFilter, - modifiedFilter, + priorityFilter?.join(), + statusFilter?.join(), + createdFilter?.join(), + creatorFilter?.join(), + modifiedFilter?.join(), ]); const [, viewIssueCount] = useQuery(() => { @@ -283,7 +292,7 @@ const App = ({rep, undoManager}: AppProps) => { viewIssueCount={viewIssueCount || 0} filteredIssues={filterdIssues} rep={rep} - hasNonViewFilters={false} + hasNonViewFilters={hasNonViewFilters} onCloseMenu={handleCloseMenu} onToggleMenu={handleToggleMenu} onUpdateIssues={handleUpdateIssues} diff --git a/client/src/filters.ts b/client/src/filters.ts index b0c1db2..937bbb4 100644 --- a/client/src/filters.ts +++ b/client/src/filters.ts @@ -8,7 +8,7 @@ export function hasNonViewFilters( statuses: Set, ) { for (const s of statuses) { - if (!viewStatuses.has(s)) { + if (viewStatuses.has(s)) { return true; } } @@ -50,11 +50,11 @@ export function getStatusFilter( viewStatuses: Set, statuses: Set, ): null | ((issue: Issue) => boolean) { - const allStatuses = new Set([...viewStatuses, ...statuses]); - if (allStatuses.size === 0) { + const filterStatuses = statuses.size === 0 ? viewStatuses : statuses; + if (filterStatuses.size === 0) { return null; } - return issue => allStatuses.has(issue.status); + return issue => filterStatuses.has(issue.status); } export function getCreatorFilter( @@ -75,13 +75,19 @@ export function getViewFilter( export function getModifiedFilter( args: DateQueryArg[] | null, -): (issue: Issue) => boolean { +): ((issue: Issue) => boolean) | null { + if (args === null) { + return null; + } return createTimeFilter('modified', args); } export function getCreatedFilter( args: DateQueryArg[] | null, -): (issue: Issue) => boolean { +): null | ((issue: Issue) => boolean) { + if (args === null) { + return null; + } return createTimeFilter('created', args); } diff --git a/client/src/layout/left-menu.tsx b/client/src/layout/left-menu.tsx index cbc376b..0305c50 100644 --- a/client/src/layout/left-menu.tsx +++ b/client/src/layout/left-menu.tsx @@ -10,9 +10,14 @@ import IssueModal from '../issue/issue-modal'; import ReactLogo from '../assets/images/logo.svg?react'; import AboutModal from '../about-modal'; import {noop} from 'lodash'; -import {useIssueDetailState, useViewState} from '../hooks/query-state-hooks'; +import { + useIssueDetailState, + useStatusFilterState, + useViewState, +} from '../hooks/query-state-hooks'; import useQueryState, {identityProcessor} from '../hooks/useQueryState'; import {Description, Issue} from 'shared'; +import {getViewStatuses} from '../filters'; interface Props { // Show menu (for small screen only) @@ -32,6 +37,7 @@ const LeftMenu = ({menuVisible, onCloseMenu = noop, onCreateIssue}: Props) => { const ref = useRef() as RefObject; const [issueModalVisible, setIssueModalVisible] = useState(false); const [aboutModalVisible, setAboutModalVisible] = useState(true); + const [statusFilter, setStatusFilter] = useStatusFilterState(); const classes = classnames( 'absolute lg:static inset-0 lg:relative lg:translate-x-0 flex flex-col flex-shrink-0 w-56 font-sans text-sm border-r lg:shadow-none justify-items-start bg-gray border-gray-850 text-white bg-opacity-1', @@ -49,6 +55,11 @@ const LeftMenu = ({menuVisible, onCloseMenu = noop, onCreateIssue}: Props) => { } }); + function pruneStatuses(view: string) { + const viewStatuses = getViewStatuses(view); + return statusFilter?.filter(s => viewStatuses.has(s)) ?? null; + } + return ( <>
@@ -108,7 +119,11 @@ const LeftMenu = ({menuVisible, onCloseMenu = noop, onCreateIssue}: Props) => {
{ - await Promise.all([setView('active'), setIss(null)]); + await Promise.all([ + setView('active'), + setStatusFilter(pruneStatuses('active')), + setIss(null), + ]); onCloseMenu && onCloseMenu(); }} > @@ -118,7 +133,11 @@ const LeftMenu = ({menuVisible, onCloseMenu = noop, onCreateIssue}: Props) => {
{ - await Promise.all([setView('backlog'), setIss(null)]); + await Promise.all([ + setView('backlog'), + setStatusFilter(pruneStatuses('backlog')), + setIss(null), + ]); onCloseMenu && onCloseMenu(); }} > diff --git a/client/src/widgets/filter-menu.tsx b/client/src/widgets/filter-menu.tsx index 7e91248..faae1c2 100644 --- a/client/src/widgets/filter-menu.tsx +++ b/client/src/widgets/filter-menu.tsx @@ -4,12 +4,14 @@ import {Filter} from '../issue/issue'; import {useClickOutside} from '../hooks/useClickOutside'; import SignalStrongIcon from '../assets/icons/signal-strong.svg?react'; import TodoIcon from '../assets/icons/circle.svg?react'; +import UserIcon from '../assets/icons/avatar.svg?react'; +import DateIcon from '../assets/icons/due-date.svg?react'; import {statusOpts} from './priority-menu'; import {statuses} from './status-menu'; import {Priority, Status} from 'shared'; -import UserIcon from './assets/icons/avatar.svg'; -import DateIcon from './assets/icons/due-date.svg'; import useId from '@mui/utils/useId'; +import {useViewState} from '../hooks/query-state-hooks'; +import {getViewStatuses} from '../filters'; type DateCallback = (date: Date) => void; interface Props { @@ -35,6 +37,7 @@ const FilterMenu = ({ const [popperRef, setPopperRef] = useState(null); const [filter, setFilter] = useState(null); const [filterDropDownVisible, setFilterDropDownVisible] = useState(false); + const [view] = useViewState(); const {styles, attributes, update} = usePopper(filterRef, popperRef, { placement: 'bottom-start', @@ -83,23 +86,27 @@ const FilterMenu = ({ ); }); - case Filter.STATUS: - return statuses.map(([Icon, status, label], idx) => { - return ( -
{ - onSelectStatus(status as Status); - setFilter(null); - setFilterDropDownVisible(false); - }} - > - - {label} -
- ); - }); + case Filter.STATUS: { + const viewStatuses = getViewStatuses(view); + return statuses + .filter(s => viewStatuses.size === 0 || viewStatuses.has(s[1])) + .map(([Icon, status, label], idx) => { + return ( +
{ + onSelectStatus(status as Status); + setFilter(null); + setFilterDropDownVisible(false); + }} + > + + {label} +
+ ); + }); + } case Filter.CREATOR: return [