1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-09 15:55:23 +02:00

feat(notifications): track toast notifications [EE-4132] (#7711)

* feat(notifications): track toast notifications [EE-4132]

* suggested refactoring

* fix failing test

* remove duplicate styles

* applying spacing to context icon
This commit is contained in:
itsconquest 2022-09-23 17:17:44 +12:00 committed by GitHub
parent 4e20d70a99
commit 648c1db437
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 608 additions and 59 deletions

View file

@ -0,0 +1,79 @@
import { Bell, Trash2 } from 'react-feather';
import { useStore } from 'zustand';
import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { react2angular } from '@/react-tools/react2angular';
import { useUser } from '@/portainer/hooks/useUser';
import { withUIRouter } from '@/react-tools/withUIRouter';
import { withReactQuery } from '@/react-tools/withReactQuery';
import { PageHeader } from '@@/PageHeader';
import { Datatable } from '@@/datatables';
import { Button } from '@@/buttons';
import { notificationsStore } from './notifications-store';
import { ToastNotification } from './types';
import { columns } from './columns';
import { createStore } from './datatable-store';
const storageKey = 'notifications-list';
const useSettingsStore = createStore(storageKey);
export function NotificationsView() {
const settingsStore = useSettingsStore();
const { user } = useUser();
const userNotifications: ToastNotification[] = useStore(
notificationsStore,
(state) => state.userNotifications[user.Id]
);
const breadcrumbs = 'Notifications';
return (
<>
<PageHeader title="Notifications" breadcrumbs={breadcrumbs} reload />
<Datatable
columns={columns}
titleOptions={{
title: 'Notifications',
icon: Bell,
}}
dataset={userNotifications}
settingsStore={settingsStore}
storageKey="notifications"
emptyContentLabel="No notifications found"
totalCount={userNotifications.length}
renderTableActions={(selectedRows) => (
<TableActions selectedRows={selectedRows} />
)}
/>
</>
);
}
function TableActions({ selectedRows }: { selectedRows: ToastNotification[] }) {
const { user } = useUser();
const notificationsStoreState = useStore(notificationsStore);
return (
<Button
icon={Trash2}
color="dangerlight"
onClick={() => handleRemove()}
disabled={selectedRows.length === 0}
>
Remove
</Button>
);
function handleRemove() {
const { removeNotifications } = notificationsStoreState;
const ids = selectedRows.map((row) => row.id);
removeNotifications(user.Id, ids);
}
}
export const NotificationsViewAngular = react2angular(
withUIRouter(withReactQuery(withCurrentUser(NotificationsView))),
[]
);

View file

@ -0,0 +1,11 @@
import { Column } from 'react-table';
import { ToastNotification } from '../types';
export const details: Column<ToastNotification> = {
Header: 'Details',
accessor: 'details',
id: 'details',
disableFilters: true,
canHide: true,
};

View file

@ -0,0 +1,6 @@
import { type } from './type';
import { title } from './title';
import { details } from './details';
import { time } from './time';
export const columns = [type, title, details, time];

View file

@ -0,0 +1,13 @@
import { Column } from 'react-table';
import { isoDate } from '@/portainer/filters/filters';
import { ToastNotification } from '../types';
export const time: Column<ToastNotification> = {
Header: 'Time',
accessor: (row) => (row.timeStamp ? isoDate(row.timeStamp) : '-'),
id: 'time',
disableFilters: true,
canHide: true,
};

View file

@ -0,0 +1,11 @@
import { Column } from 'react-table';
import { ToastNotification } from '../types';
export const title: Column<ToastNotification> = {
Header: 'Title',
accessor: 'title',
id: 'title',
disableFilters: true,
canHide: true,
};

View file

@ -0,0 +1,11 @@
import { Column } from 'react-table';
import { ToastNotification } from '../types';
export const type: Column<ToastNotification> = {
Header: 'Type',
accessor: (row) => row.type.charAt(0).toUpperCase() + row.type.slice(1),
id: 'type',
disableFilters: true,
canHide: true,
};

View file

@ -0,0 +1,36 @@
import create from 'zustand';
import { persist } from 'zustand/middleware';
import { keyBuilder } from '@/portainer/hooks/useLocalStorage';
import {
paginationSettings,
sortableSettings,
refreshableSettings,
hiddenColumnsSettings,
PaginationTableSettings,
RefreshableTableSettings,
SettableColumnsTableSettings,
SortableTableSettings,
} from '@/react/components/datatables/types';
interface TableSettings
extends SortableTableSettings,
PaginationTableSettings,
SettableColumnsTableSettings,
RefreshableTableSettings {}
export function createStore(storageKey: string) {
return create<TableSettings>()(
persist(
(set) => ({
...sortableSettings(set),
...paginationSettings(set),
...hiddenColumnsSettings(set),
...refreshableSettings(set),
}),
{
name: keyBuilder(storageKey),
}
)
);
}

View file

@ -0,0 +1,64 @@
import create from 'zustand/vanilla';
import { persist } from 'zustand/middleware';
import { keyBuilder } from '@/portainer/hooks/useLocalStorage';
import { ToastNotification } from './types';
interface NotificationsState {
userNotifications: Record<string, ToastNotification[]>;
addNotification: (userId: number, notification: ToastNotification) => void;
removeNotification: (userId: number, notificationId: string) => void;
removeNotifications: (userId: number, notifications: string[]) => void;
clearUserNotifications: (userId: number) => void;
}
export const notificationsStore = create<NotificationsState>()(
persist(
(set) => ({
userNotifications: {},
addNotification: (userId: number, notification: ToastNotification) => {
set((state) => ({
userNotifications: {
...state.userNotifications,
[userId]: [
...(state.userNotifications[userId] || []),
notification,
],
},
}));
},
removeNotification: (userId: number, notificationId: string) => {
set((state) => ({
userNotifications: {
...state.userNotifications,
[userId]: state.userNotifications[userId].filter(
(notif) => notif.id !== notificationId
),
},
}));
},
removeNotifications: (userId: number, notificationIds: string[]) => {
set((state) => ({
userNotifications: {
...state.userNotifications,
[userId]: state.userNotifications[userId].filter(
(notification) => !notificationIds.includes(notification.id)
),
},
}));
},
clearUserNotifications: (userId: number) => {
set((state) => ({
userNotifications: {
...state.userNotifications,
[userId]: [],
},
}));
},
}),
{
name: keyBuilder('notifications'),
}
)
);

View file

@ -0,0 +1,7 @@
export type ToastNotification = {
id: string;
title: string;
details: string;
type: string;
timeStamp: Date;
};