diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..562cc64 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/frontend/src/assets/icons/SearchInput/search.svg b/frontend/src/assets/icons/SearchInput/search.svg new file mode 100644 index 0000000..6636c58 --- /dev/null +++ b/frontend/src/assets/icons/SearchInput/search.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/User/clientDefault.png b/frontend/src/assets/images/User/clientDefault.png new file mode 100644 index 0000000..75dbece Binary files /dev/null and b/frontend/src/assets/images/User/clientDefault.png differ diff --git a/frontend/src/components/Base/BTN/Btn.jsx b/frontend/src/components/Base/BTN/Btn.jsx deleted file mode 100644 index 9755fa4..0000000 --- a/frontend/src/components/Base/BTN/Btn.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; -import BtnBaseStyled from "./Btn.styled"; - -const BtnComponent = ({ text, bgcolor, fontcolor,...props }) => { - return ( - - {text} - - ); -}; - -export default BtnComponent; diff --git a/frontend/src/components/Base/Badge/BadgeComponent.jsx b/frontend/src/components/Base/Badge/BadgeComponent.jsx new file mode 100644 index 0000000..6fe85e5 --- /dev/null +++ b/frontend/src/components/Base/Badge/BadgeComponent.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import { BadgeComponentStyled } from "./BadgeComponent.style"; + +const BadgeComponent = ({ counter,fontSize, position, top, left, right, bottom,bgcolor }) => { + return ( + + {counter} + + ); +}; + +export default BadgeComponent; + diff --git a/frontend/src/components/Base/Badge/BadgeComponent.style.jsx b/frontend/src/components/Base/Badge/BadgeComponent.style.jsx new file mode 100644 index 0000000..87c5cc8 --- /dev/null +++ b/frontend/src/components/Base/Badge/BadgeComponent.style.jsx @@ -0,0 +1,22 @@ +import styled from "styled-components"; +import { color } from "../../../style/varibles"; + +export const BadgeComponentStyled = styled.span` + position: ${({ position }) => (position ? position : "relative")}; + top: ${({ top }) => (top ? top : "initial")}; + left: ${({ left }) => (left ? left : "initial")}; + right: ${({ right }) => (right ? right : "initial")}; + bottom: ${({ bottom }) => (bottom ? bottom : "initial")}; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + border-radius: 50px; + object-fit: cover; + width: 21px; + height: 21px; + font-size: ${({ fontSize }) => (fontSize ? fontSize : "16px")}; + color: ${color.pricinpal.blanco}; + background-color: ${({ bgcolor }) => (bgcolor ? bgcolor : color.status.yes)}; +`; + diff --git a/frontend/src/components/Base/Btn/Btn.jsx b/frontend/src/components/Base/Btn/Btn.jsx new file mode 100644 index 0000000..4f81980 --- /dev/null +++ b/frontend/src/components/Base/Btn/Btn.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import BtnBaseStyled from "./Btn.styled"; + +const BtnComponent = ({ text, bgcolor, fontSize, fontcolor, ...props }) => { + return ( + + {text} + + ); +}; + +export default BtnComponent; + diff --git a/frontend/src/components/Base/BTN/Btn.styled.jsx b/frontend/src/components/Base/Btn/Btn.styled.jsx similarity index 90% rename from frontend/src/components/Base/BTN/Btn.styled.jsx rename to frontend/src/components/Base/Btn/Btn.styled.jsx index 9773520..e5beb3d 100644 --- a/frontend/src/components/Base/BTN/Btn.styled.jsx +++ b/frontend/src/components/Base/Btn/Btn.styled.jsx @@ -8,7 +8,7 @@ const BtnBaseStyled = styled.button` padding: 6px 16px 3px; border-radius: 5px; margin: 12px 0; - font-size: 18px; + font-size: ${({ fontSize }) => fontSize ? fontSize: "18px"}; font-family: "Helvetica55"; vertical-align: baseline; transition: all 0.2s linear; diff --git a/frontend/src/components/Base/MainContainer/MainContainer.jsx b/frontend/src/components/Base/MainContainer/MainContainer.jsx index b8bb60d..e38be30 100644 --- a/frontend/src/components/Base/MainContainer/MainContainer.jsx +++ b/frontend/src/components/Base/MainContainer/MainContainer.jsx @@ -1,7 +1,6 @@ import React from "react"; import PageTitle from "../../PageTitle/PageTitle"; - import MainContainerStyled, { TitleContainerStyled, ContentContainerStyled, diff --git a/frontend/src/components/Base/MainContainer/MainContainer.style.jsx b/frontend/src/components/Base/MainContainer/MainContainer.style.jsx index a288e71..3ee5bbd 100644 --- a/frontend/src/components/Base/MainContainer/MainContainer.style.jsx +++ b/frontend/src/components/Base/MainContainer/MainContainer.style.jsx @@ -1,4 +1,5 @@ import styled from "styled-components"; +import { color } from "../../../style/varibles"; const MainContainerStyled = styled.main` padding-left: 97px; @@ -21,6 +22,8 @@ const ContentContainerStyled = styled.div` height: 100vh; overflow: hidden; margin-top: 16px; + border-radius: 5px; + background-color: ${color.complement.azulOscuro}; `; export default MainContainerStyled; diff --git a/frontend/src/components/ConfirmationModal/ConfirmationModal.jsx b/frontend/src/components/ConfirmationModal/ConfirmationModal.jsx index 0f8d424..7db3110 100644 --- a/frontend/src/components/ConfirmationModal/ConfirmationModal.jsx +++ b/frontend/src/components/ConfirmationModal/ConfirmationModal.jsx @@ -1,5 +1,5 @@ import React from "react"; -import BtnComponent from "../Base/BTN/Btn"; +import BtnComponent from "../Base/Btn/Btn"; import { ConfirmationModalStyled, diff --git a/frontend/src/components/LoadingScreen/Loading.jsx b/frontend/src/components/LoadingScreen/Loading.jsx deleted file mode 100644 index ca2e960..0000000 --- a/frontend/src/components/LoadingScreen/Loading.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -import LoadingStyled from "./Loading.style" - -const Loading = () => { - return ; -}; - -export default Loading; diff --git a/frontend/src/components/LoadingScreen/LoadingScreen.jsx b/frontend/src/components/LoadingScreen/LoadingScreen.jsx new file mode 100644 index 0000000..3b72d76 --- /dev/null +++ b/frontend/src/components/LoadingScreen/LoadingScreen.jsx @@ -0,0 +1,8 @@ +import React from "react"; +import LoadingStyled from "./LoadingScreen.style" + +const LoadingScreen = () => { + return ; +}; + +export default LoadingScreen; diff --git a/frontend/src/components/LoadingScreen/Loading.style.jsx b/frontend/src/components/LoadingScreen/LoadingScreen.style.jsx similarity index 100% rename from frontend/src/components/LoadingScreen/Loading.style.jsx rename to frontend/src/components/LoadingScreen/LoadingScreen.style.jsx diff --git a/frontend/src/components/LoginComponents/LoginForm/Inputs/InputComponent.style.jsx b/frontend/src/components/LoginComponents/LoginForm/Inputs/InputComponent.style.jsx index 4e6c60d..613c3fc 100644 --- a/frontend/src/components/LoginComponents/LoginForm/Inputs/InputComponent.style.jsx +++ b/frontend/src/components/LoginComponents/LoginForm/Inputs/InputComponent.style.jsx @@ -3,7 +3,7 @@ import { color } from "../../../../style/varibles"; const InputComponentStyled = styled.input` width: 100%; - background: transparent; + background: ${color.complement.azulOscuro}; border: none; color: ${color.complement.azulCielo}; `; diff --git a/frontend/src/components/Menu/MenuComponent.jsx b/frontend/src/components/Menu/MenuComponent.jsx index 047aedc..dc0c6c9 100644 --- a/frontend/src/components/Menu/MenuComponent.jsx +++ b/frontend/src/components/Menu/MenuComponent.jsx @@ -63,7 +63,7 @@ const MenuComponent = () => { } text="Tickets" to="/tickets" hover={hover} /> } text="Contatos" to="/contacts" hover={hover} /> } text="Lembretes" to="/schedulesReminder" hover={hover} /> - } text="Respostas" to="" hover={hover} /> + } text="Respostas" to="/quickAnswers" hover={hover} /> } text="Usuários" to="/users" hover={hover} /> } text="Filas" to="/Queues" hover={hover} /> diff --git a/frontend/src/components/MessagesList/index.js b/frontend/src/components/MessagesList/index.js index 0344c7b..6a4745e 100644 --- a/frontend/src/components/MessagesList/index.js +++ b/frontend/src/components/MessagesList/index.js @@ -45,6 +45,7 @@ const useStyles = makeStyles((theme) => ({ flexGrow: 1, padding: "20px 20px 20px 20px", overflowY: "scroll", + height: "50vh", [theme.breakpoints.down("sm")]: { paddingBottom: "90px", }, @@ -541,11 +542,6 @@ const MessagesList = ({ ticketId, isGroup }) => { })} >
- {!message.quotedMsg?.fromMe && ( - - {message.quotedMsg?.contact?.name} - - )} {message.quotedMsg?.body}
diff --git a/frontend/src/components/PageTitle/PageTitle.jsx b/frontend/src/components/PageTitle/PageTitle.jsx index 6ba57c1..9ead028 100644 --- a/frontend/src/components/PageTitle/PageTitle.jsx +++ b/frontend/src/components/PageTitle/PageTitle.jsx @@ -1,4 +1,5 @@ import React from "react"; +import { useLocation } from "react-router-dom"; import { PageTitleStyled } from "./PageTitle.style"; import UserBtn from "./UserBtn/UserBtn"; @@ -7,15 +8,54 @@ import { AuthContext } from "../../context/Auth/AuthContext"; import logo from "../../assets/images/Logo.png"; const PageTitle = () => { + const path = useLocation(); + const [title, setTitle] = React.useState(); const [modal, setModal] = React.useState(false); const { user } = React.useContext(AuthContext); + React.useEffect(() => { + switch (path.pathname) { + case "/tickets": + setTitle("Tickets"); + break; + case "/contacts": + setTitle("Contatos"); + break; + case "/schedulesReminder": + setTitle("Lembretes"); + break; + case "/quickAnswers": + setTitle("Respostas Rápidas"); + break; + case "/users": + setTitle("Usuários"); + break; + case "/Queues": + setTitle("Filas"); + break; + case "/connections": + setTitle("Conexões"); + break; + case "/report": + setTitle("Relatórios"); + break; + case "/super": + setTitle("Supervisão"); + break; + case "/Settings": + setTitle("Configurações"); + break; + default: + setTitle("Dashboard"); + } + }, [path]); + const handleModal = () => { setModal(!modal); }; return ( -

PageTitle

+

{title}

); diff --git a/frontend/src/components/PageTitle/UserBtn/UserBtn.jsx b/frontend/src/components/PageTitle/UserBtn/UserBtn.jsx index 299a22e..6bc2e14 100644 --- a/frontend/src/components/PageTitle/UserBtn/UserBtn.jsx +++ b/frontend/src/components/PageTitle/UserBtn/UserBtn.jsx @@ -34,7 +34,7 @@ const UserBtn = ({ user, img, modal, modalSet }) => { } onClick={handleModal} /> - + Deseja Sair do sistema? diff --git a/frontend/src/components/Ticket/Ticket.jsx b/frontend/src/components/Ticket/Ticket.jsx new file mode 100644 index 0000000..929f648 --- /dev/null +++ b/frontend/src/components/Ticket/Ticket.jsx @@ -0,0 +1,117 @@ +import React from "react"; +import { useParams, useHistory } from "react-router-dom"; + +import { toast } from "react-toastify"; +import openSocket from "socket.io-client"; + +import ContactDrawer from "../ContactDrawer"; +import MessageInput from "../MessageInput"; +import TicketHeader from "../TicketHeader"; +import TicketInfo from "../TicketInfo"; +import TicketActionButtons from "../TicketActionButtons"; +import MessagesList from "../MessagesList"; +import api from "../../services/api"; +import { ReplyMessageProvider } from "../../context/ReplyingMessage/ReplyingMessageContext"; +import toastError from "../../errors/toastError"; + +const Ticket = () => { + const { ticketId } = useParams(); + const history = useHistory(); + + const [drawerOpen, setDrawerOpen] = React.useState(false); + const [loading, setLoading] = React.useState(true); + const [contact, setContact] = React.useState({}); + const [ticket, setTicket] = React.useState({}); + + const [statusChatEnd, setStatusChatEnd] = React.useState({}); + + React.useEffect(() => { + setLoading(true); + const delayDebounceFn = setTimeout(() => { + const fetchTicket = async () => { + try { + const { data } = await api.get("/tickets/" + ticketId); + + setContact(data.contact.contact); + setTicket(data.contact); + + setStatusChatEnd(data.statusChatEnd); + + setLoading(false); + } catch (err) { + setLoading(false); + toastError(err); + } + }; + fetchTicket(); + }, 500); + return () => clearTimeout(delayDebounceFn); + }, [ticketId, history]); + + React.useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + + socket.on("connect", () => socket.emit("joinChatBox", ticketId)); + + socket.on("ticket", (data) => { + if (data.action === "update") { + setTicket(data.ticket); + } + + if (data.action === "delete") { + toast.success("Ticket deleted sucessfully."); + history.push("/tickets"); + } + }); + + socket.on("contact", (data) => { + if (data.action === "update") { + setContact((prevState) => { + if (prevState.id === data.contact?.id) { + return { ...prevState, ...data.contact }; + } + return prevState; + }); + } + }); + + return () => { + socket.disconnect(); + }; + }, [ticketId, history]); + + const handleDrawerOpen = () => { + setDrawerOpen(true); + }; + + const handleDrawerClose = () => { + setDrawerOpen(false); + }; + + + return ( +
+ +
+ +
+
+ +
+
+ + + + + + +
+ ); +}; + +export default Ticket; diff --git a/frontend/src/components/Ticket/Ticket.style.jsx b/frontend/src/components/Ticket/Ticket.style.jsx new file mode 100644 index 0000000..c6e12c9 --- /dev/null +++ b/frontend/src/components/Ticket/Ticket.style.jsx @@ -0,0 +1,7 @@ +import styled from "styled-components"; +import { color } from "../../style/varibles"; + +export const TicketStyled = styled.div` + width: 100%; + background-color: ${color.status.no}; +`; diff --git a/frontend/src/components/Ticket/TicketSearch/TicketSearch.jsx b/frontend/src/components/Ticket/TicketSearch/TicketSearch.jsx new file mode 100644 index 0000000..265672f --- /dev/null +++ b/frontend/src/components/Ticket/TicketSearch/TicketSearch.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import TicketSearchInput from './TicketSearchInput/TicketSearchInput' + +const TicketSearch = ({spinning,setNewTicketModalOpen, handleSearch}) => { + return ( + + ) +} + +export default TicketSearch \ No newline at end of file diff --git a/frontend/src/components/Ticket/TicketSearch/TicketSearch.styled.jsx b/frontend/src/components/Ticket/TicketSearch/TicketSearch.styled.jsx new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/frontend/src/components/Ticket/TicketSearch/TicketSearch.styled.jsx @@ -0,0 +1 @@ + diff --git a/frontend/src/components/Ticket/TicketSearch/TicketSearchBtn/TicketSearchBtn.jsx b/frontend/src/components/Ticket/TicketSearch/TicketSearchBtn/TicketSearchBtn.jsx new file mode 100644 index 0000000..166d26e --- /dev/null +++ b/frontend/src/components/Ticket/TicketSearch/TicketSearchBtn/TicketSearchBtn.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import {TicketSearchBtnStyled} from "./TicketSearchBtn.styled" + +const TicketSearchBtn = ({setNewTicketModalOpen}) => { + return ( + setNewTicketModalOpen(true)}>+ + ) +} + +export default TicketSearchBtn \ No newline at end of file diff --git a/frontend/src/components/Ticket/TicketSearch/TicketSearchBtn/TicketSearchBtn.styled.jsx b/frontend/src/components/Ticket/TicketSearch/TicketSearchBtn/TicketSearchBtn.styled.jsx new file mode 100644 index 0000000..15cd837 --- /dev/null +++ b/frontend/src/components/Ticket/TicketSearch/TicketSearchBtn/TicketSearchBtn.styled.jsx @@ -0,0 +1,12 @@ +import styled from "styled-components"; +import { color } from "../../../../style/varibles"; + +export const TicketSearchBtnStyled = styled.button` + cursor: pointer; + background-color: transparent; + color: ${color.pricinpal.blanco}; + font-size: 26px; + border: none; + font-family: "Helvetica55"; +`; + diff --git a/frontend/src/components/Ticket/TicketSearch/TicketSearchInput/TicketSearchInput.jsx b/frontend/src/components/Ticket/TicketSearch/TicketSearchInput/TicketSearchInput.jsx new file mode 100644 index 0000000..f2127ef --- /dev/null +++ b/frontend/src/components/Ticket/TicketSearch/TicketSearchInput/TicketSearchInput.jsx @@ -0,0 +1,25 @@ +import React from "react"; + +import { + TicketSearchDivStyled, + TicketSearchInputBoxStyled, + TicketSearchInputStyled, +} from "./TicketSearchInput.styled"; + +import { ReactComponent as SearchIcon } from "../../../../assets/icons/SearchInput/search.svg"; +import TicketSearchBtn from "../TicketSearchBtn/TicketSearchBtn"; + +const TicketSearchInput = ({ spinning, setNewTicketModalOpen, handleSearch }) => { + return ( + + + + + + + + ); +}; + +export default TicketSearchInput; + diff --git a/frontend/src/components/Ticket/TicketSearch/TicketSearchInput/TicketSearchInput.styled.jsx b/frontend/src/components/Ticket/TicketSearch/TicketSearchInput/TicketSearchInput.styled.jsx new file mode 100644 index 0000000..1ef953d --- /dev/null +++ b/frontend/src/components/Ticket/TicketSearch/TicketSearchInput/TicketSearchInput.styled.jsx @@ -0,0 +1,58 @@ +import styled from "styled-components"; +import { color } from "../../../../style/varibles"; + +export const TicketSearchDivStyled = styled.div` + padding: 0 6px; + display: flex; + flex-direction: row; +`; +export const TicketSearchInputBoxStyled = styled.div` + position: relative; + display: flex; + align-items: center; + width: 100%; + margin-right: 12px; + background-color: ${color.complement.azulOscuro}; + border: 2px solid ${color.pricinpal.blanco}; + color: ${color.pricinpal.blanco}; + border-radius: 4px; + padding: 6px; + & svg { + fill: ${color.gradient.border}; + } + &:after { + position: absolute; + right: 6px; + content: ""; + display: ${({ spinning }) => (!spinning ? "none" : "block")}; + width: 16px; + height: 16px; + border-top: 2px solid ${color.gradient.border}; + border-left: 2px solid ${color.gradient.border}; + border-bottom: 2px solid ${color.gradient.border}; + border-right: 2px solid ${color.pricinpal.naranja}; + border-radius: 50%; + animation: spining 0.5s infinite linear; + } + @keyframes spining { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } +`; +export const TicketSearchInputStyled = styled.input` + width: 100%; + border: none; + color: ${color.pricinpal.blanco}; + background-color: ${color.complement.azulOscuro}; + &:focus, + &:focus-visible { + border: none; + background-color: ${color.complement.azulOscuro}; + outline: none; + } +`; + diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketListItem/TicketListItem.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketListItem/TicketListItem.jsx new file mode 100644 index 0000000..272817b --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketListItem/TicketListItem.jsx @@ -0,0 +1,108 @@ +import React from "react"; +import { color } from "../../../../../style/varibles"; +import { useHistory } from "react-router-dom"; +import { parseISO, format, isSameDay } from "date-fns"; + +import { i18n } from "../../../../../translate/i18n"; + +import api from "../../../../../services/api"; +import { AuthContext } from "../../../../../context/Auth/AuthContext"; +import toastError from "../../../../../errors/toastError"; +import Btn from "../../../../Base/Btn/Btn"; +import { + TicketDateStyled, + TicketImgStyled, + TicketListItemStyled, + TicketTitleStyled, +} from "./TicketListItem.style"; +import LoadingScreen from "../../../../LoadingScreen/LoadingScreen"; +import BadgeComponent from "../../../../Base/Badge/BadgeComponent"; +import DefaultUser from "../../../../../assets/images/User/clientDefault.png"; + +const TicketListItem = ({ tickets }) => { + const history = useHistory(); + const [loading, setLoading] = React.useState(false); + const isMounted = React.useRef(true); + const { user } = React.useContext(AuthContext); + React.useEffect(() => { + return () => { + isMounted.current = false; + }; + }, []); + + const handleAcepptTicket = async (id) => { + setLoading(true); + try { + await api.put(`/tickets/${id}`, { + status: "open", + userId: user?.id, + }); + console.log("Passou no try", tickets.status, user?.id, id); + } catch (err) { + setLoading(false); + toastError(err); + } + + if (isMounted.current) { + setLoading(false); + } + + history.push(`/tickets/${id}`); + }; + + const handleSelectTicket = (id) => { + history.push(`/tickets/${id}`); + }; + + + return ( + + handleSelectTicket(tickets.id)} + > + {tickets.contact.profilePicUrl ? ( + + ) : ( + + )} + + +

{tickets.contact.name}

+

{tickets.lastMessage}

+ {tickets.unreadMessages ? ( + + ) : ( + "" + )} +
+ + {isSameDay(parseISO(tickets.updatedAt), new Date()) ? ( + <>{format(parseISO(tickets.updatedAt), "HH:mm")} + ) : ( + <>{format(parseISO(tickets.updatedAt), "dd/MM/yyyy")} + )} + + {tickets.status === "pending" ? ( + handleAcepptTicket(tickets.id)} + text="Aceitar" + bgcolor={color.complement.azulCielo} + fontcolor={color.pricinpal.blanco} + fontSize="12px" + /> + ) : ( + "" + )} +
+
+ ); +}; + +export default TicketListItem; + diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketListItem/TicketListItem.style.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketListItem/TicketListItem.style.jsx new file mode 100644 index 0000000..33b14e8 --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketListItem/TicketListItem.style.jsx @@ -0,0 +1,72 @@ +import styled from "styled-components"; +import { color } from "../../../../../style/varibles"; + +export const TicketListItemStyled = styled.li` + cursor: pointer; + position: relative; + background-color: ${color.complement.azulOscuro}; + display: flex; + align-items: center; + padding: 0.5rem 6px; + border-bottom: 1.5px solid ${color.gradient.border}; + height: fit-content; + transition: filter .2s linear; + &:before { + position: absolute; + left: 0; + content: ""; + display: block; + background-color: ${({ queuecolor }) => (queuecolor ? queuecolor : color.gradient.border)}; + width: 4px; + height: 55px; + } + &:nth-child(1) { + margin-top: 6px; + } + &:hover{ + filter: brightness(1.2); + transition: filter .2s linear; + } +`; + +export const TicketTitleStyled = styled.div` + position: relative; + display: flex; + flex-direction: column; + flex-grow: 1; + row-gap: 6px; + & p { + color: ${color.pricinpal.blanco}; + width: 190px; + font-size: 12px; + font-family: "Helvetica55"; + &:nth-child(1) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + &:nth-child(2) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: ${color.gradient.text}; + } + } +`; +export const TicketDateStyled = styled.div` + color: ${color.pricinpal.blanco}; + font-size: 12px; + font-family: "Helvetica55"; + display: block; + color: white; + margin-right: 1rem; +`; + +export const TicketImgStyled = styled.img` + width: 40px; + height: 40px; + object-fit: contain; + border-radius: 50%; + margin: 0 6px; +`; + diff --git a/frontend/src/components/TicketListItem/index.js b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketListItem/index.js similarity index 97% rename from frontend/src/components/TicketListItem/index.js rename to frontend/src/components/Ticket/TicketsManager/TicketsList/TicketListItem/index.js index f7460bf..4774b8c 100644 --- a/frontend/src/components/TicketListItem/index.js +++ b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketListItem/index.js @@ -1,6 +1,6 @@ import React, { useState, useEffect, useRef, useContext } from "react"; -import { useHistory, useParams } from "react-router-dom"; +import ReactDOM, { useHistory, useParams } from "react-router-dom"; import { parseISO, format, isSameDay } from "date-fns"; import clsx from "clsx"; @@ -145,7 +145,7 @@ const TicketListItem = ({ ticket }) => { if (ticket.status === "pending") return; handleSelectTicket(ticket.id); }} - selected={ticketId && +ticketId === ticket.id} + selected={ticketId && +ticketId === ticket.id} ///img src{id}.jpg className={clsx(classes.ticket, { [classes.pendingTicket]: ticket.status === "pending", })} diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketsList.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketsList.jsx new file mode 100644 index 0000000..fb3b27b --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketsList.jsx @@ -0,0 +1,212 @@ +import React from "react"; + +import openSocket from "socket.io-client"; + +import LoadingScreen from "../../../LoadingScreen/LoadingScreen"; + +import TicketListStyled from "./TicketsList.style"; +import useTickets from "../../../../hooks/useTickets"; +import TicketListItem from "../TicketsList/TicketListItem/TicketListItem"; +import TicketsListSkeleton from "../TicketsListSkeleton/TicketListSkeleton"; + +import { i18n } from "../../../../translate/i18n"; +import { AuthContext } from "../../../../context/Auth/AuthContext"; + +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_UNREAD_MESSAGES") { + const message = action.payload.message; + + const ticket = action.payload.ticket; + + const ticketIndex = state.findIndex((t) => t.id === ticket.id); + + if (ticketIndex !== -1) { + if (!message.fromMe) { + ticket.unreadMessages += 1; + } + + state[ticketIndex] = ticket; + state.unshift(state.splice(ticketIndex, 1)[0]); + } else { + state.unshift(ticket); + } + + 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, selectedQueueIds, updateCount, valueTab }) => { + const [pageNumber, setPageNumber] = React.useState(1); + const [ticketsList, dispatch] = React.useReducer(reducer, []); + const { user } = React.useContext(AuthContext); + React.useEffect(() => { + dispatch({ type: "RESET" }); + setPageNumber(1); + }, [status, searchParam, dispatch, showAll, selectedQueueIds]); + const { tickets, loading } = useTickets({ + pageNumber, + searchParam, + status, + showAll, + queueIds: JSON.stringify(selectedQueueIds), + }); + + React.useEffect(() => { + if (typeof updateCount === "function") { + updateCount(ticketsList.length); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ticketsList]); + + React.useEffect(() => { + if (!status && !searchParam) return; + dispatch({ + type: "LOAD_TICKETS", + payload: tickets, + }); + }, [tickets, status, searchParam]); + + React.useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + + const shouldUpdateTicket = (ticket) => + (!ticket.userId || ticket.userId === user?.id || showAll) && + (!ticket.queueId || selectedQueueIds.indexOf(ticket.queueId) > -1); + + const notBelongsToUserQueues = (ticket) => + ticket.queueId && selectedQueueIds.indexOf(ticket.queueId) === -1; + + socket.on("connect", () => { + if (status) { + socket.emit("joinTickets", status); + } else { + socket.emit("joinNotification"); + } + }); + + socket.on("ticket", (data) => { + if (data.action === "updateUnread") { + dispatch({ + type: "RESET_UNREAD", + payload: data.ticketId, + }); + } + + if (data.action === "update" && shouldUpdateTicket(data.ticket)) { + dispatch({ + type: "UPDATE_TICKET", + payload: data.ticket, + }); + } + + if (data.action === "update" && notBelongsToUserQueues(data.ticket)) { + dispatch({ type: "DELETE_TICKET", payload: data.ticket.id }); + } + + if (data.action === "delete") { + dispatch({ type: "DELETE_TICKET", payload: data.ticketId }); + } + }); + + socket.on("appMessage", (data) => { + if (data.action === "create" && shouldUpdateTicket(data.ticket)) { + // console.log('((((((((((((((((((( DATA.MESSAGE: ', data.message) + + dispatch({ + type: "UPDATE_TICKET_UNREAD_MESSAGES", + // payload: data.ticket, + payload: data, + }); + } + }); + + socket.on("contact", (data) => { + if (data.action === "update") { + dispatch({ + type: "UPDATE_TICKET_CONTACT", + payload: data.contact, + }); + } + }); + + return () => { + socket.disconnect(); + }; + }, [status, showAll, user, selectedQueueIds]); + + if (loading) return ; + + if (status === valueTab) + return ( + + {ticketsList.map((ticket) => ( + + ))} + + ); + return null; +}; + +export default TicketsList; + diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketsList.style.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketsList.style.jsx new file mode 100644 index 0000000..141ae52 --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsList/TicketsList.style.jsx @@ -0,0 +1,10 @@ +import styled from "styled-components"; +import { color } from "../../../../style/varibles"; + +const TicketListStyled = styled.ul` + background-color: ${color.gradient.border}; + height: 100vh; + margin-top: 16px; +`; + +export default TicketListStyled; diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/Ticket/TicketsManager/TicketsList/index.js similarity index 96% rename from frontend/src/components/TicketsList/index.js rename to frontend/src/components/Ticket/TicketsManager/TicketsList/index.js index 1fb3263..c002abc 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/Ticket/TicketsManager/TicketsList/index.js @@ -5,12 +5,12 @@ import { makeStyles } from "@material-ui/core/styles"; import List from "@material-ui/core/List"; import Paper from "@material-ui/core/Paper"; -import TicketListItem from "../TicketListItem"; -import TicketsListSkeleton from "../TicketsListSkeleton"; +import TicketListItem from "../../../TicketListItem"; +import TicketsListSkeleton from "../../../TicketsListSkeleton"; -import useTickets from "../../hooks/useTickets"; -import { i18n } from "../../translate/i18n"; -import { AuthContext } from "../../context/Auth/AuthContext"; +import useTickets from "../../../../hooks/useTickets"; +import { i18n } from "../../../../translate/i18n"; +import { AuthContext } from "../../../../context/Auth/AuthContext"; const useStyles = makeStyles(theme => ({ ticketsListWrapper: { @@ -74,8 +74,6 @@ const useStyles = makeStyles(theme => ({ const reducer = (state, action) => { if (action.type === "LOAD_TICKETS") { const newTickets = action.payload; - - newTickets.forEach(ticket => { // console.log('* ticket.unreadMessages: ',ticket.unreadMessages) diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsListSkeleton/TicketListSkeleton.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsListSkeleton/TicketListSkeleton.jsx new file mode 100644 index 0000000..43ac9f6 --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsListSkeleton/TicketListSkeleton.jsx @@ -0,0 +1,42 @@ +import React from "react"; +import { + TicketListSkeletonStyled, + TicketSkeletonItemStyled, + TicketSkeletonTitleStyled, + TicketSkeletonDateStyled, + TicketSkeletonImgStyled, +} from "./TicketListSkeleton.style"; + +const TicketsSkeleton = () => { + return ( + + + + +
+
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+
+ ); +}; + +export default TicketsSkeleton; + diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsListSkeleton/TicketListSkeleton.style.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsListSkeleton/TicketListSkeleton.style.jsx new file mode 100644 index 0000000..5dca19e --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsListSkeleton/TicketListSkeleton.style.jsx @@ -0,0 +1,80 @@ +import styled from "styled-components"; +import { color } from "../../../../style/varibles"; + +export const TicketListSkeletonStyled = styled.ul` + background-color: ${color.gradient.border}; + height: 100vh; + margin-top: 16px; +`; + +export const TicketSkeletonItemStyled = styled.li` + cursor: pointer; + position: relative; + background-color: ${color.complement.azulOscuro}; + display: flex; + align-items: center; + padding: 0.5rem 6px; + border-bottom: 1.5px solid ${color.gradient.border}; + height: fit-content; + transition: filter 0.2s linear; + &:nth-child(1) { + margin-top: 6px; + } + &:hover { + filter: brightness(1.2); + transition: filter 0.2s linear; + } +`; + +export const TicketSkeletonTitleStyled = styled.div` + position: relative; + display: flex; + flex-direction: column; + flex-grow: 1; + row-gap: 6px; + opacity: 0.1; + & div { + color: ${color.pricinpal.blanco}; + width: 190px; + font-size: 12px; + &:nth-child(1) { + width: 150px; + height: 18px; + background: linear-gradient(to right, ${color.gradient.border}, ${color.pricinpal.blanco}); + animation: wave 0.8s infinite; + } + &:nth-child(2) { + width: 150px; + height: 18px; + background: linear-gradient(to right, ${color.gradient.border}, ${color.pricinpal.blanco}); + animation: wave 0.8s infinite; + } + } + @keyframes wave { + to { + background-position: 150px; + } + } +`; +export const TicketSkeletonDateStyled = styled.div` + background: linear-gradient(to right, ${color.gradient.border}, ${color.pricinpal.blanco}); + animation: wave 0.8s infinite; + opacity: 0.1; + display: block; + width: 40px; + height: 18px; + font-size: 12px; + margin-right: 1rem; +`; + +export const TicketSkeletonImgStyled = styled.div` + background: linear-gradient(to right, ${color.gradient.border}, ${color.pricinpal.blanco}); + animation: wave 0.8s infinite; + opacity: 0.1; + width: 40px; + height: 40px; + object-fit: contain; + border-radius: 50%; + margin: 0 6px; +`; + diff --git a/frontend/src/components/TicketsListSkeleton/index.js b/frontend/src/components/Ticket/TicketsManager/TicketsListSkeleton/index.js similarity index 100% rename from frontend/src/components/TicketsListSkeleton/index.js rename to frontend/src/components/Ticket/TicketsManager/TicketsListSkeleton/index.js diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsManager.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsManager.jsx new file mode 100644 index 0000000..d971cf4 --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsManager.jsx @@ -0,0 +1,96 @@ +import React from "react"; + +import TicketsManagerStyled from "./TicketsManager.style"; +import TicketsTabs from "./TicketsTabs/TicketsTabs"; + +import TicketSearch from "../TicketSearch/TicketSearch"; +import TicketsList from "./TicketsList/TicketsList"; +import NewTicketModal from "../../NewTicketModal"; + +import { AuthContext } from "../../../context/Auth/AuthContext"; + +const TicketsManager = () => { + const [valueTab, setValueTab] = React.useState("open"); + const [searchParam, setSearchParam] = React.useState(""); + const [spinning, setSpinning] = React.useState(false); + const [newTicketModalOpen, setNewTicketModalOpen] = React.useState(false); + const [openCount, setOpenCount] = React.useState(0); + const [pendingCount, setPendingCount] = React.useState(0); + const [closedCount, setClosedCount] = React.useState(0); + const [showAllTickets, setShowAllTickets] = React.useState(false); + const { user } = React.useContext(AuthContext); + const userQueueIds = user.queues.map((q) => q.id); + const [selectedQueueIds, setSelectedQueueIds] = React.useState(userQueueIds || []); + + let searchTimeout; + const handleSearch = (e) => { + setSpinning(true); + const searchedTerm = e.target.value.toLowerCase(); + + clearTimeout(searchTimeout); + + searchTimeout = setTimeout(() => { + setSearchParam(searchedTerm); + setSpinning(false); + }, 200); + + if (searchedTerm === "") { + setSearchParam(searchedTerm); + return; + } + }; + + React.useEffect(() => { + if (user.profile.toUpperCase() === "ADMIN") { + setShowAllTickets(true); + } + }, [user.profile]); + + return ( + + + + + + setOpenCount(v)} + showAll={showAllTickets} + selectedQueueIds={selectedQueueIds} + searchParam={searchParam} + valueTab={valueTab} + /> + + setPendingCount(v)} + selectedQueueIds={selectedQueueIds} + searchParam={searchParam} + valueTab={valueTab} + /> + setClosedCount(v)} + selectedQueueIds={selectedQueueIds} + searchParam={searchParam} + valueTab={valueTab} + /> + + setNewTicketModalOpen(false)} + /> + + + ); +}; + +export default TicketsManager; diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsManager.style.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsManager.style.jsx new file mode 100644 index 0000000..bce1432 --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsManager.style.jsx @@ -0,0 +1,13 @@ +import styled from "styled-components"; +import {color} from "../../../style/varibles" + +const TicketsManagerStyled = styled.div` +background-color: ${color.complement.azulOscuro}; + display:flex; + flex-direction: column; + width: 100%; + height: 100vh; + max-width: 425px; + border-right:1px solid ${color.gradient.border}; +` +export default TicketsManagerStyled \ No newline at end of file diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTab/TicketsTab.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTab/TicketsTab.jsx new file mode 100644 index 0000000..de62601 --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTab/TicketsTab.jsx @@ -0,0 +1,43 @@ +import React from "react"; +import { TicketsTabStyled } from "./TicketsTab.style"; +import { color } from "../../../../../style/varibles"; + +import BadgeComponent from "../../../../Base/Badge/BadgeComponent"; + +const TicketsTab = ({ text, id, setValueTab, valueTab, count }) => { + const [active, setActive] = React.useState(false); + + const handleClick = ({ target }) => { + setValueTab(target.id); + }; + + React.useEffect(() => { + valueTab === id ? setActive(true) : setActive(false); + }, [valueTab, id]); + + return ( + + {text} + {id !== "open" ? ( + + ) : ( + "" + )} + + ); +}; + +export default TicketsTab; + diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTab/TicketsTab.style.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTab/TicketsTab.style.jsx new file mode 100644 index 0000000..29d7617 --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTab/TicketsTab.style.jsx @@ -0,0 +1,37 @@ +import styled from "styled-components"; +import { color } from "../../../../../style/varibles"; + +export const TicketsTabStyled = styled.li` + width: 100%; + position: relative; + cursor: pointer; + background-color: ${color.complement.azulOscuro}; + color: ${color.pricinpal.blanco}; + font-size: 0.9rem; + text-transform: uppercase; + font-family: "Helvetica85"; + padding: 1rem 1.5rem; + text-align: center; + &:after { + content: ""; + display: block; + position: absolute; + left: 0; + bottom: 0; + width: 0; + height: 2px; + background-color: white; + transition: width 0.2s linear; + } + &.active { + &:after { + width: 100%; + transition: width 0.2s linear; + } + } + &:nth-child(2) { + border-left: 1px solid ${color.gradient.border}; + border-right: 1px solid ${color.gradient.border}; + } +`; + diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTabs.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTabs.jsx new file mode 100644 index 0000000..1ede3af --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTabs.jsx @@ -0,0 +1,35 @@ +import React from "react"; +import TicketsTab from "../TicketsTabs/TicketsTab/TicketsTab"; +import { TicketTabsStyled } from "./TicketsTabs.style"; + +const TicketsTabs = ({ setValueTab, valueTab, count }) => { + + return ( + + + + + + ); +}; + +export default TicketsTabs; + diff --git a/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTabs.style.jsx b/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTabs.style.jsx new file mode 100644 index 0000000..0ba690e --- /dev/null +++ b/frontend/src/components/Ticket/TicketsManager/TicketsTabs/TicketsTabs.style.jsx @@ -0,0 +1,12 @@ +import styled from "styled-components"; +import { color } from "../../../../style/varibles"; + +export const TicketTabsStyled = styled.ul` + display: flex; + justify-content: center; + border-bottom: 1px solid ${color.gradient.border}; + margin-bottom: 1rem; + margin-left: 0; + margin-right: 0; +`; + diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/Ticket/TicketsManager/index.js similarity index 92% rename from frontend/src/components/TicketsManager/index.js rename to frontend/src/components/Ticket/TicketsManager/index.js index 88e6ea0..44c89bc 100644 --- a/frontend/src/components/TicketsManager/index.js +++ b/frontend/src/components/Ticket/TicketsManager/index.js @@ -9,20 +9,19 @@ import Tab from "@material-ui/core/Tab"; import Badge from "@material-ui/core/Badge"; import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"; import CheckBoxIcon from "@material-ui/icons/CheckBox"; - import FormControlLabel from "@material-ui/core/FormControlLabel"; import Switch from "@material-ui/core/Switch"; - -import NewTicketModal from "../NewTicketModal"; -import TicketsList from "../TicketsList"; -import TabPanel from "../TabPanel"; - -import { i18n } from "../../translate/i18n"; -import { AuthContext } from "../../context/Auth/AuthContext"; -import { Can } from "../Can"; -import TicketsQueueSelect from "../TicketsQueueSelect"; import { Button } from "@material-ui/core"; +import NewTicketModal from "../../NewTicketModal/index"; +import TicketsList from "../../TicketsList"; +import TabPanel from "../../TabPanel"; + +import { i18n } from "../../../translate/i18n"; +import { AuthContext } from "../../../context/Auth/AuthContext"; +import { Can } from "../../Can"; +import TicketsQueueSelect from "../../TicketsQueueSelect"; + const useStyles = makeStyles((theme) => ({ ticketsWrapper: { position: "relative", @@ -112,7 +111,6 @@ const TicketsManager = () => { if (user.profile.toUpperCase() === "ADMIN") { setShowAllTickets(true); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { diff --git a/frontend/src/components/Ticket/index.js b/frontend/src/components/Ticket/index.js index 6b4a1cb..f569d73 100644 --- a/frontend/src/components/Ticket/index.js +++ b/frontend/src/components/Ticket/index.js @@ -19,64 +19,11 @@ import toastError from "../../errors/toastError"; const drawerWidth = 320; -const useStyles = makeStyles((theme) => ({ - root: { - display: "flex", - height: "100%", - position: "relative", - overflow: "hidden", - }, - - ticketInfo: { - maxWidth: "50%", - flexBasis: "50%", - [theme.breakpoints.down("sm")]: { - maxWidth: "80%", - flexBasis: "80%", - }, - }, - ticketActionButtons: { - maxWidth: "50%", - flexBasis: "50%", - display: "flex", - [theme.breakpoints.down("sm")]: { - maxWidth: "100%", - flexBasis: "100%", - marginBottom: "5px", - }, - }, - - mainWrapper: { - flex: 1, - height: "100%", - display: "flex", - flexDirection: "column", - overflow: "hidden", - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, - borderLeft: "0", - marginRight: -drawerWidth, - transition: theme.transitions.create("margin", { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - }, - - mainWrapperShift: { - borderTopRightRadius: 0, - borderBottomRightRadius: 0, - transition: theme.transitions.create("margin", { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen, - }), - marginRight: 0, - }, -})); const Ticket = () => { const { ticketId } = useParams(); const history = useHistory(); - const classes = useStyles(); + const [drawerOpen, setDrawerOpen] = useState(false); const [loading, setLoading] = useState(true); @@ -91,13 +38,8 @@ const Ticket = () => { const fetchTicket = async () => { try { - // maria julia - const { data } = await api.get("/tickets/" + ticketId); - // setContact(data.contact); - // setTicket(data); - setContact(data.contact.contact); setTicket(data.contact); @@ -155,23 +97,21 @@ const Ticket = () => { }; return ( -
+
-
+
-
+
diff --git a/frontend/src/components/TicketActionButtons/index.js b/frontend/src/components/TicketActionButtons/index.js index 3919503..58feff5 100644 --- a/frontend/src/components/TicketActionButtons/index.js +++ b/frontend/src/components/TicketActionButtons/index.js @@ -12,167 +12,147 @@ import ButtonWithSpinner from "../ButtonWithSpinner"; import toastError from "../../errors/toastError"; import { AuthContext } from "../../context/Auth/AuthContext"; -import Modal from "../ChatEnd/ModalChatEnd"; -import { render } from '@testing-library/react'; +import Modal from "../ChatEnd/ModalChatEnd"; +import { render } from "@testing-library/react"; -const useStyles = makeStyles(theme => ({ - actionButtons: { - marginRight: 6, - flex: "none", - alignSelf: "center", - marginLeft: "auto", - "& > *": { - margin: theme.spacing(1), - }, - }, +const useStyles = makeStyles((theme) => ({ + actionButtons: { + marginRight: 6, + flex: "none", + alignSelf: "center", + marginLeft: "auto", + "& > *": { + margin: theme.spacing(1), + }, + }, })); const TicketActionButtons = ({ ticket, statusChatEnd }) => { - const classes = useStyles(); - const history = useHistory(); - const [anchorEl, setAnchorEl] = useState(null); - const [loading, setLoading] = useState(false); - const ticketOptionsMenuOpen = Boolean(anchorEl); - const { user } = useContext(AuthContext); - - const handleOpenTicketOptionsMenu = e => { - setAnchorEl(e.currentTarget); - }; + const classes = useStyles(); + const history = useHistory(); + const [anchorEl, setAnchorEl] = useState(null); + const [loading, setLoading] = useState(false); + const ticketOptionsMenuOpen = Boolean(anchorEl); + const { user } = useContext(AuthContext); - const handleCloseTicketOptionsMenu = e => { - setAnchorEl(null); - }; + const handleOpenTicketOptionsMenu = (e) => { + setAnchorEl(e.currentTarget); + }; - - const chatEndVal = (data) => { - - if(data){ + const handleCloseTicketOptionsMenu = (e) => { + setAnchorEl(null); + }; - data = {...data, 'ticketId': ticket.id} - - console.log('ChatEnd: ',(data)); + const chatEndVal = (data) => { + if (data) { + data = { ...data, ticketId: ticket.id }; - handleUpdateTicketStatus(null, "closed", user?.id, data) + console.log("ChatEnd: ", data); - } - - } + handleUpdateTicketStatus(null, "closed", user?.id, data); + } + }; + const handleModal = (/*status, userId*/) => { + render( + + ); + }; - const handleModal = (/*status, userId*/) => { + const handleUpdateTicketStatus = async (e, status, userId, schedulingData = {}) => { + setLoading(true); + try { + if (status === "closed") { + await api.put(`/tickets/${ticket.id}`, { + status: status, + userId: userId || null, + schedulingNotifyData: JSON.stringify(schedulingData), + }); + } else { + await api.put(`/tickets/${ticket.id}`, { + status: status, + userId: userId || null, + }); + } - render() - - }; - + setLoading(false); + if (status === "open") { + history.push(`/tickets/${ticket.id}`); + } else { + history.push("/tickets"); + } + } catch (err) { + setLoading(false); + toastError(err); + } + }; - const handleUpdateTicketStatus = async (e, status, userId, schedulingData={}) => { - - setLoading(true); - try { - - if(status==='closed'){ + return ( +
+ {ticket.status === "closed" && ( + } + size="small" + onClick={(e) => handleUpdateTicketStatus(e, "open", user?.id)} + > + {i18n.t("messagesList.header.buttons.reopen")} + + )} + {ticket.status === "open" && ( + <> + } + size="small" + onClick={(e) => handleUpdateTicketStatus(e, "pending", null)} + > + {i18n.t("messagesList.header.buttons.return")} + - await api.put(`/tickets/${ticket.id}`, { - status: status, - userId: userId || null, - schedulingNotifyData: JSON.stringify(schedulingData) - }); + { + handleModal(); + // handleUpdateTicketStatus(e, "closed", user?.id) + }} + > + {i18n.t("messagesList.header.buttons.resolve")} + - } - else{ - - await api.put(`/tickets/${ticket.id}`, { - status: status, - userId: userId || null - }); - - } - - setLoading(false); - if (status === "open") { - history.push(`/tickets/${ticket.id}`); - } else { - history.push("/tickets"); - } - } catch (err) { - setLoading(false); - toastError(err); - } - - - - - }; - - return ( -
- {ticket.status === "closed" && ( - } - size="small" - onClick={e => handleUpdateTicketStatus(e, "open", user?.id)} - > - {i18n.t("messagesList.header.buttons.reopen")} - - )} - {ticket.status === "open" && ( - <> - } - size="small" - onClick={e => handleUpdateTicketStatus(e, "pending", null)} - > - {i18n.t("messagesList.header.buttons.return")} - - - { - - - handleModal() - // handleUpdateTicketStatus(e, "closed", user?.id) - - }} - > - {i18n.t("messagesList.header.buttons.resolve")} - - - - - - - - )} - {ticket.status === "pending" && ( - handleUpdateTicketStatus(e, "open", user?.id)} - > - {i18n.t("messagesList.header.buttons.accept")} - - )} -
- ); + + + + + + )} + {ticket.status === "pending" && ( + handleUpdateTicketStatus(e, "open", user?.id)} + > + {i18n.t("messagesList.header.buttons.accept")} + + )} +
+ ); }; export default TicketActionButtons; + diff --git a/frontend/src/components/TicketsQueueSelect/index.js b/frontend/src/components/TicketsQueueSelect/index.js index 2d4d225..e3d1f09 100644 --- a/frontend/src/components/TicketsQueueSelect/index.js +++ b/frontend/src/components/TicketsQueueSelect/index.js @@ -6,55 +6,52 @@ import Select from "@material-ui/core/Select"; import { Checkbox, ListItemText } from "@material-ui/core"; import { i18n } from "../../translate/i18n"; -const TicketsQueueSelect = ({ - userQueues, - selectedQueueIds = [], - onChange, -}) => { - const handleChange = e => { - onChange(e.target.value); - }; +const TicketsQueueSelect = ({ userQueues, selectedQueueIds = [], onChange }) => { + const handleChange = (e) => { + onChange(e.target.value); + }; - return ( -
- - - -
- ); + return ( +
+ + + +
+ ); }; export default TicketsQueueSelect; + diff --git a/frontend/src/components/UserModal/UserModal.jsx b/frontend/src/components/UserModal/UserModal.jsx index 3f9776c..f3cd056 100644 --- a/frontend/src/components/UserModal/UserModal.jsx +++ b/frontend/src/components/UserModal/UserModal.jsx @@ -6,30 +6,62 @@ import api from "../../services/api"; import { AuthContext } from "../../context/Auth/AuthContext"; import { Can } from "../Can"; -import BtnComponent from "../Base/BTN/Btn"; +import BtnComponent from "../Base/Btn/Btn"; import FormComponent from "../Base/Form/FormComponent"; import InputComponent from "../Base/Form/Input/InputComponent"; import UserModalComponent from "./UserModalImg/UserModalComponent"; import UserImg from "../../assets/images/User/user.jpg"; -const UserModal = ({ modal, click }) => { +const UserModal = ({ modal, click, userId }) => { const { user } = React.useContext(AuthContext); - const initalData = { - name: user.name, - email: user.email, - perfil:user.profile, - } - console.log(user); + const InitalState = { + name: "", + email: "", + password: "", + profile: "", + }; + const [userData, setUserData] = React.useState(InitalState); + const [selectedQueueIds, setSelectedQueueIds] = React.useState([]); + + React.useEffect(() => { + // const fetchUser = async () => { + // if (!userId) return; + // try { + // const { data } = await api.get(`/users/${userId}`); + // setUserData((prevState) => { + // return console.log({ ...prevState, ...data }); + // }); + // const userQueueIds = data.queues?.map((queue) => queue.id); + // setSelectedQueueIds(userQueueIds); + // } catch (err) { + // alert(err); + // } + // }; + + // fetchUser(); + }, [userId]); return ( - - + setUserData({ name: event.target.data })} + /> + setUserData({ email: event.target.data })} + /> @@ -53,4 +85,5 @@ id: 2 name: "teste" profile: "master" queues: [] -tokenVersion: 0 */ \ No newline at end of file +tokenVersion: 0 */ + diff --git a/frontend/src/layout/index.js b/frontend/src/layout/index.js index 353899e..cfc1d04 100644 --- a/frontend/src/layout/index.js +++ b/frontend/src/layout/index.js @@ -1,21 +1,16 @@ import React from "react"; -import Loading from "../components/LoadingScreen/Loading"; +import LoadingScreen from "../components/LoadingScreen/LoadingScreen"; import MainContainer from "../components/Base/MainContainer/MainContainer"; import { AuthContext } from "../context/Auth/AuthContext"; import { i18n } from "../translate/i18n"; import MenuComponent from "../components/Menu/MenuComponent"; const LoggedInLayout = ({ children }) => { - const { handleLogout, loading, isAuth } = React.useContext(AuthContext); - const logout = (e) => { - handleLogout(); - }; - const { user } = React.useContext(AuthContext); - console.log(user.name); + const { loading, user } = React.useContext(AuthContext); if (loading) { - return ; + return ; } return ( diff --git a/frontend/src/pages/Report/index.js b/frontend/src/pages/Report/index.js index 4b5d94d..be8b5c1 100644 --- a/frontend/src/pages/Report/index.js +++ b/frontend/src/pages/Report/index.js @@ -2,109 +2,97 @@ import React, { useState, useEffect, useReducer, useContext, useRef } from "reac import MainContainer from "../../components/MainContainer"; import api from "../../services/api"; import SelectField from "../../components/Report/SelectField"; -//import { data } from '../../components/Report/MTable/data'; -import DatePicker1 from '../../components/Report/DatePicker' -import DatePicker2 from '../../components/Report/DatePicker' +//import { data } from '../../components/Report/MTable/data'; +import DatePicker1 from "../../components/Report/DatePicker"; +import DatePicker2 from "../../components/Report/DatePicker"; import MTable from "../../components/Report/MTable"; -import PropTypes from 'prop-types'; -import Box from '@mui/material/Box'; +import PropTypes from "prop-types"; +import Box from "@mui/material/Box"; import { AuthContext } from "../../context/Auth/AuthContext"; import { Can } from "../../components/Can"; import { Button } from "@material-ui/core"; import ReportModal from "../../components/ReportModal"; -import MaterialTable from 'material-table'; +import MaterialTable from "material-table"; -import LogoutIcon from '@material-ui/icons/CancelOutlined'; +import LogoutIcon from "@material-ui/icons/CancelOutlined"; import { CSVLink } from "react-csv"; +import openSocket from "socket.io-client"; -import openSocket from "socket.io-client"; - -const report = [{ 'value': '1', 'label': 'Atendimento por atendentes' }, { 'value': '2', 'label': 'Usuários online/offline' }] +const report = [ + { value: "1", label: "Atendimento por atendentes" }, + { value: "2", label: "Usuários online/offline" }, +]; let columns = [ { - key: 'ticket.whatsapp.name', - label: 'Loja', - + key: "ticket.whatsapp.name", + label: "Loja", }, { - key: 'id', - label: 'id Mensagem', + key: "id", + label: "id Mensagem", }, { - key: 'ticket.id', - label: 'id Conversa', + key: "ticket.id", + label: "id Conversa", }, { - key: 'ticket.contact.name', - label: 'Cliente', + key: "ticket.contact.name", + label: "Cliente", }, { - key: 'ticket.user.name', - label: 'Atendente', + key: "ticket.user.name", + label: "Atendente", }, { - key: 'body', - label: 'Mensagem', + key: "body", + label: "Mensagem", }, { - key: 'fromMe', - label: 'Sentido', + key: "fromMe", + label: "Sentido", }, { - key: 'createdAt', - label: 'Criada', + key: "createdAt", + label: "Criada", }, { - key: 'ticket.contact.number', - label: 'Telefone cliente', + key: "ticket.contact.number", + label: "Telefone cliente", }, { - key: 'ticket.queue.name', - label: 'Fila', + key: "ticket.queue.name", + label: "Fila", }, { - key: 'ticket.status', - label: 'Status', + key: "ticket.status", + label: "Status", }, { - key: 'ticket.statusChatEnd', - label: 'Status de encerramento', - } - -] + key: "ticket.statusChatEnd", + label: "Status de encerramento", + }, +]; // - - - - - - - const reducerQ = (state, action) => { - - - - if (action.type === "DELETE_USER_STATUS") { - const userId = action.payload; //console.log('Entrou no delete user status userId: ', userId) const userIndex = state.findIndex((u) => `${u.id}` === `${userId}`); - // console.log('>>>>>>>>>>>>>>>>>>>>> userIndex: ', userIndex) + // console.log('>>>>>>>>>>>>>>>>>>>>> userIndex: ', userIndex) if (userIndex !== -1) { state.splice(userIndex, 1); @@ -113,115 +101,90 @@ const reducerQ = (state, action) => { return [...state]; } - - - if (action.type === 'LOAD_QUERY') { - - - const queries = action.payload - const newQueries = [] + if (action.type === "LOAD_QUERY") { + const queries = action.payload; + const newQueries = []; queries.forEach((query) => { - - const queryIndex = state.findIndex((q) => q.id === query.id) + const queryIndex = state.findIndex((q) => q.id === query.id); if (queryIndex !== -1) { - state[queryIndex] = query - } - else { - newQueries.push(query) + state[queryIndex] = query; + } else { + newQueries.push(query); } + }); - }) - - return [...state, ...newQueries] + return [...state, ...newQueries]; } - if (action.type === "UPDATE_STATUS_ONLINE") { + let onlineUser = action.payload; + let index = -1; - let onlineUser = action.payload - let index = -1 - - // console.log('sssssssssstate: ', state, ' | ONLINE USERS: onlineUser.userId ', onlineUser.userId) + // console.log('sssssssssstate: ', state, ' | ONLINE USERS: onlineUser.userId ', onlineUser.userId) if (onlineUser.sumOpen || onlineUser.sumClosed) { - index = state.findIndex((e) => ((onlineUser.sumOpen && e.id === onlineUser.sumOpen.userId) || (onlineUser.sumClosed && e.id === onlineUser.sumClosed.userId))) - } - else { - index = state.findIndex((e) => `${e.id}` === `${onlineUser.userId}`) + index = state.findIndex( + (e) => + (onlineUser.sumOpen && e.id === onlineUser.sumOpen.userId) || + (onlineUser.sumClosed && e.id === onlineUser.sumClosed.userId) + ); + } else { + index = state.findIndex((e) => `${e.id}` === `${onlineUser.userId}`); } - //console.log(' *********************** index: ', index) - - + //console.log(' *********************** index: ', index) if (index !== -1) { - - // console.log('ENTROU NO INDEX') - + // console.log('ENTROU NO INDEX') if (!("statusOnline" in state[index])) { - state[index].statusOnline = onlineUser + state[index].statusOnline = onlineUser; + } else if ("statusOnline" in state[index]) { + state[index].statusOnline["status"] = onlineUser.status; } - else if ("statusOnline" in state[index]) { - state[index].statusOnline['status'] = onlineUser.status - } - if ("onlineTime" in onlineUser) { - if ("sumOnlineTime" in state[index]) { - state[index].sumOnlineTime['sum'] = (onlineUser.onlineTime).split(" ")[1] - } - else if (!("sumOnlineTime" in state[index])) { - state[index].sumOnlineTime = { userId: onlineUser.userId, sum: (onlineUser.onlineTime).split(" ")[1] } + state[index].sumOnlineTime["sum"] = onlineUser.onlineTime.split(" ")[1]; + } else if (!("sumOnlineTime" in state[index])) { + state[index].sumOnlineTime = { + userId: onlineUser.userId, + sum: onlineUser.onlineTime.split(" ")[1], + }; } } - if (onlineUser.sumOpen) { - if ("sumOpen" in state[index]) { - // console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1 | state[index].sumOpen["count"]: ', state[index].sumOpen['count'], ' | onlineUser.sumOpen.count: ', onlineUser.sumOpen.count) - state[index].sumOpen['count'] = onlineUser.sumOpen.count + // console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1 | state[index].sumOpen["count"]: ', state[index].sumOpen['count'], ' | onlineUser.sumOpen.count: ', onlineUser.sumOpen.count) + state[index].sumOpen["count"] = onlineUser.sumOpen.count; } else if (!("sumOpen" in state[index])) { - // console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1') - state[index].sumOpen = onlineUser.sumOpen + // console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1') + state[index].sumOpen = onlineUser.sumOpen; } - } if (onlineUser.sumClosed) { - if ("sumClosed" in state[index]) { - // console.log(' >>>>>>>>>>>>>>>>>> sumClosed 1 | state[index].sumClosed["count"]: ', state[index].sumClosed['count'], ' | onlineUser.sumClosed.count: ', onlineUser.sumClosed.count) - state[index].sumClosed['count'] = onlineUser.sumClosed.count + // console.log(' >>>>>>>>>>>>>>>>>> sumClosed 1 | state[index].sumClosed["count"]: ', state[index].sumClosed['count'], ' | onlineUser.sumClosed.count: ', onlineUser.sumClosed.count) + state[index].sumClosed["count"] = onlineUser.sumClosed.count; } else if (!("sumClosed" in state[index])) { - // console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1') - state[index].sumClosed = onlineUser.sumClosed + // console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1') + state[index].sumClosed = onlineUser.sumClosed; } - } - - } - return [...state] - + return [...state]; } - - if (action.type === "RESET") { return []; } - -} - - +}; const reducer = (state, action) => { - if (action.type === "LOAD_USERS") { const users = action.payload; const newUsers = []; @@ -253,24 +216,20 @@ const reducer = (state, action) => { } }; - - - function Item(props) { const { sx, ...other } = props; return ( (theme.palette.mode === 'dark' ? '#101010' : '#fff'), - color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'), - border: '1px solid', - borderColor: (theme) => - theme.palette.mode === 'dark' ? 'grey.800' : 'grey.300', + bgcolor: (theme) => (theme.palette.mode === "dark" ? "#101010" : "#fff"), + color: (theme) => (theme.palette.mode === "dark" ? "grey.300" : "grey.800"), + border: "1px solid", + borderColor: (theme) => (theme.palette.mode === "dark" ? "grey.800" : "grey.300"), p: 1, m: 1, borderRadius: 2, - fontSize: '0.875rem', - fontWeight: '700', + fontSize: "0.875rem", + fontWeight: "700", ...sx, }} {...other} @@ -280,76 +239,64 @@ function Item(props) { Item.propTypes = { sx: PropTypes.oneOfType([ - PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool]), - ), + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object, ]), }; - - let columnsData = [ - { title: 'Unidade', field: 'whatsapp.name' }, - { title: 'Atendente', field: 'user.name' }, - { title: 'Contato', field: 'contact.number' }, - { title: 'Nome', field: 'contact.name' }, - { title: 'Assunto', field: 'queue.name' }, + { title: "Unidade", field: "whatsapp.name" }, + { title: "Atendente", field: "user.name" }, + { title: "Contato", field: "contact.number" }, + { title: "Nome", field: "contact.name" }, + { title: "Assunto", field: "queue.name" }, - { title: 'Status', field: 'status' }, - - { title: 'Criado', field: 'createdAt' }, + { title: "Status", field: "status" }, + + { title: "Criado", field: "createdAt" }, //{title: 'Atualizado', field: 'updatedAt'}, - {title: 'Status de encerramento', field: 'statusChatEnd'}]; - + { title: "Status de encerramento", field: "statusChatEnd" }, +]; const Report = () => { - - const csvLink = useRef() + const csvLink = useRef(); const { user: userA } = useContext(AuthContext); - //-------- + //-------- const [searchParam] = useState(""); //const [loading, setLoading] = useState(false); - //const [hasMore, setHasMore] = useState(false); + //const [hasMore, setHasMore] = useState(false); const [pageNumber, setPageNumber] = useState(1); const [users, dispatch] = useReducer(reducer, []); - //const [columns, setColums] = useState([]) - const [startDate, setDatePicker1] = useState(new Date()) - const [endDate, setDatePicker2] = useState(new Date()) - const [userId, setUser] = useState(null) - const [query, dispatchQ] = useReducer(reducerQ, []) + //const [columns, setColums] = useState([]) + const [startDate, setDatePicker1] = useState(new Date()); + const [endDate, setDatePicker2] = useState(new Date()); + const [userId, setUser] = useState(null); + const [query, dispatchQ] = useReducer(reducerQ, []); - const [dataCSV, setDataCSV] = useState([]) + const [dataCSV, setDataCSV] = useState([]); const [isMount, setIsMount] = useState(true); - const [reportOption, setReport] = useState('1') - const [reporList,] = useState(report) - const [profile, setProfile] = useState('') + const [reportOption, setReport] = useState("1"); + const [reporList] = useState(report); + const [profile, setProfile] = useState(""); const [dataRows, setData] = useState([]); - - - useEffect(() => { dispatch({ type: "RESET" }); - dispatchQ({ type: "RESET" }) + dispatchQ({ type: "RESET" }); setPageNumber(1); }, [searchParam, profile]); - - useEffect(() => { - //setLoading(true); + //setLoading(true); const delayDebounceFn = setTimeout(() => { - const fetchUsers = async () => { try { - //console.log('profile: ', profile) const { data } = await api.get("/users/", { @@ -358,111 +305,87 @@ const Report = () => { dispatch({ type: "LOAD_USERS", payload: data.users }); //setHasMore(data.hasMore); - //setLoading(false); - - + //setLoading(false); } catch (err) { console.log(err); } }; fetchUsers(); - }, 500); return () => clearTimeout(delayDebounceFn); }, [searchParam, pageNumber, reportOption, profile]); - - useEffect(() => { - //setLoading(true); const delayDebounceFn = setTimeout(() => { - const fetchQueries = async () => { try { - - if (reportOption === '1') { - - const dataQuery = await api.get("/reports/", { params: { userId, startDate, endDate }, }); - dispatchQ({ type: "RESET" }) + if (reportOption === "1") { + const dataQuery = await api.get("/reports/", { + params: { userId, startDate, endDate }, + }); + dispatchQ({ type: "RESET" }); dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }); //setLoading(false); - // console.log('dataQuery: ', dataQuery.data) + // console.log('dataQuery: ', dataQuery.data) - // console.log() - - } - else if (reportOption === '2') { - - const dataQuery = await api.get("/reports/user/services", { params: { userId, startDate, endDate }, }); - dispatchQ({ type: "RESET" }) + // console.log() + } else if (reportOption === "2") { + const dataQuery = await api.get("/reports/user/services", { + params: { userId, startDate, endDate }, + }); + dispatchQ({ type: "RESET" }); dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }); //setLoading(false); - // console.log('REPORT 2 dataQuery : ', dataQuery.data) + // console.log('REPORT 2 dataQuery : ', dataQuery.data) //console.log() - } - } catch (err) { console.log(err); } }; fetchQueries(); - }, 500); return () => clearTimeout(delayDebounceFn); - }, [userId, startDate, endDate, reportOption]); - // Get from child 1 const datePicker1Value = (data) => { - - setDatePicker1(data) - } + setDatePicker1(data); + }; // Get from child 2 const datePicker2Value = (data) => { - - setDatePicker2(data) - } + setDatePicker2(data); + }; // Get from child 3 const textFieldSelectUser = (data) => { - - setUser(data) - } - - - + setUser(data); + }; // Get from report option const reportValue = (data) => { + setReport(data); - setReport(data) - - // console.log(' data: ', data) - } + // console.log(' data: ', data) + }; useEffect(() => { - - if (reportOption === '1') { - setProfile('') + if (reportOption === "1") { + setProfile(""); + } else if (reportOption === "2") { + setProfile("user"); } - else if (reportOption === '2') { - setProfile('user') - } - - }, [reportOption]) - + }, [reportOption]); // useEffect(() => { @@ -470,108 +393,87 @@ const Report = () => { // }, [query]) - - - // test del + // test del const handleCSVMessages = () => { - - // setLoading(true); + // setLoading(true); const fetchQueries = async () => { - try { + const dataQuery = await api.get("/reports/messages", { + params: { userId, startDate, endDate }, + }); - const dataQuery = await api.get("/reports/messages", { params: { userId, startDate, endDate }, }); - - // console.log('dataQuery messages: ', dataQuery.data) + // console.log('dataQuery messages: ', dataQuery.data) if (dataQuery.data.length > 0) { - let dataCSVFormat = dataQuery.data; for (var i = 0; i < dataCSVFormat.length; i++) { if (dataCSVFormat[i].fromMe) { - dataCSVFormat[i].fromMe = 'Atendente' - } - else { - dataCSVFormat[i].fromMe = 'Cliente' + dataCSVFormat[i].fromMe = "Atendente"; + } else { + dataCSVFormat[i].fromMe = "Cliente"; } } // console.log('dataCSVFormat: ', dataCSVFormat) - setDataCSV(dataCSVFormat) + setDataCSV(dataCSVFormat); setIsMount(false); // setDataCSV(dataQuery.data) } - // setLoading(false); - + // setLoading(false); } catch (err) { console.log(err); } }; fetchQueries(); - - } - - - - + }; useEffect(() => { - if (isMount) { return; } - csvLink.current.link.click() - + csvLink.current.link.click(); }, [dataCSV, isMount, csvLink]); - - - useEffect(() => { - - if (reportOption === '2') { - + if (reportOption === "2") { const socket = openSocket(process.env.REACT_APP_BACKEND_URL); socket.on("onlineStatus", (data) => { + // setLoading(true); - // setLoading(true); + let date = new Date().toLocaleDateString("pt-BR").split("/"); + let dateToday = `${date[2]}-${date[1]}-${date[0]}`; + // console.log('date: ', new Date(startDate).toLocaleDateString('pt-BR')) + // console.log('date2: ', startDate) - let date = new Date().toLocaleDateString('pt-BR').split('/') - let dateToday = `${date[2]}-${date[1]}-${date[0]}` - - // console.log('date: ', new Date(startDate).toLocaleDateString('pt-BR')) - // console.log('date2: ', startDate) - - - if (data.action === "logout" || (data.action === "update" && - ((`${startDate}` === `${endDate}`) && (`${endDate}` === `${dateToday}`) && (`${startDate}` === `${dateToday}`)))) { - + if ( + data.action === "logout" || + (data.action === "update" && + `${startDate}` === `${endDate}` && + `${endDate}` === `${dateToday}` && + `${startDate}` === `${dateToday}`) + ) { //console.log('UPDATE FROM ONLINE/OFFLINE LOGED USERS: ', data.userOnlineTime, ' | data.action : ', data.action) dispatchQ({ type: "UPDATE_STATUS_ONLINE", payload: data.userOnlineTime }); - - } - else if (data.action === "delete") { + } else if (data.action === "delete") { dispatchQ({ type: "DELETE_USER_STATUS", payload: data.userOnlineTime }); } // setLoading(false); - }); socket.on("user", (data) => { - if (data.action === "delete") { - // console.log(' entrou no delete user: ', data) + // console.log(' entrou no delete user: ', data) dispatch({ type: "DELETE_USER", payload: +data.userId }); } }); @@ -579,13 +481,9 @@ const Report = () => { return () => { socket.disconnect(); }; - } - - }, [reportOption, startDate, endDate]); - // const handleDeleteRows = (id) => { // let _data = [...dataRows]; @@ -593,46 +491,42 @@ const Report = () => { // _data.forEach(rd => { // _data = _data.filter(t => t.id !== id); // }); - // setData(_data); + // setData(_data); // }; - useEffect(() => { - - //if (!loading) { - + //if (!loading) { // setData(query.map(({ scheduleReminder, ...others }) => ( // { ...others, 'scheduleReminder': `${others.statusChatEndId}` === '3' ? 'Agendamento' : 'Lembrete' } // ))) // } - setData(query.map((column) => { return { ...column } })) - - }, [query]) + setData( + query.map((column) => { + return { ...column }; + }) + ); + }, [query]); const handleLogouOnlineUser = async (userId) => { try { await api.get(`/users/logout/${userId}`); //toast.success(("Desloged!")); - //handleDeleteRows(scheduleId) + //handleDeleteRows(scheduleId) } catch (err) { // toastError(err); } - - }; - return ( - ( - + { @@ -642,13 +536,27 @@ const Report = () => { - + + + + + + + -
- - {reportOption === '1' && +
{/*
- - } - - + )} - - - - - - - - - - {reportOption === '1' && - - + + {reportOption === "1" && ( + - } - {reportOption === '2' && - + table_title={"Atendimento por atendentes"} + /> + )} + {reportOption === "2" && ( imagem de perfil do whatsapp }, + { title: "Nome", field: "name", cellStyle: { whiteSpace: "nowrap" } }, + + { + title: "Status", + field: "statusOnline.status", + + cellStyle: (e, rowData) => { + if (rowData["statusOnline"] && rowData["statusOnline"].status) { + if (rowData["statusOnline"].status === "offline") { + return { color: "red" }; + } else if (rowData["statusOnline"].status === "online") { + return { color: "green" }; + } else if (rowData["statusOnline"].status === "logout...") { + return { color: "orange" }; + } else if (rowData["statusOnline"].status === "waiting...") { + return { color: "orange" }; + } + } + }, }, - }} - - title="Usuários online/offline" - columns={ - [ - - // { title: 'Foto', field: 'ticket.contact.profilePicUrl', render: rowData => imagem de perfil do whatsapp }, - { title: 'Nome', field: 'name', cellStyle: {whiteSpace: 'nowrap'}, }, - - { - title: 'Status', field: 'statusOnline.status', - - cellStyle: (e, rowData) => { - - if (rowData['statusOnline'] && rowData['statusOnline'].status) { - - if (rowData['statusOnline'].status === 'offline') { - - return { color: "red" }; - } - else if (rowData['statusOnline'].status === 'online') { - return { color: "green" }; - } - else if (rowData['statusOnline'].status === 'logout...') { - return { color: "orange" }; - } - else if (rowData['statusOnline'].status === 'waiting...') { - return { color: "orange" }; - } - } - - - }, - - }, - - { title: 'Tempo online', field: 'sumOnlineTime.sum' }, - { title: 'Data inicio', field: 'startDate' }, - { title: 'Data fim', field: 'endDate' }, - { title: 'Em atendimento', field: 'sumOpen.count' }, - { title: 'Finalizado', field: 'sumClosed.count' }, - - ] - } + { title: "Tempo online", field: "sumOnlineTime.sum" }, + { title: "Data inicio", field: "startDate" }, + { title: "Data fim", field: "endDate" }, + { title: "Em atendimento", field: "sumOpen.count" }, + { title: "Finalizado", field: "sumClosed.count" }, + ]} data={dataRows} // icons={tableIcons} actions={[ (rowData) => { - - if (rowData.statusOnline && - rowData.statusOnline['status'] && - rowData.statusOnline['status'] === 'online') { - - + if ( + rowData.statusOnline && + rowData.statusOnline["status"] && + rowData.statusOnline["status"] === "online" + ) { return { icon: LogoutIcon, - tooltip: 'deslogar', + tooltip: "deslogar", disable: false, onClick: (event, rowData) => { - - // console.log(' ROW DATA INFO: ', rowData, ' | rowData: ', rowData.id) - handleLogouOnlineUser(rowData.id) - } - } - - + // console.log(' ROW DATA INFO: ', rowData, ' | rowData: ', rowData.id) + handleLogouOnlineUser(rowData.id); + }, + }; } - } + }, ]} + options={{ + search: true, + selection: false, + paging: false, + padding: "dense", + sorting: true, + //loadingType: 'linear', + searchFieldStyle: { + width: 300, + }, + pageSize: 20, + headerStyle: { + position: "sticky", + top: "0", + }, + maxBodyHeight: "400px", - options={ - { - search: true, - selection: false, - paging: false, - padding: 'dense', - sorting: true, - //loadingType: 'linear', - searchFieldStyle: { - width: 300, - }, + rowStyle: { + fontSize: 14, + }, - pageSize: 20, - headerStyle: { - position: "sticky", - top: "0" - }, - maxBodyHeight: "400px", + // cellStyle: (rowData) => { + // return { + // fontSize: 12, + // color: "#fff", - rowStyle: { - fontSize: 14, - } - - - // cellStyle: (rowData) => { - // return { - // fontSize: 12, - // color: "#fff", - - // }; - // } - - - - - - - - }} + // }; + // } + }} /> - - } - + )} - - )} /> - ) + ); }; export default Report; + diff --git a/frontend/src/pages/Tickets/Tickets.jsx b/frontend/src/pages/Tickets/Tickets.jsx new file mode 100644 index 0000000..78ef468 --- /dev/null +++ b/frontend/src/pages/Tickets/Tickets.jsx @@ -0,0 +1,20 @@ +import React from "react"; +import { useParams } from "react-router-dom"; + +import TicketsStyled from "./Tickets.style"; + +import TicketsManager from "../../components/Ticket/TicketsManager/TicketsManager"; +import Ticket from "../../components/Ticket/Ticket"; + +const Tickets = () => { + const { ticketId } = useParams(); + return ( + + + {ticketId ? :
Não tem nada
} +
+ ); +}; + +export default Tickets; + diff --git a/frontend/src/pages/Tickets/Tickets.style.jsx b/frontend/src/pages/Tickets/Tickets.style.jsx new file mode 100644 index 0000000..b690fc6 --- /dev/null +++ b/frontend/src/pages/Tickets/Tickets.style.jsx @@ -0,0 +1,10 @@ +import styled from "styled-components"; + +const TicketsStyled = styled.div` + display: flex; + flex-direction: row; + min-height: auto; + +`; + +export default TicketsStyled \ No newline at end of file diff --git a/frontend/src/pages/Tickets/index.js b/frontend/src/pages/Tickets/index.js index 461145a..1c68971 100644 --- a/frontend/src/pages/Tickets/index.js +++ b/frontend/src/pages/Tickets/index.js @@ -4,7 +4,7 @@ import Grid from "@material-ui/core/Grid"; import Paper from "@material-ui/core/Paper"; import { makeStyles } from "@material-ui/core/styles"; -import TicketsManager from "../../components/TicketsManager/"; +import TicketsManager from "../../components/Ticket/TicketsManager/index"; import Ticket from "../../components/Ticket/"; import { i18n } from "../../translate/i18n"; diff --git a/frontend/src/routes/Route.js b/frontend/src/routes/Route.js index 788501e..8aa58b5 100644 --- a/frontend/src/routes/Route.js +++ b/frontend/src/routes/Route.js @@ -2,7 +2,7 @@ import React, { useContext } from "react"; import { Route as RouterRoute, Redirect } from "react-router-dom"; import { AuthContext } from "../context/Auth/AuthContext"; -import Loading from "../components/LoadingScreen/Loading" +import LoadingScreen from "../components/LoadingScreen/LoadingScreen" const Route = ({ component: Component, isPrivate = false, ...rest }) => { const { isAuth, loading } = useContext(AuthContext); @@ -10,7 +10,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => { if (!isAuth && isPrivate) { return ( <> - {loading && } + {loading && } ); @@ -19,7 +19,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => { if (isAuth && !isPrivate) { return ( <> - {loading && } + {loading && } ; ); @@ -27,7 +27,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => { return ( <> - {loading && } + {loading && } ); diff --git a/frontend/src/routes/index.js b/frontend/src/routes/index.js index 431140c..ac11b68 100644 --- a/frontend/src/routes/index.js +++ b/frontend/src/routes/index.js @@ -10,7 +10,7 @@ import SchedulesReminder from "../pages/SchedulesReminder/"; import Login from "../pages/Login/"; import Signup from "../pages/Signup/"; -import Tickets from "../pages/Tickets/"; +import Tickets from "../pages/Tickets/Tickets"; import Connections from "../pages/Connections/"; import Settings from "../pages/Settings/"; import Users from "../pages/Users"; diff --git a/frontend/src/style/varibles.jsx b/frontend/src/style/varibles.jsx index ecf66e6..68e8e71 100644 --- a/frontend/src/style/varibles.jsx +++ b/frontend/src/style/varibles.jsx @@ -39,22 +39,24 @@ export const color = { grisOscuro: "#3C3C3B", blanco: "#FFFFFF", }, - complement:{ + complement: { azulCielo: "#55A5DC", azulOscuro: "#212F3C", crisClaro: "#F6F6F6", }, - status:{ + status: { no: "#FF0000", yes: "#00BE1E", - warning: "#FFC700" + warning: "#ffc800c7", }, - gradient:{ - bgOpacity:"#212F3CD8", - placeholder:"#ffffff83" + gradient: { + bgOpacity: "#212f3cd7", + placeholder: "#ffffff83", + border: "#55A5DC3F", + text: "#AEBAC1", + }, + shadow: { + dark: "2px 2px 4px 2px", }, - shadow:{ - dark:"2px 2px 4px 2px" - } };