Skip to content

Commit 8bc1661

Browse files
authored
feat(filters): account filtering (#2858)
* feat(filters): account filtering Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat(filters): account filtering Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat(filters): account filtering Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat(filters): account filtering Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat(filters): account filtering Signed-off-by: Adam Setch <adam.setch@outlook.com> --------- Signed-off-by: Adam Setch <adam.setch@outlook.com>
1 parent 64a5e36 commit 8bc1661

7 files changed

Lines changed: 221 additions & 5 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { FC } from 'react';
2+
3+
import { PersonIcon } from '@primer/octicons-react';
4+
import { Stack, Text } from '@primer/react';
5+
6+
import { useAppContext } from '../../hooks/useAppContext';
7+
import { useFiltersStore } from '../../stores';
8+
9+
import { Checkbox } from '../fields/Checkbox';
10+
import { Title } from '../primitives/Title';
11+
12+
import type { AccountUUID } from '../../types';
13+
14+
import { getAccountUUID } from '../../utils/auth/utils';
15+
16+
export const AccountFilter: FC = () => {
17+
const { auth, notifications } = useAppContext();
18+
const filteredAccounts = useFiltersStore((s) => s.accounts);
19+
const updateFilter = useFiltersStore((s) => s.updateFilter);
20+
21+
const accounts = auth?.accounts ?? [];
22+
23+
return (
24+
<fieldset id="filter-accounts">
25+
<Title
26+
icon={PersonIcon}
27+
tooltip={<Text>Filter notifications by account.</Text>}
28+
>
29+
Account
30+
</Title>
31+
32+
<Stack direction="vertical" gap="condensed">
33+
{accounts.map((account) => {
34+
const uuid = getAccountUUID(account);
35+
const isChecked = filteredAccounts.includes(uuid);
36+
const label = account.user?.login ?? account.hostname;
37+
const accountNotificationCount =
38+
notifications?.find((n) => getAccountUUID(n.account) === uuid)
39+
?.notifications.length ?? 0;
40+
41+
return (
42+
<Checkbox
43+
checked={isChecked}
44+
counter={accountNotificationCount}
45+
key={uuid}
46+
label={label}
47+
name={`account-${uuid}`}
48+
onChange={() => updateFilter('accounts', uuid, !isChecked)}
49+
/>
50+
);
51+
})}
52+
</Stack>
53+
</fieldset>
54+
);
55+
};

src/renderer/routes/Filters.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import type { FC } from 'react';
33
import { FilterIcon, FilterRemoveIcon } from '@primer/octicons-react';
44
import { Button, Stack, Tooltip } from '@primer/react';
55

6+
import { useAppContext } from '../hooks/useAppContext';
67
import { useFiltersStore } from '../stores';
78

9+
import { AccountFilter } from '../components/filters/AccountFilter';
810
import { ReasonFilter } from '../components/filters/ReasonFilter';
911
import { SearchFilter } from '../components/filters/SearchFilter';
1012
import { StateFilter } from '../components/filters/StateFilter';
@@ -16,8 +18,11 @@ import { Footer } from '../components/primitives/Footer';
1618
import { Header } from '../components/primitives/Header';
1719

1820
export const FiltersRoute: FC = () => {
21+
const { auth } = useAppContext();
1922
const clearFilters = useFiltersStore((s) => s.reset);
2023

24+
const hasMultipleAccounts = (auth?.accounts.length ?? 0) > 1;
25+
2126
return (
2227
<Page testId="filters">
2328
<Header fetchOnBack icon={FilterIcon}>
@@ -26,6 +31,7 @@ export const FiltersRoute: FC = () => {
2631

2732
<Contents paddingBottom>
2833
<Stack direction="vertical" gap="spacious">
34+
{hasMultipleAccounts && <AccountFilter />}
2935
<SearchFilter />
3036
<UserTypeFilter />
3137
<SubjectTypeFilter />

src/renderer/routes/Notifications.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type FC, useMemo, useRef } from 'react';
22

33
import { useAppContext } from '../hooks/useAppContext';
4+
import { useFiltersStore } from '../stores';
45

56
import { AllRead } from '../components/AllRead';
67
import { Contents } from '../components/layout/Contents';
@@ -13,6 +14,7 @@ import { getAccountUUID } from '../utils/auth/utils';
1314
export const NotificationsRoute: FC = () => {
1415
const { notifications, status, globalError, settings, hasNotifications } =
1516
useAppContext();
17+
const filteredAccounts = useFiltersStore((s) => s.accounts);
1618

1719
// Store previous successful state
1820
const prevStateRef = useRef({
@@ -48,9 +50,19 @@ export const NotificationsRoute: FC = () => {
4850
[displayState.notifications],
4951
);
5052

53+
const visibleNotifications = useMemo(
54+
() =>
55+
filteredAccounts.length === 0
56+
? displayState.notifications
57+
: displayState.notifications.filter((n) =>
58+
filteredAccounts.includes(getAccountUUID(n.account)),
59+
),
60+
[displayState.notifications, filteredAccounts],
61+
);
62+
5163
const hasNoAccountErrors = useMemo(
52-
() => displayState.notifications.every((account) => account.error === null),
53-
[displayState.notifications],
64+
() => visibleNotifications.every((account) => account.error === null),
65+
[visibleNotifications],
5466
);
5567

5668
if (displayState.status === 'error') {
@@ -64,7 +76,7 @@ export const NotificationsRoute: FC = () => {
6476
return (
6577
<Page testId="notifications">
6678
<Contents paddingHorizontal={false}>
67-
{displayState.notifications.map((accountNotification) => {
79+
{visibleNotifications.map((accountNotification) => {
6880
return (
6981
<AccountNotifications
7082
account={accountNotification.account}

src/renderer/routes/__snapshots__/Filters.test.tsx.snap

Lines changed: 136 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/renderer/stores/defaults.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { FiltersState } from './types';
66
export const DEFAULT_FILTERS_STATE: FiltersState = {
77
includeSearchTokens: [],
88
excludeSearchTokens: [],
9+
accounts: [],
910
userTypes: [],
1011
subjectTypes: [],
1112
states: [],

src/renderer/stores/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
AccountUUID,
23
FilterStateType,
34
Reason,
45
SearchToken,
@@ -24,6 +25,12 @@ export interface FiltersState {
2425
*/
2526
excludeSearchTokens: SearchToken[];
2627

28+
/**
29+
* The account UUIDs to filter notifications by.
30+
* When empty, all accounts are shown.
31+
*/
32+
accounts: AccountUUID[];
33+
2734
/**
2835
* The user types to filter notifications by.
2936
*/

src/renderer/stores/useFiltersStore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const useFiltersStore = create<FiltersStore>()(
2323
return (
2424
state.includeSearchTokens.length > 0 ||
2525
state.excludeSearchTokens.length > 0 ||
26+
state.accounts.length > 0 ||
2627
state.userTypes.length > 0 ||
2728
state.subjectTypes.length > 0 ||
2829
state.states.length > 0 ||

0 commit comments

Comments
 (0)