change(ui) - notifications count and list
This commit is contained in:
parent
8273fc08bc
commit
d8911e93c1
6 changed files with 137 additions and 173 deletions
|
|
@ -1,182 +1,46 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import stl from './notifications.module.css';
|
||||
import ListItem from './ListItem';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, SlideModal, Icon, Popup, NoContent } from 'UI';
|
||||
import { Icon, Popup } from 'UI';
|
||||
import { fetchList, setViewed, clearAll } from 'Duck/notifications';
|
||||
import { setLastRead } from 'Duck/announcements';
|
||||
import cn from 'classnames';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import AlertTriggersModal from 'Shared/AlertTriggersModal';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
|
||||
const AUTOREFRESH_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
// @withToggle('visible', 'toggleVisisble')
|
||||
// @withRouter
|
||||
// class Notifications extends React.Component {
|
||||
// state = { alertType: '' };
|
||||
|
||||
// constructor(props) {
|
||||
// super(props);
|
||||
// // setTimeout(() => {
|
||||
// // props.fetchList();
|
||||
// // }, 1000);
|
||||
|
||||
// setInterval(() => {
|
||||
// props.fetchList();
|
||||
// }, AUTOREFRESH_INTERVAL);
|
||||
// }
|
||||
|
||||
// writeOption = (e, { name, value }) => this.setState({ [ name ]: value });
|
||||
|
||||
// navigateToUrl = notification => { // TODO should be able to open the alert edit form
|
||||
// if (notification.options.source === 'ALERT') {
|
||||
// const { initAlert } = this.props;
|
||||
// this.props.fetchAlerts().then(function() {
|
||||
// const { alerts } = this.props;
|
||||
// const alert = alerts.find(i => i.alertId === notification.options.sourceId)
|
||||
// initAlert(alert.toJS());
|
||||
// }.bind(this));
|
||||
// }
|
||||
// }
|
||||
|
||||
// onClearAll = () => {
|
||||
// const { notifications } = this.props;
|
||||
// const firstItem = notifications.first();
|
||||
// this.props.clearAll({ endTimestamp: firstItem.createdAt.ts });
|
||||
// }
|
||||
|
||||
// onClear = notification => {
|
||||
// this.props.setViewed(notification.notificationId)
|
||||
// }
|
||||
|
||||
// toggleModal = () => {
|
||||
// this.props.toggleVisisble(!this.props.visible);
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// const { notifications, visible, loading, clearing, clearingAll } = this.props;
|
||||
// const { alertType } = this.state;
|
||||
// const unReadNotificationsCount = notifications.filter(({viewed}) => !viewed).size
|
||||
|
||||
// const filteredList = alertType === '' ?
|
||||
// notifications :
|
||||
// notifications.filter(i => i.filterKey === alertType);
|
||||
|
||||
// return (
|
||||
// <div>
|
||||
// <Popup
|
||||
// content={ `Alerts` }
|
||||
// >
|
||||
// <div className={ stl.button } onClick={ this.toggleModal } data-active={ visible }>
|
||||
// <div className={ stl.counter } data-hidden={ unReadNotificationsCount === 0 }>
|
||||
// { unReadNotificationsCount }
|
||||
// </div>
|
||||
// <Icon name="bell" size="18" />
|
||||
// </div>
|
||||
// </Popup>
|
||||
// <SlideModal
|
||||
// title={
|
||||
// <div className="flex items-center justify-between">
|
||||
// <div>Alerts</div>
|
||||
// { unReadNotificationsCount > 0 && (
|
||||
// <div className="">
|
||||
// <Button
|
||||
// loading={clearingAll}
|
||||
// variant="text"
|
||||
// onClick={this.props.setLastRead}
|
||||
// disabled={unReadNotificationsCount === 0}
|
||||
// >
|
||||
// <span
|
||||
// className={ cn("text-sm color-gray-medium", { 'invisible' : clearingAll })}
|
||||
// onClick={this.onClearAll}>
|
||||
// IGNORE ALL
|
||||
// </span>
|
||||
// </Button>
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// }
|
||||
// right
|
||||
// isDisplayed={ visible }
|
||||
// onClose={ visible && this.toggleModal }
|
||||
// bgColor="white"
|
||||
// size="small"
|
||||
// content={
|
||||
// <div className="">
|
||||
// <NoContent
|
||||
// title={
|
||||
// <div className="flex items-center justify-between">
|
||||
// <AnimatedSVG name={ICONS.EMPTY_STATE} size="100" />
|
||||
// </div>
|
||||
// }
|
||||
// subtext="There are no alerts to show."
|
||||
// show={ !loading && notifications.size === 0 }
|
||||
// size="small"
|
||||
// >
|
||||
// {
|
||||
// filteredList.map(item => (
|
||||
// <div className="border-b" key={item.key}>
|
||||
// <ListItem
|
||||
// key={item.key}
|
||||
// alert={item}
|
||||
// onClear={() => this.onClear(item)}
|
||||
// loading={clearing}
|
||||
// />
|
||||
// </div>
|
||||
// ))
|
||||
// }
|
||||
// </NoContent>
|
||||
// </div>
|
||||
// }
|
||||
// />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// export default connect(state => ({
|
||||
// notifications: state.getIn(['notifications', 'list']),
|
||||
// loading: state.getIn(['notifications', 'fetchRequest', 'loading']),
|
||||
// clearing: state.getIn(['notifications', 'setViewed', 'loading']),
|
||||
// clearingAll: state.getIn(['notifications', 'clearAll', 'loading']),
|
||||
// alerts: state.getIn(['alerts', 'list']),
|
||||
// }), { fetchList, setLastRead, setViewed, clearAll, fetchAlerts, initAlert })(Notifications);
|
||||
|
||||
interface Props {
|
||||
notifications: any;
|
||||
fetchList: any;
|
||||
}
|
||||
function Notifications(props: Props) {
|
||||
const { notifications } = props;
|
||||
// const { notifications } = props;
|
||||
const { showModal } = useModal();
|
||||
const unReadNotificationsCount = notifications.filter(({viewed}: any) => !viewed).size
|
||||
// const unReadNotificationsCount = notifications.filter(({viewed}: any) => !viewed).size
|
||||
const { notificationStore } = useStore();
|
||||
const count = useObserver(() => notificationStore.notificationsCount);
|
||||
|
||||
useEffect(() => {
|
||||
if (notifications.size === 0) {
|
||||
props.fetchList();
|
||||
}
|
||||
|
||||
notificationStore.fetchNotificationsCount();
|
||||
const interval = setInterval(() => {
|
||||
props.fetchList();
|
||||
notificationStore.fetchNotificationsCount()
|
||||
}, AUTOREFRESH_INTERVAL);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Popup
|
||||
content={ `Alerts` }
|
||||
>
|
||||
<div className={ stl.button } onClick={ () => showModal(<AlertTriggersModal unReadNotificationsCount={unReadNotificationsCount} />, { right: true }) }>
|
||||
<div className={ stl.counter } data-hidden={ unReadNotificationsCount === 0 }>
|
||||
{ unReadNotificationsCount }
|
||||
return useObserver(() => (
|
||||
<Popup content={ `Alerts` } >
|
||||
<div className={ stl.button } onClick={ () => showModal(<AlertTriggersModal />, { right: true }) }>
|
||||
<div className={ stl.counter } data-hidden={ count === 0 }>
|
||||
{ count }
|
||||
</div>
|
||||
<Icon name="bell" size="18" />
|
||||
</div>
|
||||
</Popup>
|
||||
);
|
||||
</Popup>
|
||||
));
|
||||
}
|
||||
|
||||
export default connect((state: any) => ({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Button, NoContent } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
import { fetchList, setViewed, clearAll } from 'Duck/notifications';
|
||||
|
|
@ -6,9 +6,10 @@ import { setLastRead } from 'Duck/announcements';
|
|||
import cn from 'classnames';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
import ListItem from './ListItem'
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
|
||||
interface Props {
|
||||
unReadNotificationsCount: number;
|
||||
setLastRead: Function;
|
||||
|
||||
clearingAll: boolean;
|
||||
|
|
@ -20,7 +21,10 @@ interface Props {
|
|||
setViewed: Function;
|
||||
}
|
||||
function AlertTriggersModal(props: Props) {
|
||||
const { list, unReadNotificationsCount, loading, clearingAll, clearing } = props;
|
||||
const { loading, clearingAll, clearing } = props;
|
||||
const { notificationStore } = useStore();
|
||||
const count = useObserver(() => notificationStore.notificationsCount);
|
||||
const list = useObserver(() => notificationStore.notifications);
|
||||
|
||||
const onClearAll = () => {
|
||||
const { list } = props;
|
||||
|
|
@ -29,21 +33,24 @@ function AlertTriggersModal(props: Props) {
|
|||
}
|
||||
|
||||
const onClear = (notification: any) => {
|
||||
console.log('onClear', notification);
|
||||
props.setViewed(notification.notificationId)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
notificationStore.fetchNotifications();
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="bg-white box-shadow h-screen overflow-y-auto" style={{ width: '450px'}}>
|
||||
<div className="flex items-center justify-between p-5 text-2xl">
|
||||
<div>Alerts</div>
|
||||
{ unReadNotificationsCount > 0 && (
|
||||
{ count > 0 && (
|
||||
<div className="">
|
||||
<Button
|
||||
loading={clearingAll}
|
||||
variant="text"
|
||||
onClick={props.setLastRead}
|
||||
disabled={unReadNotificationsCount === 0}
|
||||
disabled={count === 0}
|
||||
>
|
||||
<span
|
||||
className={ cn("text-sm color-gray-medium", { 'invisible' : clearingAll })}
|
||||
|
|
@ -63,7 +70,7 @@ function AlertTriggersModal(props: Props) {
|
|||
</div>
|
||||
}
|
||||
subtext="There are no alerts to show."
|
||||
show={ !loading && unReadNotificationsCount === 0 }
|
||||
show={ !loading && count === 0 }
|
||||
size="small"
|
||||
>
|
||||
{list.map((item: any, i: any) => (
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { makeAutoObservable, observable, action } from "mobx"
|
|||
import { dashboardService, metricService, sessionService, userService, auditService } from 'App/services';
|
||||
import SettingsStore from './settingsStore';
|
||||
import AuditStore from './auditStore';
|
||||
import NotificationStore from './notificationStore';
|
||||
|
||||
export class RootStore {
|
||||
dashboardStore: IDashboardSotre;
|
||||
|
|
@ -16,6 +17,7 @@ export class RootStore {
|
|||
userStore: UserStore;
|
||||
roleStore: RoleStore;
|
||||
auditStore: AuditStore;
|
||||
notificationStore: NotificationStore
|
||||
|
||||
limits: any;
|
||||
|
||||
|
|
@ -26,6 +28,7 @@ export class RootStore {
|
|||
this.userStore = new UserStore();
|
||||
this.roleStore = new RoleStore();
|
||||
this.auditStore = new AuditStore();
|
||||
this.notificationStore = new NotificationStore();
|
||||
makeAutoObservable(this, {
|
||||
limits: observable,
|
||||
fetchLimits: action,
|
||||
|
|
|
|||
42
frontend/app/mstore/notificationStore.ts
Normal file
42
frontend/app/mstore/notificationStore.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { action, makeAutoObservable, observable } from "mobx";
|
||||
import { userService } from 'App/services';
|
||||
import Notification from './types/notification';
|
||||
|
||||
export default class NotificationStore {
|
||||
notificationsCount: any = 0;
|
||||
notifications: any = [];
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
notificationsCount: observable,
|
||||
notifications: observable,
|
||||
|
||||
fetchNotificationsCount: action,
|
||||
fetchNotifications: action,
|
||||
});
|
||||
}
|
||||
|
||||
fetchNotifications(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.getNotifications()
|
||||
.then((response: any) => {
|
||||
this.notifications = response.map((notification: any) => new Notification().fromJson(notification));
|
||||
resolve(response);
|
||||
}).catch((error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchNotificationsCount(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.getNotificationsCount()
|
||||
.then((response: any) => {
|
||||
this.notificationsCount = response.count;
|
||||
resolve(response);
|
||||
}).catch((error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
36
frontend/app/mstore/types/notification.ts
Normal file
36
frontend/app/mstore/types/notification.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export default class Notification {
|
||||
notificationId: string
|
||||
level: string
|
||||
editedAt: string
|
||||
createdAt: string
|
||||
text: string
|
||||
link: string
|
||||
viewed: boolean
|
||||
title: string
|
||||
description: string
|
||||
type: string
|
||||
filterKey: string
|
||||
options: any = { source: '', sourceId: '', projectId: '', sourceMeta: ''}
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
fromJson(json: any) {
|
||||
this.notificationId = json.notificationId
|
||||
this.level = json.level
|
||||
this.editedAt = json.editedAt && DateTime.fromMillis(json.editedAt || 0);
|
||||
this.createdAt = json.createdAt && DateTime.fromMillis(json.createdAt || 0);
|
||||
this.text = json.text
|
||||
this.link = json.link
|
||||
this.viewed = json.viewed
|
||||
this.title = json.title
|
||||
this.description = json.description
|
||||
this.type = json.type
|
||||
this.filterKey = json.filterKey
|
||||
this.options = json.options
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
@ -14,45 +14,45 @@ export default class UserService {
|
|||
|
||||
all() {
|
||||
return this.client.get('/client/members')
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || []);
|
||||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || []);
|
||||
}
|
||||
|
||||
one(userId: string) {
|
||||
return this.client.get('/users/' + userId)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || {});
|
||||
}
|
||||
|
||||
save(user: IUser): Promise<any> {
|
||||
const data = user.toSave();
|
||||
if (user.userId) {
|
||||
return this.client.put('/client/members/' + user.userId, data)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {})
|
||||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || {})
|
||||
} else {
|
||||
return this.client.post('/client/members', data)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || {});
|
||||
}
|
||||
}
|
||||
|
||||
generateInviteCode(userId: any): Promise<any> {
|
||||
return this.client.get(`/client/members/${userId}/reset`)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || {});
|
||||
}
|
||||
|
||||
delete(userId: string) {
|
||||
return this.client.delete('/client/members/' + userId)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || {});
|
||||
}
|
||||
|
||||
getRoles() {
|
||||
return this.client.get('/client/roles')
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || []);
|
||||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || []);
|
||||
}
|
||||
|
||||
getLimits() {
|
||||
|
|
@ -60,4 +60,16 @@ export default class UserService {
|
|||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || {});
|
||||
}
|
||||
|
||||
getNotificationsCount() {
|
||||
return this.client.get('/notifications/count')
|
||||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || {});
|
||||
}
|
||||
|
||||
getNotifications() {
|
||||
return this.client.get('/notifications')
|
||||
.then((response: { json: () => any; }) => response.json())
|
||||
.then((response: { data: any; }) => response.data || {});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue