Skip to content
This repository has been archived by the owner on Jul 23, 2024. It is now read-only.

Commit

Permalink
Merge pull request #206 from parodos-dev/dl/notifications-filter
Browse files Browse the repository at this point in the history
Add search to notifications
  • Loading branch information
wKich authored Aug 23, 2023
2 parents 5a81d80 + 679cbbb commit e8690b1
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 68 deletions.
16 changes: 16 additions & 0 deletions plugins/parodos/src/components/notification/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const Notification = () => {
useEffect(() => {
fetchNotifications({
filter: state.notificationFilter,
search: state.notificationSearch,
page: state.page,
rowsPerPage: state.rowsPerPage,
fetch,
Expand All @@ -75,6 +76,7 @@ export const Notification = () => {
state.page,
state.rowsPerPage,
state.notificationFilter,
state.notificationSearch,
fetch,
]);

Expand All @@ -93,6 +95,16 @@ export const Notification = () => {
});
};

const searchChangeHandler = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch({
type: 'CHANGE_SEARCH',
payload: { search: e.target.value },
});
},
[dispatch],
);

const handleChangePage = (
_event: MouseEvent<HTMLButtonElement> | null,
newPage: number,
Expand Down Expand Up @@ -167,6 +179,7 @@ export const Notification = () => {

fetchNotifications({
filter: state.notificationFilter,
search: state.notificationSearch,
page: state.page,
rowsPerPage: state.rowsPerPage,
fetch,
Expand All @@ -185,6 +198,7 @@ export const Notification = () => {
notifications,
state.action,
state.notificationFilter,
state.notificationSearch,
state.page,
state.rowsPerPage,
state.selectedNotifications,
Expand Down Expand Up @@ -224,6 +238,8 @@ export const Notification = () => {
<NotificationListHeader
filterChangeHandler={filterChangeHandler}
filter={state.notificationFilter}
searchChangeHandler={searchChangeHandler}
search={state.notificationSearch}
selected={state.selectedNotifications.length}
archiveHandler={archiveNotificationsHandler}
unarchiveHandler={unarchiveNotificationsHandler}
Expand Down
188 changes: 122 additions & 66 deletions plugins/parodos/src/components/notification/NotificationsListHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import React, { MouseEventHandler } from 'react';
import { Grid, IconButton, makeStyles, Typography } from '@material-ui/core';
import React, {
MouseEventHandler,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import {
debounce,
Grid,
IconButton,
makeStyles,
TextField,
Typography,
} from '@material-ui/core';
import { AccordionIcon } from '../icons/AccordionIcon';
import { Select } from '@backstage/core-components';
import { Progress, Select } from '@backstage/core-components';
import { type PropsFromComponent } from '../types';
import ArchiveIcon from '@material-ui/icons/Archive';
import UnarchiveIcon from '@material-ui/icons/Unarchive';
import DeleteIcon from '@material-ui/icons/DeleteForever';
import cs from 'classnames';

type SelectProps = PropsFromComponent<typeof Select>;
type TextFieldProps = PropsFromComponent<typeof TextField>;

type View = 'Filter' | 'Actions';

interface NotificationListHeaderProps {
filterChangeHandler: SelectProps['onChange'];
filter: SelectProps['selected'];
searchChangeHandler: TextFieldProps['onChange'];
search: TextFieldProps['value'];
selected: number;
deleteHandler: MouseEventHandler<HTMLButtonElement>;
archiveHandler: MouseEventHandler<HTMLButtonElement>;
Expand All @@ -32,12 +48,8 @@ const useStyles = makeStyles(theme => ({
selected: {
marginLeft: theme.spacing(2),
color: theme.palette.error.main,
position: 'relative',
top: theme.spacing(2),
},
actions: {
position: 'relative',
top: theme.spacing(2),
display: 'flex',
justifyContent: 'flex-end',
marginRight: theme.spacing(1),
Expand All @@ -51,11 +63,18 @@ const useStyles = makeStyles(theme => ({
filterIcon: {
color: theme.palette.primary.main,
},
progress: {
position: 'relative',
top: theme.spacing(1),
height: theme.spacing(0.5),
},
}));

export function NotificationListHeader({
filterChangeHandler,
filter,
searchChangeHandler,
search,
selected,
deleteHandler,
archiveHandler,
Expand All @@ -65,74 +84,111 @@ export function NotificationListHeader({
const styles = useStyles();
const view: View = selected === 0 ? 'Filter' : 'Actions';

const [searchValue, setSearchValue] = useState(search);

const debouncedSearchHandler = useMemo(
() => searchChangeHandler && debounce(searchChangeHandler, 500),
[searchChangeHandler],
);
const isLoading = searchValue !== search;

const handleSearchChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(event.target.value);
debouncedSearchHandler?.(event);
},
[debouncedSearchHandler],
);

useEffect(() => {
setSearchValue(search);
}, [search]);

return (
<Grid
container
justifyContent="space-between"
alignItems="center"
className={cs(styles.root, view === 'Actions' && 'actions')}
>
<Grid item xs={3}>
{view === 'Filter' ? (
<Select
onChange={filterChangeHandler}
selected={filter}
label="Filter by"
items={[
{ label: 'All', value: 'ALL' },
{ label: 'Unread', value: 'UNREAD' },
{ label: 'Archived', value: 'ARCHIVED' },
]}
/>
) : (
<Typography className={styles.selected}>
{selected} selected
</Typography>
)}
</Grid>
<>
<Grid
item
xs={1}
className={cs(styles.actions, {
[styles.accordionIcon]: view === 'Filter',
})}
container
justifyContent="space-between"
alignItems="flex-end"
className={cs(styles.root, view === 'Actions' && 'actions')}
>
{view === 'Filter' ? (
<span className={styles.filterIcon}>
<AccordionIcon />
</span>
<>
<Grid item xs={3}>
<Select
onChange={filterChangeHandler}
selected={filter}
label="Filter by"
items={[
{ label: 'All', value: 'ALL' },
{ label: 'Unread', value: 'UNREAD' },
{ label: 'Archived', value: 'ARCHIVED' },
]}
/>
</Grid>
<Grid item xs={3}>
<TextField
placeholder="Search"
label="Search"
variant="outlined"
size="small"
fullWidth
onChange={handleSearchChange}
value={searchValue}
/>
</Grid>
</>
) : (
<span className={styles.buttons}>
{filter !== 'ARCHIVED' && (
<IconButton
aria-label="archive"
onClick={archiveHandler}
disabled={disableButtons}
>
<ArchiveIcon />
</IconButton>
)}
{filter !== 'UNREAD' && (
<Grid item xs={3}>
<Typography className={styles.selected}>
{selected} selected
</Typography>
</Grid>
)}
<Grid
item
xs={1}
className={cs(styles.actions, {
[styles.accordionIcon]: view === 'Filter',
})}
>
{view === 'Filter' ? (
<span className={styles.filterIcon}>
<AccordionIcon />
</span>
) : (
<span className={styles.buttons}>
{filter !== 'ARCHIVED' && (
<IconButton
aria-label="archive"
onClick={archiveHandler}
disabled={disableButtons}
>
<ArchiveIcon />
</IconButton>
)}
{filter !== 'UNREAD' && (
<IconButton
aria-label="unarchive"
onClick={unarchiveHandler}
disabled={disableButtons}
>
<UnarchiveIcon />
</IconButton>
)}
<IconButton
aria-label="unarchive"
onClick={unarchiveHandler}
onClick={deleteHandler}
aria-label="delete"
edge="end"
disabled={disableButtons}
>
<UnarchiveIcon />
<DeleteIcon />
</IconButton>
)}
<IconButton
size="medium"
onClick={deleteHandler}
aria-label="delete"
edge="end"
disabled={disableButtons}
>
<DeleteIcon />
</IconButton>
</span>
)}
</span>
)}
</Grid>
</Grid>
</Grid>
<div className={styles.progress}>{isLoading && <Progress />}</div>
</>
);
}
13 changes: 13 additions & 0 deletions plugins/parodos/src/components/notification/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ type Actions =
payload: {
filter: NotificationState;
};
}
| {
type: 'CHANGE_SEARCH';
payload: {
search: string;
};
};

export const initialState = {
Expand All @@ -55,6 +61,7 @@ export const initialState = {
selectedNotifications: [] as string[],
action: 'ARCHIVE' as Action,
notificationFilter: 'ALL' as NotificationState,
notificationSearch: '',
};

export type State = typeof initialState;
Expand Down Expand Up @@ -127,6 +134,12 @@ export const reducer = (draft: State, action: Actions) => {

break;
}
case 'CHANGE_SEARCH': {
draft.notificationSearch = action.payload.search;
draft.page = 0;

break;
}
default: {
throw new Error(`unknown action type`);
}
Expand Down
15 changes: 13 additions & 2 deletions plugins/parodos/src/stores/slices/notificationsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ async function fetchNotifications(
baseUrl: string,
options: Parameters<NotificationsSlice['fetchNotifications']>[0],
) {
const { filter, page, rowsPerPage, fetch } = options;
const { filter, search, page, rowsPerPage, fetch } = options;
let urlQuery = `?page=${page}&size=${rowsPerPage}&sort=notificationMessage.createdOn,desc`;
if (filter !== 'ALL') {
urlQuery += `&state=${filter}`;
}
if (search) {
urlQuery += `&searchTerm=${search}`;
}

const response = await fetch(`${baseUrl}${urls.Notifications}${urlQuery}`);

Expand All @@ -34,6 +37,7 @@ export const createNotificationsSlice: StateCreator<
try {
const notifications = await fetchNotifications(get().baseUrl as string, {
filter: 'UNREAD',
search: '',
page: 0,
rowsPerPage: 0,
fetch,
Expand All @@ -46,7 +50,13 @@ export const createNotificationsSlice: StateCreator<
console.error('Error fetching notifications', e);
}
},
async fetchNotifications({ filter = 'ALL', page, rowsPerPage, fetch }) {
async fetchNotifications({
filter = 'ALL',
search,
page,
rowsPerPage,
fetch,
}) {
if (get().notificationsLoading) {
return;
}
Expand All @@ -58,6 +68,7 @@ export const createNotificationsSlice: StateCreator<
try {
const notifications = await fetchNotifications(get().baseUrl as string, {
filter,
search,
page,
rowsPerPage,
fetch,
Expand Down
1 change: 1 addition & 0 deletions plugins/parodos/src/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface NotificationsSlice {
fetchNotifications(params: {
fetch: FetchApi['fetch'];
filter: NotificationState;
search: string;
page: number;
rowsPerPage: number;
}): Promise<void>;
Expand Down

0 comments on commit e8690b1

Please sign in to comment.