feat: show pending messages in appbar

This commit is contained in:
canove
2020-08-25 12:24:19 -03:00
parent ea4ff654ff
commit 6e8eba1187
11 changed files with 255 additions and 469 deletions

View File

@@ -13,12 +13,14 @@ exports.index = async (req, res) => {
status = "", status = "",
date = "", date = "",
searchParam = "", searchParam = "",
showAll,
} = req.query; } = req.query;
const userId = req.userId;
const limit = 20; const limit = 20;
const offset = limit * (pageNumber - 1); const offset = limit * (pageNumber - 1);
let whereCondition = {};
let includeCondition = [ let includeCondition = [
{ {
model: Contact, model: Contact,
@@ -27,16 +29,20 @@ exports.index = async (req, res) => {
}, },
]; ];
let whereCondition = { userId: userId };
if (showAll === "true") {
whereCondition = {};
}
if (status) { if (status) {
whereCondition = { whereCondition = {
...whereCondition, ...whereCondition,
status: status, status: status,
}; };
} }
// else if (status === "closed") {
// whereCondition = { ...whereCondition, status: "closed" }; if (searchParam) {
// }
else if (searchParam) {
includeCondition = [ includeCondition = [
...includeCondition, ...includeCondition,
{ {
@@ -56,7 +62,6 @@ exports.index = async (req, res) => {
]; ];
whereCondition = { whereCondition = {
...whereCondition,
[Sequelize.Op.or]: [ [Sequelize.Op.or]: [
{ {
"$contact.name$": Sequelize.where( "$contact.name$": Sequelize.where(

View File

@@ -15,7 +15,7 @@ module.exports = async (req, res, next) => {
if (error) { if (error) {
return res.status(401).json({ error: "Invalid token" }); return res.status(401).json({ error: "Invalid token" });
} }
req.userId = token.userId; req.userId = result.userId;
// todo >> find user in DB and store in req.user to use latter, or throw an error if user not exists anymore // todo >> find user in DB and store in req.user to use latter, or throw an error if user not exists anymore
next(); next();
}); });

View File

@@ -56,6 +56,7 @@ const NotificationsPopOver = () => {
const ticketId = +history.location.pathname.split("/")[2]; const ticketId = +history.location.pathname.split("/")[2];
const anchorEl = useRef(); const anchorEl = useRef();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [notifications, setNotifications] = useState([]);
useEffect(() => { useEffect(() => {
if (!("Notification" in window)) { if (!("Notification" in window)) {
@@ -87,7 +88,27 @@ const NotificationsPopOver = () => {
}; };
}, [history, ticketId, userId]); }, [history, ticketId, userId]);
const { tickets } = useTickets({ status: "open" }); const { tickets: openTickets } = useTickets({ status: "open" });
const { tickets: pendingTickets } = useTickets({ status: "pending" });
useEffect(() => {
if (openTickets.length > 0 || pendingTickets.length > 0) {
let aux = [];
openTickets.forEach(ticket => {
if (ticket.unreadMessages > 0) {
aux.push(ticket);
}
});
pendingTickets.forEach(ticket => {
if (ticket.unreadMessages > 0) {
aux.push(ticket);
}
});
setNotifications(aux);
}
}, [openTickets, pendingTickets]);
const showDesktopNotification = ({ message, contact, ticket }) => { const showDesktopNotification = ({ message, contact, ticket }) => {
const options = { const options = {
@@ -135,7 +156,7 @@ const NotificationsPopOver = () => {
aria-label="Open Notifications" aria-label="Open Notifications"
color="inherit" color="inherit"
> >
<Badge badgeContent={tickets.length} color="secondary"> <Badge badgeContent={notifications.length} color="secondary">
<ChatIcon /> <ChatIcon />
</Badge> </Badge>
</IconButton> </IconButton>
@@ -155,14 +176,12 @@ const NotificationsPopOver = () => {
onClose={handleClickAway} onClose={handleClickAway}
> >
<List dense className={classes.tabContainer}> <List dense className={classes.tabContainer}>
{tickets.length === 0 ? ( {notifications.length === 0 ? (
<ListItem> <ListItem>
<ListItemText> <ListItemText>No tickets with unread messages.</ListItemText>
You haven&apos;t received any messages yet.
</ListItemText>
</ListItem> </ListItem>
) : ( ) : (
tickets.map(ticket => ( notifications.map(ticket => (
<TicketListItem <TicketListItem
key={ticket.id} key={ticket.id}
ticket={ticket} ticket={ticket}

View File

@@ -86,7 +86,7 @@ const TicketListItem = ({
ticket, ticket,
handleAcepptTicket, handleAcepptTicket,
handleSelectTicket, handleSelectTicket,
ticketId, selectedTicketId,
}) => { }) => {
const classes = useStyles(); const classes = useStyles();
@@ -99,7 +99,7 @@ const TicketListItem = ({
if (ticket.status === "pending" && handleAcepptTicket) return; if (ticket.status === "pending" && handleAcepptTicket) return;
handleSelectTicket(e, ticket); handleSelectTicket(e, ticket);
}} }}
selected={ticketId && +ticketId === ticket.id} selected={selectedTicketId && +selectedTicketId === ticket.id}
className={classes.ticket} className={classes.ticket}
> >
<ListItemAvatar> <ListItemAvatar>

View File

@@ -1,4 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -11,11 +12,13 @@ import ConfirmationModal from "../ConfirmationModal";
const TicketOptionsMenu = ({ ticket, menuOpen, handleClose, anchorEl }) => { const TicketOptionsMenu = ({ ticket, menuOpen, handleClose, anchorEl }) => {
const [confirmationOpen, setConfirmationOpen] = useState(false); const [confirmationOpen, setConfirmationOpen] = useState(false);
const history = useHistory();
const handleDeleteTicket = async () => { const handleDeleteTicket = async () => {
try { try {
await api.delete(`/tickets/${ticket.id}`); await api.delete(`/tickets/${ticket.id}`);
toast.success("Ticket deletado com sucesso."); toast.success("Ticket deletado com sucesso.");
history.push("/chat");
} catch (err) { } catch (err) {
toast.error("Erro ao deletar o ticket"); toast.error("Erro ao deletar o ticket");
} }

View File

@@ -1,9 +1,8 @@
import React, { useState, useEffect } from "react"; import React, { useState } from "react";
import { useHistory, useParams } from "react-router-dom"; import { useHistory, useParams } from "react-router-dom";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
import List from "@material-ui/core/List";
import SearchIcon from "@material-ui/icons/Search"; import SearchIcon from "@material-ui/icons/Search";
import InputBase from "@material-ui/core/InputBase"; import InputBase from "@material-ui/core/InputBase";
import Tabs from "@material-ui/core/Tabs"; import Tabs from "@material-ui/core/Tabs";
@@ -14,19 +13,13 @@ import IconButton from "@material-ui/core/IconButton";
import AddIcon from "@material-ui/icons/Add"; import AddIcon from "@material-ui/icons/Add";
import FormControlLabel from "@material-ui/core/FormControlLabel"; import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from "@material-ui/core/Switch"; import Switch from "@material-ui/core/Switch";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import TicketsSkeleton from "../TicketsSkeleton";
import NewTicketModal from "../NewTicketModal"; import NewTicketModal from "../NewTicketModal";
// import TicketsList from "../TicketsList"; import TicketsList from "../TicketsList";
import TicketListItem from "../TicketListItem";
import TabPanel from "../TabPanel"; import TabPanel from "../TabPanel";
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n";
import api from "../../services/api"; import api from "../../services/api";
import useTickets from "../../hooks/useTickets";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
ticketsWrapper: { ticketsWrapper: {
@@ -40,7 +33,6 @@ const useStyles = makeStyles(theme => ({
}, },
tabsHeader: { tabsHeader: {
// display: "flex",
flex: "none", flex: "none",
backgroundColor: "#eee", backgroundColor: "#eee",
}, },
@@ -56,87 +48,21 @@ const useStyles = makeStyles(theme => ({
width: 120, width: 120,
}, },
halfTicketsList: {
height: "50%",
overflowY: "scroll",
"&::-webkit-scrollbar": {
width: "8px",
height: "8px",
},
"&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8",
},
borderTop: "1px solid rgba(0, 0, 0, 0.12)",
},
fullHeightTicketsList: {
flex: 1,
overflowY: "scroll",
"&::-webkit-scrollbar": {
width: "8px",
height: "8px",
},
"&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8",
},
borderTop: "2px solid rgba(0, 0, 0, 0.12)",
},
ticketsListHeader: {
display: "flex",
alignItems: "center",
fontWeight: 500,
fontSize: "16px",
height: "56px",
color: "rgb(67, 83, 105)",
padding: "0px 12px",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
},
ticketsCount: {
fontWeight: "normal",
color: "rgb(104, 121, 146)",
marginLeft: "8px",
fontSize: "14px",
},
ticketsListActions: { ticketsListActions: {
flex: "none", flex: "none",
marginLeft: "auto", marginLeft: "auto",
}, },
noTicketsDiv: { searchBox: {
display: "flex",
height: "100px",
margin: 40,
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
},
noTicketsText: {
textAlign: "center",
color: "rgb(104, 121, 146)",
fontSize: "14px",
lineHeight: "1.4",
},
noTicketsTitle: {
textAlign: "center",
fontSize: "16px",
fontWeight: "600",
margin: "0px",
},
contactsSearchBox: {
position: "relative", position: "relative",
display: "flex",
alignItems: "center",
background: "#fafafa", background: "#fafafa",
padding: "10px 13px", padding: "10px 13px",
}, },
serachInputWrapper: { serachInputWrapper: {
flex: 1,
background: "#fff", background: "#fff",
display: "flex", display: "flex",
borderRadius: 40, borderRadius: 40,
@@ -150,7 +76,7 @@ const useStyles = makeStyles(theme => ({
alignSelf: "center", alignSelf: "center",
}, },
contactsSearchInput: { searchInput: {
flex: 1, flex: 1,
border: "none", border: "none",
borderRadius: 30, borderRadius: 30,
@@ -167,46 +93,11 @@ const Tickets = () => {
const [tab, setTab] = useState("open"); const [tab, setTab] = useState("open");
const [newTicketModalOpen, setNewTicketModalOpen] = useState(false); const [newTicketModalOpen, setNewTicketModalOpen] = useState(false);
const [showAllTickets, setShowAllTickets] = useState(false); const [showAllTickets, setShowAllTickets] = useState(false);
const [pageNumber, setPageNumber] = useState(1);
const {
tickets: ticketsOpen,
hasMore: hasMoreOpen,
loading: loadingOpen,
dispatch: dispatchOpen,
} = useTickets({
pageNumber,
searchParam,
status: "open",
});
const {
tickets: ticketsPending,
hasMore: hasMorePending,
loading: loadingPending,
dispatch: dispatchPending,
} = useTickets({
pageNumber,
searchParam,
status: "pending",
});
useEffect(() => {
dispatchOpen({ type: "RESET" });
dispatchPending({ type: "RESET" });
setPageNumber(1);
}, [searchParam, tab, dispatchOpen, dispatchPending]);
const loadMore = () => {
setPageNumber(prevState => prevState + 1);
};
const handleSelectTicket = (e, ticket) => { const handleSelectTicket = (e, ticket) => {
history.push(`/chat/${ticket.id}`); history.push(`/chat/${ticket.id}`);
}; };
// console.log(tickets);
const handleSearchContact = e => { const handleSearchContact = e => {
if (e.target.value === "") { if (e.target.value === "") {
setSearchParam(e.target.value.toLowerCase()); setSearchParam(e.target.value.toLowerCase());
@@ -217,15 +108,6 @@ const Tickets = () => {
setTab("search"); setTab("search");
}; };
const handleScroll = e => {
if (!hasMoreOpen || loadingOpen) return;
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore();
}
};
const handleChangeTab = (e, newValue) => { const handleChangeTab = (e, newValue) => {
setTab(newValue); setTab(newValue);
}; };
@@ -242,17 +124,6 @@ const Tickets = () => {
history.push(`/chat/${ticketId}`); history.push(`/chat/${ticketId}`);
}; };
// const countTickets = (status, userId) => {
// const ticketsFound = tickets.filter(
// t =>
// (t.status === status && t.userId === userId) ||
// (t.status === status && showAllTickets)
// ).length;
// if (ticketsFound === 0) return "";
// return ticketsFound;
// };
return ( return (
<Paper elevation={0} variant="outlined" className={classes.ticketsWrapper}> <Paper elevation={0} variant="outlined" className={classes.ticketsWrapper}>
<NewTicketModal <NewTicketModal
@@ -288,151 +159,70 @@ const Tickets = () => {
/> />
</Tabs> </Tabs>
</Paper> </Paper>
<Paper square elevation={0} className={classes.contactsSearchBox}> <Paper square elevation={0} className={classes.searchBox}>
<div className={classes.serachInputWrapper}> <div className={classes.serachInputWrapper}>
<SearchIcon className={classes.searchIcon} /> <SearchIcon className={classes.searchIcon} />
<InputBase <InputBase
className={classes.contactsSearchInput} className={classes.searchInput}
placeholder={i18n.t("tickets.search.placeholder")} placeholder={i18n.t("tickets.search.placeholder")}
type="search" type="search"
onChange={handleSearchContact} onChange={handleSearchContact}
/> />
</div> </div>
<div className={classes.ticketsListActions}>
<FormControlLabel
label={i18n.t("tickets.buttons.showAll")}
labelPlacement="start"
control={
<Switch
size="small"
checked={showAllTickets}
onChange={() => setShowAllTickets(prevState => !prevState)}
name="showAllTickets"
color="primary"
/>
}
/>
<IconButton
aria-label="add ticket"
size="small"
onClick={e => setNewTicketModalOpen(true)}
style={{ marginLeft: 20 }}
>
<AddIcon />
</IconButton>
</div>
</Paper> </Paper>
<TabPanel value={tab} name="open" className={classes.ticketsWrapper}> <TabPanel value={tab} name="open" className={classes.ticketsWrapper}>
<Paper {/* <TicketsList
square status="open"
elevation={0} handleSelectTicket={handleSelectTicket}
className={classes.halfTicketsList} selectedTicketId={ticketId}
onScroll={handleScroll} showAll={showAllTickets}
> /> */}
<div className={classes.ticketsListHeader}> <TicketsList
{i18n.t("tickets.tabs.open.assignedHeader")} status="pending"
<span className={classes.ticketsCount}>{ticketsOpen.length}</span> handleSelectTicket={handleSelectTicket}
<div className={classes.ticketsListActions}> handleAcepptTicket={handleAcepptTicket}
<FormControlLabel selectedTicketId={ticketId}
label={i18n.t("tickets.buttons.showAll")} showAll={true}
labelPlacement="start" />
control={
<Switch
size="small"
checked={showAllTickets}
onChange={() => setShowAllTickets(prevState => !prevState)}
name="showAllTickets"
color="primary"
/>
}
/>
<IconButton
aria-label="add ticket"
onClick={e => setNewTicketModalOpen(true)}
style={{ marginLeft: 20 }}
>
<AddIcon />
</IconButton>
</div>
</div>
<List style={{ paddingTop: 0 }}>
{ticketsOpen.length === 0 ? (
<ListItem>
<ListItemText>
You haven&apos;t received any messages yet.
</ListItemText>
</ListItem>
) : (
ticketsOpen.map(ticket => (
<TicketListItem
ticket={ticket}
key={ticket.id}
// loading={loading}
handleSelectTicket={handleSelectTicket}
handleAcepptTicket={handleAcepptTicket}
ticketId={ticketId}
/>
))
)}
{loadingOpen && <TicketsSkeleton />}
</List>
</Paper>
<Paper
square
elevation={0}
className={classes.halfTicketsList}
onScroll={handleScroll}
>
<div className={classes.ticketsListHeader}>
{i18n.t("tickets.tabs.open.pendingHeader")}
<span className={classes.ticketsCount}>
{ticketsPending.length}
</span>
</div>
<List style={{ paddingTop: 0 }}>
{ticketsPending.length === 0 ? (
<ListItem>
<ListItemText>
You haven&apos;t received any messages yet.
</ListItemText>
</ListItem>
) : (
ticketsPending.map(ticket => (
<TicketListItem
ticket={ticket}
key={ticket.id}
// loading={loading}
handleSelectTicket={handleSelectTicket}
handleAcepptTicket={handleAcepptTicket}
ticketId={ticketId}
/>
))
)}
{loadingPending && <TicketsSkeleton />}
</List>
</Paper>
</TabPanel> </TabPanel>
<TabPanel value={tab} name="closed" className={classes.ticketsWrapper}> <TabPanel value={tab} name="closed" className={classes.ticketsWrapper}>
<Paper <TicketsList
square status="closed"
elevation={0} handleSelectTicket={handleSelectTicket}
className={classes.fullHeightTicketsList} selectedTicketId={ticketId}
onScroll={handleScroll} showAll={true}
> />
<List style={{ paddingTop: 0 }}>
{/* <TicketsList
tickets={tickets}
loading={loading}
handleSelectTicket={handleSelectTicket}
showAllTickets={showAllTickets}
ticketId={ticketId}
handleAcepptTicket={handleAcepptTicket}
status="closed"
userId={null}
/>
{loading && <TicketsSkeleton />} */}
</List>
</Paper>
</TabPanel> </TabPanel>
<TabPanel value={tab} name="search" className={classes.ticketsWrapper}> <TabPanel value={tab} name="search" className={classes.ticketsWrapper}>
<Paper <TicketsList
square handleSelectTicket={handleSelectTicket}
elevation={0} selectedTicketId={ticketId}
className={classes.fullHeightTicketsList} searchParam={searchParam}
onScroll={handleScroll} showAll={true}
> />
<List style={{ paddingTop: 0 }}>
{/* <TicketsList
tickets={tickets}
loading={loading}
handleSelectTicket={handleSelectTicket}
showAllTickets={showAllTickets}
ticketId={ticketId}
handleAcepptTicket={handleAcepptTicket}
noTicketsTitle={i18n.t("tickets.tabs.search.noTicketsTitle")}
noTicketsMessage={i18n.t("tickets.tabs.search.noTicketsMessage")}
status="all"
/>
{loading && <TicketsSkeleton />} */}
</List>
</Paper>
</TabPanel> </TabPanel>
</Paper> </Paper>
); );

View File

@@ -1,38 +1,55 @@
import React from "react"; import React, { useState, useEffect } from "react";
import { parseISO, format, isSameDay } from "date-fns";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { green } from "@material-ui/core/colors"; import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem"; import Paper from "@material-ui/core/Paper";
import ListItemText from "@material-ui/core/ListItemText";
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
import Typography from "@material-ui/core/Typography";
import Avatar from "@material-ui/core/Avatar";
import Divider from "@material-ui/core/Divider";
import Badge from "@material-ui/core/Badge";
import Button from "@material-ui/core/Button";
import TicketListItem from "../TicketListItem";
import TicketsSkeleton from "../TicketsSkeleton";
import useTickets from "../../hooks/useTickets";
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
ticket: { ticketsListWrapper: {
position: "relative", position: "relative",
"& .hidden-button": {
display: "none",
},
"&:hover .hidden-button": {
display: "flex",
position: "absolute",
left: "50%",
},
},
noTicketsDiv: {
display: "flex", display: "flex",
height: "100px", height: "100%",
margin: 40,
flexDirection: "column", flexDirection: "column",
overflow: "hidden",
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
},
ticketsList: {
flex: 1,
overflowY: "scroll",
"&::-webkit-scrollbar": {
width: "8px",
height: "8px",
},
"&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8",
},
borderTop: "2px solid rgba(0, 0, 0, 0.12)",
},
ticketsListHeader: {
display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", fontWeight: 500,
fontSize: "16px",
height: "56px",
color: "rgb(67, 83, 105)",
padding: "0px 12px",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
},
ticketsCount: {
fontWeight: "normal",
color: "rgb(104, 121, 146)",
marginLeft: "8px",
fontSize: "14px",
}, },
noTicketsText: { noTicketsText: {
@@ -49,163 +66,100 @@ const useStyles = makeStyles(theme => ({
margin: "0px", margin: "0px",
}, },
contactNameWrapper: { noTicketsDiv: {
display: "flex", display: "flex",
justifyContent: "space-between", height: "100px",
}, margin: 40,
flexDirection: "column",
lastMessageTime: { alignItems: "center",
justifySelf: "flex-end", justifyContent: "center",
},
closedBadge: {
alignSelf: "center",
justifySelf: "flex-end",
marginRight: 32,
marginLeft: "auto",
},
contactLastMessage: {
paddingRight: 20,
},
newMessagesCount: {
alignSelf: "center",
marginRight: 8,
marginLeft: "auto",
},
badgeStyle: {
color: "white",
backgroundColor: green[500],
}, },
})); }));
const TicketsList = ({ const TicketsList = ({
tickets,
status, status,
userId, searchParam,
handleSelectTicket, handleSelectTicket,
showAllTickets,
ticketId,
noTicketsTitle,
loading,
noTicketsMessage,
handleAcepptTicket, handleAcepptTicket,
selectedTicketId,
showAll,
}) => { }) => {
const classes = useStyles(); const classes = useStyles();
const [pageNumber, setPageNumber] = useState(1);
let viewTickets = []; const { tickets, hasMore, loading, dispatch } = useTickets({
pageNumber,
tickets.forEach(ticket => { searchParam,
if ( status,
(ticket.status === status && ticket.userId === userId) || showAll,
(ticket.status === status && showAllTickets) ||
(ticket.status === "closed" && status === "closed") ||
status === "all"
)
viewTickets.push(
<React.Fragment key={ticket.id}>
<ListItem
dense
button
onClick={e => {
if (ticket.status === "pending" && handleAcepptTicket) return;
handleSelectTicket(e, ticket);
}}
selected={ticketId && +ticketId === ticket.id}
className={classes.ticket}
>
<ListItemAvatar>
<Avatar
src={
ticket.contact.profilePicUrl && ticket.contact.profilePicUrl
}
></Avatar>
</ListItemAvatar>
<ListItemText
primary={
<span className={classes.contactNameWrapper}>
<Typography
noWrap
component="span"
variant="body2"
color="textPrimary"
>
{ticket.contact.name}
</Typography>
{ticket.status === "closed" && (
<Badge
className={classes.closedBadge}
badgeContent={"closed"}
color="primary"
/>
)}
{ticket.lastMessage && (
<Typography
className={classes.lastMessageTime}
component="span"
variant="body2"
color="textSecondary"
>
{isSameDay(parseISO(ticket.updatedAt), new Date()) ? (
<>{format(parseISO(ticket.updatedAt), "HH:mm")}</>
) : (
<>{format(parseISO(ticket.updatedAt), "dd/MM/yyyy")}</>
)}
</Typography>
)}
</span>
}
secondary={
<span className={classes.contactNameWrapper}>
<Typography
className={classes.contactLastMessage}
noWrap
component="span"
variant="body2"
color="textSecondary"
>
{ticket.lastMessage || <br />}
</Typography>
<Badge
className={classes.newMessagesCount}
badgeContent={ticket.unreadMessages}
classes={{
badge: classes.badgeStyle,
}}
/>
</span>
}
/>
{ticket.status === "pending" && handleAcepptTicket ? (
<Button
variant="contained"
size="small"
color="primary"
className="hidden-button"
onClick={e => handleAcepptTicket(ticket.id)}
>
{i18n.t("ticketsList.buttons.accept")}
</Button>
) : null}
</ListItem>
<Divider variant="inset" component="li" />
</React.Fragment>
);
}); });
if (viewTickets.length > 0) { useEffect(() => {
return viewTickets; dispatch({ type: "RESET" });
} else if (!loading) { setPageNumber(1);
return ( }, [status, searchParam, dispatch, showAll]);
<div className={classes.noTicketsDiv}>
<span className={classes.noTicketsTitle}>{noTicketsTitle}</span> const loadMore = () => {
<p className={classes.noTicketsText}>{noTicketsMessage}</p> setPageNumber(prevState => prevState + 1);
</div> };
);
} else return null; const handleScroll = e => {
if (!hasMore || loading) return;
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore();
}
};
return (
<div className={classes.ticketsListWrapper}>
<Paper
square
name="closed"
elevation={0}
className={classes.ticketsList}
onScroll={handleScroll}
>
<List style={{ paddingTop: 0 }}>
{status === "open" && (
<div className={classes.ticketsListHeader}>
{i18n.t("tickets.tabs.open.assignedHeader")}
<span className={classes.ticketsCount}>{tickets.length}</span>
</div>
)}
{status === "pending" && (
<div className={classes.ticketsListHeader}>
{i18n.t("tickets.tabs.open.pendingHeader")}
<span className={classes.ticketsCount}>{tickets.length}</span>
</div>
)}
{tickets.length === 0 && !loading ? (
<div className={classes.noTicketsDiv}>
<span className={classes.noTicketsTitle}>Nothing here!</span>
<p className={classes.noTicketsText}>
No tickets found with this status or search term.
</p>
</div>
) : (
<>
{tickets.map(ticket => (
<TicketListItem
ticket={ticket}
key={ticket.id}
handleSelectTicket={handleSelectTicket}
handleAcepptTicket={handleAcepptTicket}
selectedTicketId={selectedTicketId}
/>
))}
</>
)}
{loading && <TicketsSkeleton />}
</List>
</Paper>
</div>
);
}; };
export default TicketsList; export default TicketsList;

View File

@@ -16,7 +16,7 @@ import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
import AccountCircle from "@material-ui/icons/AccountCircle"; import AccountCircle from "@material-ui/icons/AccountCircle";
import MainListItems from "./MainListItems"; import MainListItems from "./MainListItems";
import NotificationsPopOver from "./NotificationsPopOver"; import NotificationsPopOver from "../NotificationsPopOver";
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext";
const drawerWidth = 240; const drawerWidth = 240;
@@ -181,7 +181,7 @@ const MainDrawer = ({ appTitle, children }) => {
> >
{appTitle} {appTitle}
</Typography> </Typography>
<NotificationsPopOver /> {/* <NotificationsPopOver /> */}
<div> <div>
<IconButton <IconButton

View File

@@ -1,6 +1,5 @@
import { useState, useEffect, useReducer } from "react"; import { useState, useEffect, useReducer } from "react";
import openSocket from "socket.io-client"; import openSocket from "socket.io-client";
import { useHistory } from "react-router-dom";
import api from "../../services/api"; import api from "../../services/api";
@@ -24,17 +23,22 @@ const reducer = (state, action) => {
} }
if (action.type === "UPDATE_TICKETS") { if (action.type === "UPDATE_TICKETS") {
const ticket = action.payload; const { ticket, status, loggedUser } = action.payload;
const ticketIndex = state.findIndex(t => t.id === ticket.id); const ticketIndex = state.findIndex(t => t.id === ticket.id);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
if (ticket.status !== state[ticketIndex]) { if (ticket.status !== state[ticketIndex].status) {
state.splice(ticketIndex, 1); state.splice(ticketIndex, 1);
} else { } else {
state[ticketIndex] = ticket; state[ticketIndex] = ticket;
state.unshift(state.splice(ticketIndex, 1)[0]); state.unshift(state.splice(ticketIndex, 1)[0]);
} }
} else { } else if (
ticket.status === status &&
(ticket.userId === loggedUser ||
!ticket.userId ||
ticket.status === "closed")
) {
state.unshift(ticket); state.unshift(ticket);
} }
return [...state]; return [...state];
@@ -65,13 +69,14 @@ const reducer = (state, action) => {
} }
}; };
const useTickets = ({ searchParam, pageNumber, status, date }) => { const useTickets = ({ searchParam, pageNumber, status, date, showAll }) => {
const history = useHistory(); const userId = +localStorage.getItem("userId");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [hasMore, setHasMore] = useState(false); const [hasMore, setHasMore] = useState(false);
const [tickets, dispatch] = useReducer(reducer, []); const [tickets, dispatch] = useReducer(reducer, []);
console.log("rendering");
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
@@ -83,6 +88,7 @@ const useTickets = ({ searchParam, pageNumber, status, date }) => {
pageNumber, pageNumber,
status, status,
date, date,
showAll,
}, },
}); });
dispatch({ dispatch({
@@ -98,7 +104,7 @@ const useTickets = ({ searchParam, pageNumber, status, date }) => {
fetchTickets(); fetchTickets();
}, 500); }, 500);
return () => clearTimeout(delayDebounceFn); return () => clearTimeout(delayDebounceFn);
}, [searchParam, pageNumber, status, date]); }, [searchParam, pageNumber, status, date, showAll]);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
@@ -110,11 +116,13 @@ const useTickets = ({ searchParam, pageNumber, status, date }) => {
} }
if (data.action === "updateStatus" || data.action === "create") { if (data.action === "updateStatus" || data.action === "create") {
console.log("to aqui", status, data.ticket);
dispatch({ dispatch({
type: "UPDATE_TICKETS", type: "UPDATE_TICKETS",
payload: data.ticket, payload: {
status: status, ticket: data.ticket,
status: status,
loggedUser: userId,
},
}); });
} }
@@ -125,14 +133,21 @@ const useTickets = ({ searchParam, pageNumber, status, date }) => {
socket.on("appMessage", data => { socket.on("appMessage", data => {
if (data.action === "create") { if (data.action === "create") {
dispatch({ type: "UPDATE_TICKETS", payload: data.ticket }); dispatch({
type: "UPDATE_TICKETS",
payload: {
ticket: data.ticket,
status: status,
loggedUser: userId,
},
});
} }
}); });
return () => { return () => {
socket.disconnect(); socket.disconnect();
}; };
}, [status]); }, [status, userId]);
return { loading, tickets, hasMore, dispatch }; return { loading, tickets, hasMore, dispatch };
}; };

View File

@@ -100,7 +100,7 @@ const Contacts = () => {
} }
}; };
fetchContacts(); fetchContacts();
}, 1000); }, 500);
return () => clearTimeout(delayDebounceFn); return () => clearTimeout(delayDebounceFn);
}, [searchParam, page, rowsPerPage]); }, [searchParam, page, rowsPerPage]);

View File

@@ -37,7 +37,7 @@ const messages = {
dashboard: { dashboard: {
charts: { charts: {
perDay: { perDay: {
title: "Tickets today:", title: "Tickets today: ",
}, },
}, },
}, },