diff --git a/backend/src/controllers/MessageController.ts b/backend/src/controllers/MessageController.ts index 4c33aef..b2080d8 100644 --- a/backend/src/controllers/MessageController.ts +++ b/backend/src/controllers/MessageController.ts @@ -66,7 +66,7 @@ export const store = async (req: Request, res: Response): Promise => { }); const io = getIO(); - io.to(ticketId).to("notification").emit("appMessage", { + io.to(ticketId).to("notification").to(ticket.status).emit("appMessage", { action: "create", message, ticket, diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index ef30029..f87726b 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -18,6 +18,7 @@ type IndexQuery = { interface TicketData { contactId: number; status: string; + userId: number; } export const index = async (req: Request, res: Response): Promise => { @@ -46,12 +47,12 @@ export const index = async (req: Request, res: Response): Promise => { }; export const store = async (req: Request, res: Response): Promise => { - const { contactId, status }: TicketData = req.body; + const { contactId, status, userId }: TicketData = req.body; - const ticket = await CreateTicketService({ contactId, status }); + const ticket = await CreateTicketService({ contactId, status, userId }); const io = getIO(); - io.to("notification").emit("ticket", { + io.to(ticket.status).emit("ticket", { action: "create", ticket }); @@ -66,12 +67,24 @@ export const update = async ( const { ticketId } = req.params; const ticketData: TicketData = req.body; - const ticket = await UpdateTicketService({ ticketData, ticketId }); + const { ticket, oldStatus, ticketUser } = await UpdateTicketService({ + ticketData, + ticketId + }); const io = getIO(); - io.to("notification").emit("ticket", { + + if (ticket.status !== oldStatus) { + io.to(oldStatus).emit("ticket", { + action: "delete", + ticketId: ticket.id + }); + } + + io.to(ticket.status).to(ticketId).emit("ticket", { action: "updateStatus", - ticket + ticket, + user: ticketUser }); return res.status(200).json(ticket); @@ -83,13 +96,15 @@ export const remove = async ( ): Promise => { const { ticketId } = req.params; - await DeleteTicketService(ticketId); + const ticket = await DeleteTicketService(ticketId); const io = getIO(); - io.to("notification").emit("ticket", { - action: "delete", - ticketId: +ticketId - }); + io.to(ticket.status) + .to(ticketId) + .emit("ticket", { + action: "delete", + ticketId: +ticketId + }); return res.status(200).json({ message: "ticket deleted" }); }; diff --git a/backend/src/helpers/SetTicketMessagesAsRead.ts b/backend/src/helpers/SetTicketMessagesAsRead.ts index c6bbff9..fea43e3 100644 --- a/backend/src/helpers/SetTicketMessagesAsRead.ts +++ b/backend/src/helpers/SetTicketMessagesAsRead.ts @@ -24,7 +24,7 @@ const SetTicketMessagesAsRead = async (ticket: Ticket): Promise => { } const io = getIO(); - io.to("notification").emit("ticket", { + io.to(ticket.status).to("notification").emit("ticket", { action: "updateUnread", ticketId: ticket.id }); diff --git a/backend/src/libs/socket.ts b/backend/src/libs/socket.ts index cdeacc1..9e983ad 100644 --- a/backend/src/libs/socket.ts +++ b/backend/src/libs/socket.ts @@ -18,6 +18,11 @@ export const initIO = (httpServer: Server): SocketIO => { socket.join("notification"); }); + socket.on("joinTickets", status => { + console.log(`A client joined to ${status} tickets channel.`); + socket.join(status); + }); + socket.on("disconnect", () => { console.log("Client disconnected"); }); diff --git a/backend/src/services/ContactServices/UpdateContactService.ts b/backend/src/services/ContactServices/UpdateContactService.ts index 18d5bc2..cc39bc7 100644 --- a/backend/src/services/ContactServices/UpdateContactService.ts +++ b/backend/src/services/ContactServices/UpdateContactService.ts @@ -27,7 +27,7 @@ const UpdateContactService = async ({ const contact = await Contact.findOne({ where: { id: contactId }, - attributes: ["id", "name", "number", "email"], + attributes: ["id", "name", "number", "email", "profilePicUrl"], include: ["extraInfo"] }); diff --git a/backend/src/services/MessageServices/CreateMessageService.ts b/backend/src/services/MessageServices/CreateMessageService.ts index 700e07a..957ddc4 100644 --- a/backend/src/services/MessageServices/CreateMessageService.ts +++ b/backend/src/services/MessageServices/CreateMessageService.ts @@ -22,7 +22,7 @@ const CreateMessageService = async ({ const ticket = await ShowTicketService(ticketId); if (!ticket) { - throw new AppError("No ticket found with this ID"); + throw new AppError("No ticket found with this ID", 404); } const message: Message = await ticket.$create("message", messageData); diff --git a/backend/src/services/MessageServices/ListMessagesService.ts b/backend/src/services/MessageServices/ListMessagesService.ts index 5791420..4d5f694 100644 --- a/backend/src/services/MessageServices/ListMessagesService.ts +++ b/backend/src/services/MessageServices/ListMessagesService.ts @@ -1,4 +1,5 @@ import { where, fn, col } from "sequelize"; +import AppError from "../../errors/AppError"; import Message from "../../models/Message"; import Ticket from "../../models/Ticket"; import ShowTicketService from "../TicketServices/ShowTicketService"; @@ -24,7 +25,7 @@ const ListMessagesService = async ({ const ticket = await ShowTicketService(ticketId); if (!ticket) { - throw new Error("No ticket found with this ID"); + throw new AppError("No ticket found with this ID", 404); } const whereCondition = { diff --git a/backend/src/services/TicketServices/CreateTicketService.ts b/backend/src/services/TicketServices/CreateTicketService.ts index 6dfefff..9131669 100644 --- a/backend/src/services/TicketServices/CreateTicketService.ts +++ b/backend/src/services/TicketServices/CreateTicketService.ts @@ -4,12 +4,14 @@ import Ticket from "../../models/Ticket"; interface Request { contactId: number; - status?: string; + status: string; + userId: number; } const CreateTicketService = async ({ contactId, - status + status, + userId }: Request): Promise => { const defaultWhatsapp = await GetDefaultWhatsApp(); @@ -19,7 +21,8 @@ const CreateTicketService = async ({ const { id }: Ticket = await defaultWhatsapp.$create("ticket", { contactId, - status + status, + userId }); const ticket = await Ticket.findByPk(id, { include: ["contact"] }); diff --git a/backend/src/services/TicketServices/DeleteTicketService.ts b/backend/src/services/TicketServices/DeleteTicketService.ts index 9e1f579..1906f08 100644 --- a/backend/src/services/TicketServices/DeleteTicketService.ts +++ b/backend/src/services/TicketServices/DeleteTicketService.ts @@ -1,7 +1,7 @@ import Ticket from "../../models/Ticket"; import AppError from "../../errors/AppError"; -const DeleteTicketService = async (id: string): Promise => { +const DeleteTicketService = async (id: string): Promise => { const ticket = await Ticket.findOne({ where: { id } }); @@ -11,6 +11,8 @@ const DeleteTicketService = async (id: string): Promise => { } await ticket.destroy(); + + return ticket; }; export default DeleteTicketService; diff --git a/backend/src/services/TicketServices/ShowTicketService.ts b/backend/src/services/TicketServices/ShowTicketService.ts index 9379aff..dff0214 100644 --- a/backend/src/services/TicketServices/ShowTicketService.ts +++ b/backend/src/services/TicketServices/ShowTicketService.ts @@ -21,7 +21,7 @@ const ShowTicketService = async (id: string | number): Promise => { }); if (!ticket) { - throw new AppError("No ticket found with this ID"); + throw new AppError("No ticket found with this ID", 404); } return ticket; diff --git a/backend/src/services/TicketServices/UpdateTicketService.ts b/backend/src/services/TicketServices/UpdateTicketService.ts index f7b893d..6fc9353 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -1,6 +1,7 @@ import AppError from "../../errors/AppError"; import Contact from "../../models/Contact"; import Ticket from "../../models/Ticket"; +import User from "../../models/User"; interface TicketData { status?: string; @@ -12,10 +13,16 @@ interface Request { ticketId: string; } +interface Response { + ticket: Ticket; + ticketUser: User | null; + oldStatus: string; +} + const UpdateTicketService = async ({ ticketData, ticketId -}: Request): Promise => { +}: Request): Promise => { const { status, userId } = ticketData; const ticket = await Ticket.findOne({ @@ -33,12 +40,15 @@ const UpdateTicketService = async ({ throw new AppError("No ticket found with this ID.", 404); } + const oldStatus = ticket.status; + await ticket.update({ status, userId }); + const ticketUser = await ticket.$get("user", { attributes: ["id", "name"] }); - return ticket; + return { ticket, oldStatus, ticketUser }; }; export default UpdateTicketService; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 8fc37e2..6c7d218 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -138,12 +138,15 @@ const handleMessage = async ( } const io = getIO(); - io.to(ticket.id.toString()).to("notification").emit("appMessage", { - action: "create", - message: newMessage, - ticket, - contact - }); + io.to(ticket.id.toString()) + .to(ticket.status) + .to("notification") + .emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact + }); }; const isValidMsg = (msg: WbotMessage): boolean => { diff --git a/frontend/src/components/BackdropLoading/index.js b/frontend/src/components/BackdropLoading/index.js new file mode 100644 index 0000000..183b02b --- /dev/null +++ b/frontend/src/components/BackdropLoading/index.js @@ -0,0 +1,23 @@ +import React from "react"; + +import Backdrop from "@material-ui/core/Backdrop"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import { makeStyles } from "@material-ui/core/styles"; + +const useStyles = makeStyles(theme => ({ + backdrop: { + zIndex: theme.zIndex.drawer + 1, + color: "#fff", + }, +})); + +const BackdropLoading = () => { + const classes = useStyles(); + return ( + + + + ); +}; + +export default BackdropLoading; diff --git a/frontend/src/components/MessagesList/index.js b/frontend/src/components/MessagesList/index.js index 00b4e55..a310c93 100644 --- a/frontend/src/components/MessagesList/index.js +++ b/frontend/src/components/MessagesList/index.js @@ -293,11 +293,13 @@ const MessagesList = () => { const { data } = await api.get("/messages/" + ticketId, { params: { pageNumber }, }); + setContact(data.ticket.contact); setTicket(data.ticket); dispatch({ type: "LOAD_MESSAGES", payload: data.messages }); setHasMore(data.hasMore); setLoading(false); + if (pageNumber === 1 && data.messages.length > 1) { scrollToBottom(); } @@ -318,7 +320,7 @@ const MessagesList = () => { useEffect(() => { const socket = openSocket(process.env.REACT_APP_BACKEND_URL); - socket.emit("joinChatBox", ticketId, () => {}); + socket.emit("joinChatBox", ticketId); socket.on("appMessage", data => { if (data.action === "create") { @@ -330,6 +332,17 @@ const MessagesList = () => { } }); + socket.on("ticket", data => { + if (data.action === "updateStatus") { + setTicket({ ...data.ticket, user: data.user }); + } + + if (data.action === "delete") { + toast.success("Ticket deleted sucessfully."); + history.push("/tickets"); + } + }); + socket.on("contact", data => { if (data.action === "update") { setContact(data.contact); @@ -339,7 +352,7 @@ const MessagesList = () => { return () => { socket.disconnect(); }; - }, [ticketId]); + }, [ticketId, history]); const loadMore = () => { setPageNumber(prevPageNumber => prevPageNumber + 1); @@ -585,11 +598,12 @@ const MessagesList = () => { subheader={ loading ? ( - ) : ( - ticket.user && + ) : ticket.user ? ( `${i18n.t("messagesList.header.assignedTo")} ${ ticket.user.name }` + ) : ( + "Pending" ) } /> diff --git a/frontend/src/components/NotificationsPopOver/index.js b/frontend/src/components/NotificationsPopOver/index.js index b1ee0d3..932547e 100644 --- a/frontend/src/components/NotificationsPopOver/index.js +++ b/frontend/src/components/NotificationsPopOver/index.js @@ -1,5 +1,6 @@ import React, { useState, useRef, useCallback, useEffect } from "react"; +import { useHistory } from "react-router-dom"; import { format } from "date-fns"; import openSocket from "socket.io-client"; @@ -13,11 +14,7 @@ import Badge from "@material-ui/core/Badge"; import ChatIcon from "@material-ui/icons/Chat"; import TicketListItem from "../TicketListItem"; - -// import { toast } from "react-toastify"; -import { useHistory } from "react-router-dom"; import { i18n } from "../../translate/i18n"; - import useTickets from "../../hooks/useTickets"; const useStyles = makeStyles(theme => ({ @@ -46,10 +43,13 @@ const NotificationsPopOver = () => { const history = useHistory(); const userId = +localStorage.getItem("userId"); const soundAlert = useRef(new Audio(require("../../assets/sound.mp3"))); - const ticketId = +history.location.pathname.split("/")[2]; + const ticketIdUrl = +history.location.pathname.split("/")[2]; + const ticketIdRef = useRef(ticketIdUrl); const anchorEl = useRef(); const [isOpen, setIsOpen] = useState(false); - // const [notifications, setNotifications] = useState([]); + const [notifications, setNotifications] = useState([]); + + const { tickets } = useTickets({ withUnreadMessages: "true" }); useEffect(() => { if (!("Notification" in window)) { @@ -59,54 +59,87 @@ const NotificationsPopOver = () => { } }, []); + useEffect(() => { + setNotifications(tickets); + }, [tickets]); + + useEffect(() => { + ticketIdRef.current = ticketIdUrl; + }, [ticketIdUrl]); + useEffect(() => { const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + socket.emit("joinNotification"); + socket.on("ticket", data => { + if (data.action === "updateUnread") { + setNotifications(prevState => { + const ticketIndex = prevState.findIndex(t => t.id === data.ticketId); + if (ticketIndex !== -1) { + prevState.splice(ticketIndex, 1); + return [...prevState]; + } + return prevState; + }); + } + }); + socket.on("appMessage", data => { - if (data.action === "create") { + if ( + data.action === "create" && + (data.ticket.userId === userId || !data.ticket.userId) + ) { + setNotifications(prevState => { + const ticketIndex = prevState.findIndex(t => t.id === data.ticket.id); + if (ticketIndex !== -1) { + prevState[ticketIndex] = data.ticket; + return [...prevState]; + } + return [data.ticket, ...prevState]; + }); + if ( - (ticketId && - data.message.ticketId === +ticketId && + (ticketIdRef.current && + data.message.ticketId === ticketIdRef.current && document.visibilityState === "visible") || (data.ticket.userId !== userId && data.ticket.userId) ) return; - showDesktopNotification(data); + else { + // show desktop notification + const { message, contact, ticket } = data; + const options = { + body: `${message.body} - ${format(new Date(), "HH:mm")}`, + icon: contact.profilePicUrl, + tag: ticket.id, + }; + let notification = new Notification( + `${i18n.t("tickets.notification.message")} ${contact.name}`, + options + ); + + notification.onclick = function (event) { + event.preventDefault(); // + window.focus(); + history.push(`/tickets/${ticket.id}`); + }; + + document.addEventListener("visibilitychange", () => { + if (document.visibilityState === "visible") { + notification.close(); + } + }); + + soundAlert.current.play(); + } } }); return () => { socket.disconnect(); }; - }, [history, ticketId, userId]); - - const { tickets: notifications } = useTickets({ withUnreadMessages: "true" }); - - const showDesktopNotification = ({ message, contact, ticket }) => { - const options = { - body: `${message.body} - ${format(new Date(), "HH:mm")}`, - icon: contact.profilePicUrl, - tag: ticket.id, - }; - let notification = new Notification( - `${i18n.t("tickets.notification.message")} ${contact.name}`, - options - ); - - notification.onclick = function (event) { - event.preventDefault(); // - window.open(`/tickets/${ticket.id}`, "_self"); - }; - - document.addEventListener("visibilitychange", () => { - if (document.visibilityState === "visible") { - notification.close(); - } - }); - - soundAlert.current.play(); - }; + }, [history, userId]); const handleClick = useCallback(() => { setIsOpen(!isOpen); @@ -153,8 +186,6 @@ const NotificationsPopOver = () => { No tickets with unread messages. ) : ( - notifications && - notifications.length > 0 && notifications.map(ticket => ( diff --git a/frontend/src/components/TicketOptionsMenu/index.js b/frontend/src/components/TicketOptionsMenu/index.js index fc92752..9b88f59 100644 --- a/frontend/src/components/TicketOptionsMenu/index.js +++ b/frontend/src/components/TicketOptionsMenu/index.js @@ -1,5 +1,4 @@ import React, { useState } from "react"; -import { useHistory } from "react-router-dom"; import { toast } from "react-toastify"; @@ -12,13 +11,10 @@ import ConfirmationModal from "../ConfirmationModal"; const TicketOptionsMenu = ({ ticket, menuOpen, handleClose, anchorEl }) => { const [confirmationOpen, setConfirmationOpen] = useState(false); - const history = useHistory(); const handleDeleteTicket = async () => { try { await api.delete(`/tickets/${ticket.id}`); - toast.success("Ticket deletado com sucesso."); - history.push("/tickets"); } catch (err) { toast.error("Erro ao deletar o ticket"); } diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/TicketsList/index.js index d34b9bd..0aa075a 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/TicketsList/index.js @@ -1,4 +1,5 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useReducer } from "react"; +import openSocket from "socket.io-client"; import { makeStyles } from "@material-ui/core/styles"; import List from "@material-ui/core/List"; @@ -69,11 +70,97 @@ const useStyles = makeStyles(theme => ({ }, })); +const reducer = (state, action) => { + if (action.type === "LOAD_TICKETS") { + const newTickets = action.payload; + + newTickets.forEach(ticket => { + const ticketIndex = state.findIndex(t => t.id === ticket.id); + if (ticketIndex !== -1) { + state[ticketIndex] = ticket; + if (ticket.unreadMessages > 0) { + state.unshift(state.splice(ticketIndex, 1)[0]); + } + } else { + state.push(ticket); + } + }); + + return [...state]; + } + + if (action.type === "RESET_UNREAD") { + const ticketId = action.payload; + + const ticketIndex = state.findIndex(t => t.id === ticketId); + if (ticketIndex !== -1) { + state[ticketIndex].unreadMessages = 0; + } + + return [...state]; + } + + if (action.type === "UPDATE_TICKET") { + const ticket = action.payload; + + const ticketIndex = state.findIndex(t => t.id === ticket.id); + if (ticketIndex !== -1) { + state[ticketIndex] = ticket; + } else { + state.unshift(ticket); + } + + return [...state]; + } + + if (action.type === "UPDATE_TICKET_MESSAGES_COUNT") { + const ticket = action.payload; + + const ticketIndex = state.findIndex(t => t.id === ticket.id); + if (ticketIndex !== -1) { + state[ticketIndex] = ticket; + state.unshift(state.splice(ticketIndex, 1)[0]); + } + + return [...state]; + } + + if (action.type === "UPDATE_TICKET_CONTACT") { + const contact = action.payload; + const ticketIndex = state.findIndex(t => t.contactId === contact.id); + if (ticketIndex !== -1) { + state[ticketIndex].contact = contact; + } + return [...state]; + } + + if (action.type === "DELETE_TICKET") { + const ticketId = action.payload; + const ticketIndex = state.findIndex(t => t.id === ticketId); + if (ticketIndex !== -1) { + state.splice(ticketIndex, 1); + } + + return [...state]; + } + + if (action.type === "RESET") { + return []; + } +}; + const TicketsList = ({ status, searchParam, showAll }) => { + const userId = +localStorage.getItem("userId"); const classes = useStyles(); const [pageNumber, setPageNumber] = useState(1); + const [ticketsList, dispatch] = useReducer(reducer, []); - const { tickets, hasMore, loading, dispatch } = useTickets({ + useEffect(() => { + dispatch({ type: "RESET" }); + setPageNumber(1); + }, [status, searchParam, dispatch, showAll]); + + const { tickets, hasMore, loading } = useTickets({ pageNumber, searchParam, status, @@ -81,9 +168,61 @@ const TicketsList = ({ status, searchParam, showAll }) => { }); useEffect(() => { - dispatch({ type: "RESET" }); - setPageNumber(1); - }, [status, searchParam, dispatch, showAll]); + dispatch({ + type: "LOAD_TICKETS", + payload: tickets, + }); + }, [tickets]); + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + socket.emit("joinTickets", status); + + socket.on("ticket", data => { + if (data.action === "updateUnread") { + dispatch({ + type: "RESET_UNREAD", + payload: data.ticketId, + }); + } + + if ( + (data.action === "updateStatus" || data.action === "create") && + (!data.ticket.userId || data.ticket.userId === userId || showAll) + ) { + dispatch({ + type: "UPDATE_TICKET", + payload: data.ticket, + }); + } + + if (data.action === "delete") { + dispatch({ type: "DELETE_TICKET", payload: data.ticketId }); + } + }); + + socket.on("appMessage", data => { + if (data.action === "create") { + dispatch({ + type: "UPDATE_TICKET_MESSAGES_COUNT", + payload: data.ticket, + }); + } + }); + + socket.on("contact", data => { + if (data.action === "update") { + dispatch({ + type: "UPDATE_TICKET_CONTACT", + payload: data.contact, + }); + } + }); + + return () => { + socket.disconnect(); + }; + }, [status, showAll, userId]); const loadMore = () => { setPageNumber(prevState => prevState + 1); @@ -91,7 +230,9 @@ const TicketsList = ({ status, searchParam, showAll }) => { const handleScroll = e => { if (!hasMore || loading) return; + const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; + if (scrollHeight - (scrollTop + 100) < clientHeight) { loadMore(); } @@ -110,16 +251,16 @@ const TicketsList = ({ status, searchParam, showAll }) => { {status === "open" && (
{i18n.t("ticketsList.assignedHeader")} - {tickets.length} + {ticketsList.length}
)} {status === "pending" && (
{i18n.t("ticketsList.pendingHeader")} - {tickets.length} + {ticketsList.length}
)} - {tickets.length === 0 && !loading ? ( + {ticketsList.length === 0 && !loading ? (
{i18n.t("ticketsList.noTicketsTitle")} @@ -130,7 +271,7 @@ const TicketsList = ({ status, searchParam, showAll }) => {
) : ( <> - {tickets.map(ticket => ( + {ticketsList.map(ticket => ( ))} diff --git a/frontend/src/components/_layout/index.js b/frontend/src/components/_layout/index.js index 633e071..f4a00c5 100644 --- a/frontend/src/components/_layout/index.js +++ b/frontend/src/components/_layout/index.js @@ -17,7 +17,9 @@ import AccountCircle from "@material-ui/icons/AccountCircle"; import MainListItems from "./MainListItems"; import NotificationsPopOver from "../NotificationsPopOver"; +import UserModal from "../UserModal"; import { AuthContext } from "../../context/Auth/AuthContext"; +import BackdropLoading from "../BackdropLoading"; const drawerWidth = 240; @@ -100,13 +102,15 @@ const useStyles = makeStyles(theme => ({ }, })); -const MainDrawer = ({ appTitle, children }) => { - const { handleLogout } = useContext(AuthContext); +const LoggedInLayout = ({ appTitle, children }) => { + const drawerState = localStorage.getItem("drawerOpen"); + const userId = +localStorage.getItem("userId"); const classes = useStyles(); const [open, setOpen] = useState(true); - const [anchorEl, setAnchorEl] = React.useState(null); + const [userModalOpen, setUserModalOpen] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); const [menuOpen, setMenuOpen] = useState(false); - const drawerState = localStorage.getItem("drawerOpen"); + const { handleLogout, loading } = useContext(AuthContext); useEffect(() => { if (drawerState === "0") { @@ -134,6 +138,15 @@ const MainDrawer = ({ appTitle, children }) => { setMenuOpen(false); }; + const handleOpenUserModal = () => { + setUserModalOpen(true); + handleCloseMenu(); + }; + + if (loading) { + return ; + } + return (
{ + setUserModalOpen(false)} + userId={userId} + /> { noWrap className={classes.title} > - {appTitle} + WHATICKET @@ -208,7 +226,7 @@ const MainDrawer = ({ appTitle, children }) => { open={menuOpen} onClose={handleCloseMenu} > - Profile + Profile Logout
@@ -223,4 +241,4 @@ const MainDrawer = ({ appTitle, children }) => { ); }; -export default MainDrawer; +export default LoggedInLayout; diff --git a/frontend/src/hooks/useTickets/index.js b/frontend/src/hooks/useTickets/index.js index 0090b6b..b913c09 100644 --- a/frontend/src/hooks/useTickets/index.js +++ b/frontend/src/hooks/useTickets/index.js @@ -1,80 +1,8 @@ -import { useState, useEffect, useReducer } from "react"; -import openSocket from "socket.io-client"; +import { useState, useEffect } from "react"; import { toast } from "react-toastify"; import api from "../../services/api"; -const reducer = (state, action) => { - if (action.type === "LOAD_TICKETS") { - const newTickets = action.payload; - - newTickets.forEach(ticket => { - const ticketIndex = state.findIndex(t => t.id === ticket.id); - if (ticketIndex !== -1) { - state[ticketIndex] = ticket; - if (ticket.unreadMessages > 0) { - state.unshift(state.splice(ticketIndex, 1)[0]); - } - } else { - state.push(ticket); - } - }); - - return [...state]; - } - - if (action.type === "UPDATE_TICKETS") { - const { ticket, status, loggedUser, withUnreadMessages } = action.payload; - - const ticketIndex = state.findIndex(t => t.id === ticket.id); - if (ticketIndex !== -1) { - if (ticket.status !== state[ticketIndex].status) { - state.splice(ticketIndex, 1); - } else { - state[ticketIndex] = ticket; - state.unshift(state.splice(ticketIndex, 1)[0]); - } - } else if ( - ticket.status === status && - (ticket.userId === loggedUser || - !ticket.userId || - ticket.status === "closed") - ) { - state.unshift(ticket); - } else if (withUnreadMessages) { - state.unshift(ticket); - } - return [...state]; - } - - if (action.type === "DELETE_TICKET") { - const ticketId = action.payload; - - const ticketIndex = state.findIndex(t => t.id === ticketId); - if (ticketIndex !== -1) { - state.splice(ticketIndex, 1); - } - return [...state]; - } - - if (action.type === "RESET_UNREAD") { - const { ticketId, withUnreadMessages } = action.payload; - - const ticketIndex = state.findIndex(t => t.id === ticketId); - if (ticketIndex !== -1) { - state[ticketIndex].unreadMessages = 0; - if (withUnreadMessages) { - state.splice(ticketIndex, 1); - } - } - return [...state]; - } - - if (action.type === "RESET") { - return []; - } -}; - const useTickets = ({ searchParam, pageNumber, @@ -83,10 +11,9 @@ const useTickets = ({ showAll, withUnreadMessages, }) => { - const userId = +localStorage.getItem("userId"); const [loading, setLoading] = useState(true); const [hasMore, setHasMore] = useState(false); - const [tickets, dispatch] = useReducer(reducer, []); + const [tickets, setTickets] = useState([]); useEffect(() => { setLoading(true); @@ -103,10 +30,7 @@ const useTickets = ({ withUnreadMessages, }, }); - dispatch({ - type: "LOAD_TICKETS", - payload: data.tickets, - }); + setTickets(data.tickets); setHasMore(data.hasMore); setLoading(false); } catch (err) { @@ -121,57 +45,7 @@ const useTickets = ({ return () => clearTimeout(delayDebounceFn); }, [searchParam, pageNumber, status, date, showAll, withUnreadMessages]); - useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); - socket.emit("joinNotification"); - - socket.on("ticket", data => { - if (data.action === "updateUnread") { - dispatch({ - type: "RESET_UNREAD", - payload: { - ticketId: data.ticketId, - withUnreadMessages: withUnreadMessages, - }, - }); - } - - if (data.action === "updateStatus" || data.action === "create") { - dispatch({ - type: "UPDATE_TICKETS", - payload: { - ticket: data.ticket, - status: status, - loggedUser: userId, - }, - }); - } - - if (data.action === "delete") { - dispatch({ type: "DELETE_TICKET", payload: data.ticketId }); - } - }); - - socket.on("appMessage", data => { - if (data.action === "create") { - dispatch({ - type: "UPDATE_TICKETS", - payload: { - ticket: data.ticket, - status: status, - withUnreadMessages: withUnreadMessages, - loggedUser: userId, - }, - }); - } - }); - - return () => { - socket.disconnect(); - }; - }, [status, withUnreadMessages, userId]); - - return { loading, tickets, hasMore, dispatch }; + return { tickets, loading, hasMore }; }; export default useTickets; diff --git a/frontend/src/pages/Connections/index.js b/frontend/src/pages/Connections/index.js index 800b478..dc155c1 100644 --- a/frontend/src/pages/Connections/index.js +++ b/frontend/src/pages/Connections/index.js @@ -167,8 +167,8 @@ const Connections = () => { }; const handleCloseQrModal = useCallback(() => { - setQrModalOpen(false); setSelectedWhatsApp(null); + setQrModalOpen(false); }, [setQrModalOpen, setSelectedWhatsApp]); const handleEditWhatsApp = whatsApp => { diff --git a/frontend/src/routes/Route.js b/frontend/src/routes/Route.js index 38fcd06..d0643a4 100644 --- a/frontend/src/routes/Route.js +++ b/frontend/src/routes/Route.js @@ -1,29 +1,13 @@ import React, { useContext } from "react"; import { Route, Redirect } from "react-router-dom"; -import Backdrop from "@material-ui/core/Backdrop"; -import CircularProgress from "@material-ui/core/CircularProgress"; -import { makeStyles } from "@material-ui/core/styles"; - import { AuthContext } from "../context/Auth/AuthContext"; - -const useStyles = makeStyles(theme => ({ - backdrop: { - zIndex: theme.zIndex.drawer + 1, - color: "#fff", - }, -})); +import BackdropLoading from "../components/BackdropLoading"; const RouteWrapper = ({ component: Component, isPrivate = false, ...rest }) => { - const classes = useStyles(); const { isAuth, loading } = useContext(AuthContext); - if (loading) - return ( - - - - ); + if (loading) return ; if (!isAuth && isPrivate) { return ( diff --git a/frontend/src/routes/index.js b/frontend/src/routes/index.js index 10b3b5f..74569a0 100644 --- a/frontend/src/routes/index.js +++ b/frontend/src/routes/index.js @@ -2,7 +2,7 @@ import React from "react"; import { BrowserRouter, Switch } from "react-router-dom"; import { ToastContainer } from "react-toastify"; -import MainDrawer from "../components/_layout"; +import LoggedInLayout from "../components/_layout"; import Dashboard from "../pages/Dashboard/"; import Tickets from "../pages/Tickets/"; import Signup from "../pages/Signup/"; @@ -21,7 +21,7 @@ const Routes = () => { - + { - +