296 lines
11 KiB
TypeScript
296 lines
11 KiB
TypeScript
import {
|
|
Avatar,
|
|
Button,
|
|
CircularProgress,
|
|
createStyles,
|
|
Divider,
|
|
Drawer,
|
|
Grid,
|
|
IconButton,
|
|
List,
|
|
ListItem,
|
|
ListItemIcon,
|
|
ListItemSecondaryAction,
|
|
ListItemText,
|
|
Theme,
|
|
Tooltip,
|
|
Typography,
|
|
useTheme
|
|
} from "@mui/material";
|
|
import { makeStyles } from "@mui/styles"
|
|
import { Remove } from "@mui/icons-material";
|
|
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
import DeviceIcon from "assets/editor/device.png";
|
|
import { useMobile, useUserAPI } from "hooks";
|
|
import moment from "moment";
|
|
//import { GetComponentIcon } from "pbHelpers/ComponentType"; //TODO: Put this pack in for the icons once components are done
|
|
import { pond } from "protobuf-ts/pond";
|
|
import { useGlobalState } from "providers";
|
|
import { useNotificationAPI } from "providers";
|
|
import React, { useCallback, useEffect, useState } from "react";
|
|
import { useNavigate } from "react-router";
|
|
|
|
const useStyles = makeStyles((theme: Theme) => ({
|
|
chatDrawer: {
|
|
padding: theme.spacing(1),
|
|
height: "100%",
|
|
minWidth: theme.spacing(54),
|
|
maxWidth: theme.spacing(54)
|
|
},
|
|
chatDrawerMobile: {
|
|
padding: theme.spacing(1),
|
|
height: "auto",
|
|
width: "100%"
|
|
},
|
|
drawerHeader: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
padding: theme.spacing(0, 1),
|
|
// necessary for content to be below app bar
|
|
...theme.mixins.toolbar,
|
|
justifyContent: "space-between"
|
|
}
|
|
}));
|
|
|
|
interface Props {
|
|
notificationDrawerOpen: boolean;
|
|
setNotificationDrawerOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
}
|
|
|
|
export default function NotificationDrawer(props: Props) {
|
|
const { notificationDrawerOpen, setNotificationDrawerOpen } = props;
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [loadingMore, setLoadingMore] = useState(false);
|
|
const [notifications, setNotifications] = useState<Map<string, pond.Notification>>(new Map());
|
|
const [offset, setOffset] = useState(0);
|
|
const [total, setTotal] = useState(9999999);
|
|
|
|
const history = useNavigate();
|
|
const isMobile = useMobile();
|
|
const classes = useStyles();
|
|
const theme = useTheme();
|
|
const notificationAPI = useNotificationAPI();
|
|
const userAPI = useUserAPI();
|
|
const [{ user, as }] = useGlobalState();
|
|
|
|
const loadNotifications = useCallback(() => {
|
|
if (user.id().length < 1) return;
|
|
setLoading(true);
|
|
notificationAPI
|
|
.listNotifications(10, 0, "desc", "timestamp", undefined, as)
|
|
.then(resp => {
|
|
let r = pond.ListNotificationsResponse.fromObject(resp.data);
|
|
setTotal(r.total);
|
|
let n: Map<string, pond.Notification> = new Map();
|
|
r.notifications.forEach(noti => {
|
|
n.set(noti.settings?.key ? noti.settings.key : "", noti);
|
|
});
|
|
setNotifications(n);
|
|
let u = user.protobuf();
|
|
if (u.status) {
|
|
let DATE_RFC2822 = "ddd, DD MMM YYYY HH:mm:ss ZZ";
|
|
u.status.lastNotificationViewed = moment()
|
|
.locale("en")
|
|
.format(DATE_RFC2822);
|
|
userAPI.updateUser(user.id(), u);
|
|
}
|
|
})
|
|
.finally(() => {
|
|
setLoading(false);
|
|
});
|
|
}, [notificationAPI, setLoading, setNotifications, user, userAPI, as]);
|
|
|
|
useEffect(() => {
|
|
if (offset === 0) return;
|
|
setLoadingMore(true);
|
|
notificationAPI
|
|
.listNotifications(10, offset, "desc", "timestamp", undefined, as)
|
|
.then(resp => {
|
|
let r = pond.ListNotificationsResponse.fromObject(resp.data);
|
|
let n: Map<string, pond.Notification> = new Map(notifications);
|
|
r.notifications.forEach(noti => {
|
|
n.set(noti.settings?.key ? noti.settings.key : "", noti);
|
|
});
|
|
setNotifications(n);
|
|
})
|
|
.finally(() => {
|
|
setLoadingMore(false);
|
|
});
|
|
}, [offset, notificationAPI]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
const removeNotification = (keys: string[]) => {
|
|
notificationAPI.hideNotification(keys).then(() => {
|
|
let n = new Map(notifications);
|
|
keys.forEach(key => {
|
|
n.delete(key);
|
|
});
|
|
setNotifications(n);
|
|
setTotal(total - keys.length);
|
|
});
|
|
};
|
|
|
|
const hasSource = (notification: pond.Notification) => {
|
|
return Boolean(notification.settings?.sourceType && notification.settings?.sourceKey);
|
|
};
|
|
|
|
const notificationClick = (notification: pond.Notification) => {
|
|
setNotificationDrawerOpen(false);
|
|
setNotifications(new Map());
|
|
if (!notification.settings?.sourceType) return;
|
|
if (!notification.settings?.sourceKey) return;
|
|
let url = "/" + notification.settings?.sourceType + "s/" + notification.settings?.sourceKey;
|
|
history(url); //in order to update the history stack add '{ replace: true }' as a second parameter
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (loading) return;
|
|
if (!notificationDrawerOpen) return;
|
|
loadNotifications();
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [loadNotifications, notificationDrawerOpen]);
|
|
|
|
return (
|
|
<Drawer
|
|
anchor="right"
|
|
open={notificationDrawerOpen}
|
|
onClose={() => setNotificationDrawerOpen(false)}
|
|
style={{ height: "100%", maxWidth: "100%" }}
|
|
title={"Notifications"}>
|
|
<div className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}>
|
|
<div className={classes.drawerHeader}>
|
|
<IconButton onClick={() => setNotificationDrawerOpen(false)}>
|
|
<ChevronRightIcon />
|
|
</IconButton>
|
|
<Typography style={{ fontWeight: 650, fontSize: 20 }}>Notifications</Typography>
|
|
<Button
|
|
variant="contained"
|
|
color="primary"
|
|
onClick={() => {
|
|
removeNotification(Array.from(notifications.keys()));
|
|
}}>
|
|
Clear
|
|
</Button>
|
|
</div>
|
|
<List>
|
|
<Divider />
|
|
{loading && (
|
|
<div style={{ textAlign: "center", margin: theme.spacing(2) }}>
|
|
<CircularProgress />
|
|
</div>
|
|
)}
|
|
{!loading && notifications.size < 1 && (
|
|
<Typography
|
|
color="textSecondary"
|
|
style={{ textAlign: "center", margin: theme.spacing(2) }}>
|
|
No Notifications
|
|
</Typography>
|
|
)}
|
|
{!loading &&
|
|
[...notifications.values()].map(notification => {
|
|
let cIcon = DeviceIcon;
|
|
// TODO: put this back in when the component stuff has been added
|
|
// if (notification.settings?.componentSource) {
|
|
// let newIcon = GetComponentIcon(
|
|
// notification.settings.componentSource.type,
|
|
// undefined,
|
|
// theme.palette.type
|
|
// );
|
|
// cIcon = newIcon ? newIcon : cIcon;
|
|
// } else if (notification.settings?.logo) {
|
|
// cIcon = notification.settings?.logo;
|
|
// }
|
|
let title = notification.settings?.title
|
|
? notification.settings?.title
|
|
: "Device " + notification.settings?.sourceKey;
|
|
//notification.settings?.sourceKey
|
|
return (
|
|
<React.Fragment key={notification.settings?.key}>
|
|
<ListItem
|
|
// button={hasSource(notification) as any}
|
|
onClick={() => notificationClick(notification)}
|
|
>
|
|
<ListItemIcon>
|
|
{/*<ImgIcon src={cIcon} />*/}
|
|
<Avatar alt={user.name()} src={cIcon} />
|
|
</ListItemIcon>
|
|
<ListItemText>
|
|
<Grid container direction="column">
|
|
<Grid container direction="row" alignItems="center" alignContent="center">
|
|
<Grid item>
|
|
<Typography>{title}</Typography>
|
|
</Grid>
|
|
<Grid item>
|
|
<Typography
|
|
color={"textSecondary"}
|
|
style={{ marginLeft: theme.spacing(0.5) }}>
|
|
{notification.settings?.subtitle &&
|
|
" - " + notification.settings?.subtitle}
|
|
</Typography>
|
|
</Grid>
|
|
</Grid>
|
|
{notification.status?.timestamp && (
|
|
<Grid
|
|
item
|
|
style={{
|
|
marginTop: theme.spacing(-0.75),
|
|
marginLeft: theme.spacing(1)
|
|
}}>
|
|
<Typography variant="caption" color="textSecondary">
|
|
{moment(notification.status.timestamp).fromNow()}
|
|
</Typography>
|
|
</Grid>
|
|
)}
|
|
{hasSource(notification) && (
|
|
<Grid item>
|
|
<Typography variant="subtitle1" color="textSecondary">
|
|
Click to view {notification.settings?.sourceType}
|
|
</Typography>
|
|
</Grid>
|
|
)}
|
|
</Grid>
|
|
</ListItemText>
|
|
<ListItemSecondaryAction>
|
|
<Tooltip title="Hide Notification">
|
|
<IconButton
|
|
onClick={() =>
|
|
removeNotification(
|
|
notification.settings?.key ? [notification.settings.key] : []
|
|
)
|
|
}>
|
|
<Remove />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</ListItemSecondaryAction>
|
|
</ListItem>
|
|
<Divider />
|
|
</React.Fragment>
|
|
);
|
|
})}
|
|
{!loading && !loadingMore && notifications.size > 0 && notifications.size < total && (
|
|
<ListItem onClick={() => setOffset(offset + 10)}>
|
|
<div style={{ margin: "auto" }}>
|
|
<Typography>Load More</Typography>
|
|
</div>
|
|
</ListItem>
|
|
)}{" "}
|
|
{loadingMore && (
|
|
<ListItem>
|
|
<div style={{ margin: "auto" }}>
|
|
<CircularProgress />
|
|
</div>
|
|
</ListItem>
|
|
)}{" "}
|
|
{!loadingMore && notifications.size > 0 && notifications.size >= total && (
|
|
<ListItem>
|
|
<div style={{ margin: "auto" }}>
|
|
<Typography color="textSecondary">End of list.</Typography>
|
|
</div>
|
|
</ListItem>
|
|
)}
|
|
</List>
|
|
</div>
|
|
</Drawer>
|
|
);
|
|
}
|