Merge pull request #5 from RenatoDiGiacomo/layout/tickets

Layout/tickets
pull/14/head
Renato Di Giacomo 2022-07-28 12:23:24 -03:00 committed by GitHub
commit 8cb36fc636
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 1693 additions and 762 deletions

15
.vscode/launch.json vendored 100644
View File

@ -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}"
}
]
}

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.6667 18.6667H19.6133L19.24 18.3067C20.5467 16.7867 21.3333 14.8133 21.3333 12.6667C21.3333 7.88 17.4533 4 12.6667 4C7.88 4 4 7.88 4 12.6667C4 17.4533 7.88 21.3333 12.6667 21.3333C14.8133 21.3333 16.7867 20.5467 18.3067 19.24L18.6667 19.6133V20.6667L25.3333 27.32L27.32 25.3333L20.6667 18.6667ZM12.6667 18.6667C9.34667 18.6667 6.66667 15.9867 6.66667 12.6667C6.66667 9.34667 9.34667 6.66667 12.6667 6.66667C15.9867 6.66667 18.6667 9.34667 18.6667 12.6667C18.6667 15.9867 15.9867 18.6667 12.6667 18.6667Z" fill="current" />
</svg>

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,12 +0,0 @@
import React from "react";
import BtnBaseStyled from "./Btn.styled";
const BtnComponent = ({ text, bgcolor, fontcolor,...props }) => {
return (
<BtnBaseStyled bgcolor={bgcolor} fontcolor={fontcolor} {...props}>
{text}
</BtnBaseStyled>
);
};
export default BtnComponent;

View File

@ -0,0 +1,13 @@
import React from "react";
import { BadgeComponentStyled } from "./BadgeComponent.style";
const BadgeComponent = ({ counter,fontSize, position, top, left, right, bottom,bgcolor }) => {
return (
<BadgeComponentStyled position={position} top={top} left={left} right={right} bottom={bottom} fontSize={fontSize} bgcolor={bgcolor}>
{counter}
</BadgeComponentStyled>
);
};
export default BadgeComponent;

View File

@ -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)};
`;

View File

@ -0,0 +1,13 @@
import React from "react";
import BtnBaseStyled from "./Btn.styled";
const BtnComponent = ({ text, bgcolor, fontSize, fontcolor, ...props }) => {
return (
<BtnBaseStyled bgcolor={bgcolor} fontcolor={fontcolor} fontSize={fontSize} {...props}>
{text}
</BtnBaseStyled>
);
};
export default BtnComponent;

View File

@ -8,7 +8,7 @@ const BtnBaseStyled = styled.button`
padding: 6px 16px 3px; padding: 6px 16px 3px;
border-radius: 5px; border-radius: 5px;
margin: 12px 0; margin: 12px 0;
font-size: 18px; font-size: ${({ fontSize }) => fontSize ? fontSize: "18px"};
font-family: "Helvetica55"; font-family: "Helvetica55";
vertical-align: baseline; vertical-align: baseline;
transition: all 0.2s linear; transition: all 0.2s linear;

View File

@ -1,7 +1,6 @@
import React from "react"; import React from "react";
import PageTitle from "../../PageTitle/PageTitle"; import PageTitle from "../../PageTitle/PageTitle";
import MainContainerStyled, { import MainContainerStyled, {
TitleContainerStyled, TitleContainerStyled,
ContentContainerStyled, ContentContainerStyled,

View File

@ -1,4 +1,5 @@
import styled from "styled-components"; import styled from "styled-components";
import { color } from "../../../style/varibles";
const MainContainerStyled = styled.main` const MainContainerStyled = styled.main`
padding-left: 97px; padding-left: 97px;
@ -21,6 +22,8 @@ const ContentContainerStyled = styled.div`
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
margin-top: 16px; margin-top: 16px;
border-radius: 5px;
background-color: ${color.complement.azulOscuro};
`; `;
export default MainContainerStyled; export default MainContainerStyled;

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import BtnComponent from "../Base/BTN/Btn"; import BtnComponent from "../Base/Btn/Btn";
import { import {
ConfirmationModalStyled, ConfirmationModalStyled,

View File

@ -1,9 +0,0 @@
import React from "react";
import LoadingStyled from "./Loading.style"
const Loading = () => {
return <LoadingStyled/>;
};
export default Loading;

View File

@ -0,0 +1,8 @@
import React from "react";
import LoadingStyled from "./LoadingScreen.style"
const LoadingScreen = () => {
return <LoadingStyled/>;
};
export default LoadingScreen;

View File

@ -3,7 +3,7 @@ import { color } from "../../../../style/varibles";
const InputComponentStyled = styled.input` const InputComponentStyled = styled.input`
width: 100%; width: 100%;
background: transparent; background: ${color.complement.azulOscuro};
border: none; border: none;
color: ${color.complement.azulCielo}; color: ${color.complement.azulCielo};
`; `;

View File

@ -63,7 +63,7 @@ const MenuComponent = () => {
<MenuItem icon={<Tickets />} text="Tickets" to="/tickets" hover={hover} /> <MenuItem icon={<Tickets />} text="Tickets" to="/tickets" hover={hover} />
<MenuItem icon={<Contact />} text="Contatos" to="/contacts" hover={hover} /> <MenuItem icon={<Contact />} text="Contatos" to="/contacts" hover={hover} />
<MenuItem icon={<Reminder />} text="Lembretes" to="/schedulesReminder" hover={hover} /> <MenuItem icon={<Reminder />} text="Lembretes" to="/schedulesReminder" hover={hover} />
<MenuItem icon={<FastAanswer />} text="Respostas" to="" hover={hover} /> <MenuItem icon={<FastAanswer />} text="Respostas" to="/quickAnswers" hover={hover} />
<Divider /> <Divider />
<MenuItem icon={<Users />} text="Usuários" to="/users" hover={hover} /> <MenuItem icon={<Users />} text="Usuários" to="/users" hover={hover} />
<MenuItem icon={<Rows />} text="Filas" to="/Queues" hover={hover} /> <MenuItem icon={<Rows />} text="Filas" to="/Queues" hover={hover} />

View File

@ -45,6 +45,7 @@ const useStyles = makeStyles((theme) => ({
flexGrow: 1, flexGrow: 1,
padding: "20px 20px 20px 20px", padding: "20px 20px 20px 20px",
overflowY: "scroll", overflowY: "scroll",
height: "50vh",
[theme.breakpoints.down("sm")]: { [theme.breakpoints.down("sm")]: {
paddingBottom: "90px", paddingBottom: "90px",
}, },
@ -541,11 +542,6 @@ const MessagesList = ({ ticketId, isGroup }) => {
})} })}
></span> ></span>
<div className={classes.quotedMsg}> <div className={classes.quotedMsg}>
{!message.quotedMsg?.fromMe && (
<span className={classes.messageContactName}>
{message.quotedMsg?.contact?.name}
</span>
)}
{message.quotedMsg?.body} {message.quotedMsg?.body}
</div> </div>
</div> </div>

View File

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { useLocation } from "react-router-dom";
import { PageTitleStyled } from "./PageTitle.style"; import { PageTitleStyled } from "./PageTitle.style";
import UserBtn from "./UserBtn/UserBtn"; import UserBtn from "./UserBtn/UserBtn";
@ -7,15 +8,54 @@ import { AuthContext } from "../../context/Auth/AuthContext";
import logo from "../../assets/images/Logo.png"; import logo from "../../assets/images/Logo.png";
const PageTitle = () => { const PageTitle = () => {
const path = useLocation();
const [title, setTitle] = React.useState();
const [modal, setModal] = React.useState(false); const [modal, setModal] = React.useState(false);
const { user } = React.useContext(AuthContext); 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 = () => { const handleModal = () => {
setModal(!modal); setModal(!modal);
}; };
return ( return (
<PageTitleStyled> <PageTitleStyled>
<h1>PageTitle</h1> <h1>{title}</h1>
<UserBtn user={user} img={logo} modal={modal} modalSet={handleModal} /> <UserBtn user={user} img={logo} modal={modal} modalSet={handleModal} />
</PageTitleStyled> </PageTitleStyled>
); );

View File

@ -34,7 +34,7 @@ const UserBtn = ({ user, img, modal, modalSet }) => {
<UserItem title="Sair" icon={<Signoff />} onClick={handleModal} /> <UserItem title="Sair" icon={<Signoff />} onClick={handleModal} />
</UserModalListStyled> </UserModalListStyled>
</UserModalStyled> </UserModalStyled>
<UserModal modal={modalUser} click={handleModalUser}/> <UserModal modal={modalUser} click={handleModalUser} userId={user.id}/>
<ConfirmationModal title="Sair?" modal={modalConfirm} click={{ handleModal, handleLogout }}> <ConfirmationModal title="Sair?" modal={modalConfirm} click={{ handleModal, handleLogout }}>
Deseja Sair do sistema? Deseja Sair do sistema?
</ConfirmationModal> </ConfirmationModal>

View File

@ -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 (
<div className="test" >
<TicketHeader loading={loading}>
<div>
<TicketInfo contact={contact} ticket={ticket} onClick={handleDrawerOpen} />
</div>
<div>
<TicketActionButtons ticket={ticket} statusChatEnd={statusChatEnd} />
</div>
</TicketHeader>
<ReplyMessageProvider>
<MessagesList ticketId={ticketId} isGroup={ticket.isGroup}></MessagesList>
<MessageInput ticketStatus={ticket.status} />
</ReplyMessageProvider>
<ContactDrawer
open={drawerOpen}
handleDrawerClose={handleDrawerClose}
contact={contact}
loading={loading}
/>
</div>
);
};
export default Ticket;

View File

@ -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};
`;

View File

@ -0,0 +1,10 @@
import React from 'react'
import TicketSearchInput from './TicketSearchInput/TicketSearchInput'
const TicketSearch = ({spinning,setNewTicketModalOpen, handleSearch}) => {
return (
<TicketSearchInput spinning={spinning} setNewTicketModalOpen={setNewTicketModalOpen} handleSearch={handleSearch}/>
)
}
export default TicketSearch

View File

@ -0,0 +1,10 @@
import React from 'react';
import {TicketSearchBtnStyled} from "./TicketSearchBtn.styled"
const TicketSearchBtn = ({setNewTicketModalOpen}) => {
return (
<TicketSearchBtnStyled onClick={()=>setNewTicketModalOpen(true)}>+</TicketSearchBtnStyled>
)
}
export default TicketSearchBtn

View File

@ -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";
`;

View File

@ -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 (
<TicketSearchDivStyled>
<TicketSearchInputBoxStyled spinning={spinning}>
<SearchIcon />
<TicketSearchInputStyled onChange={handleSearch} />
</TicketSearchInputBoxStyled>
<TicketSearchBtn setNewTicketModalOpen={setNewTicketModalOpen} />
</TicketSearchDivStyled>
);
};
export default TicketSearchInput;

View File

@ -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;
}
`;

View File

@ -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 (
<React.Fragment key={tickets.id}>
<TicketListItemStyled
queuecolor={tickets.queue}
onClick={() => handleSelectTicket(tickets.id)}
>
{tickets.contact.profilePicUrl ? (
<TicketImgStyled src={tickets.contact.profilePicUrl} alt={tickets.id} />
) : (
<TicketImgStyled src={DefaultUser} alt={tickets.id} />
)}
<TicketTitleStyled>
<p>{tickets.contact.name}</p>
<p>{tickets.lastMessage}</p>
{tickets.unreadMessages ? (
<BadgeComponent
counter={tickets.unreadMessages}
position="absolute"
right="6px"
top="6px"
/>
) : (
""
)}
</TicketTitleStyled>
<TicketDateStyled>
{isSameDay(parseISO(tickets.updatedAt), new Date()) ? (
<>{format(parseISO(tickets.updatedAt), "HH:mm")}</>
) : (
<>{format(parseISO(tickets.updatedAt), "dd/MM/yyyy")}</>
)}
</TicketDateStyled>
{tickets.status === "pending" ? (
<Btn
onClick={() => handleAcepptTicket(tickets.id)}
text="Aceitar"
bgcolor={color.complement.azulCielo}
fontcolor={color.pricinpal.blanco}
fontSize="12px"
/>
) : (
""
)}
</TicketListItemStyled>
</React.Fragment>
);
};
export default TicketListItem;

View File

@ -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;
`;

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useContext } from "react"; 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 { parseISO, format, isSameDay } from "date-fns";
import clsx from "clsx"; import clsx from "clsx";
@ -145,7 +145,7 @@ const TicketListItem = ({ ticket }) => {
if (ticket.status === "pending") return; if (ticket.status === "pending") return;
handleSelectTicket(ticket.id); handleSelectTicket(ticket.id);
}} }}
selected={ticketId && +ticketId === ticket.id} selected={ticketId && +ticketId === ticket.id} ///img src{id}.jpg
className={clsx(classes.ticket, { className={clsx(classes.ticket, {
[classes.pendingTicket]: ticket.status === "pending", [classes.pendingTicket]: ticket.status === "pending",
})} })}

View File

@ -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 <TicketsListSkeleton />;
if (status === valueTab)
return (
<TicketListStyled>
{ticketsList.map((ticket) => (
<TicketListItem tickets={ticket} key={ticket.id} />
))}
</TicketListStyled>
);
return null;
};
export default TicketsList;

View File

@ -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;

View File

@ -5,12 +5,12 @@ import { makeStyles } from "@material-ui/core/styles";
import List from "@material-ui/core/List"; import List from "@material-ui/core/List";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
import TicketListItem from "../TicketListItem"; import TicketListItem from "../../../TicketListItem";
import TicketsListSkeleton from "../TicketsListSkeleton"; import TicketsListSkeleton from "../../../TicketsListSkeleton";
import useTickets from "../../hooks/useTickets"; import useTickets from "../../../../hooks/useTickets";
import { i18n } from "../../translate/i18n"; import { i18n } from "../../../../translate/i18n";
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../../../context/Auth/AuthContext";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
ticketsListWrapper: { ticketsListWrapper: {
@ -74,8 +74,6 @@ const useStyles = makeStyles(theme => ({
const reducer = (state, action) => { const reducer = (state, action) => {
if (action.type === "LOAD_TICKETS") { if (action.type === "LOAD_TICKETS") {
const newTickets = action.payload; const newTickets = action.payload;
newTickets.forEach(ticket => { newTickets.forEach(ticket => {
// console.log('* ticket.unreadMessages: ',ticket.unreadMessages) // console.log('* ticket.unreadMessages: ',ticket.unreadMessages)

View File

@ -0,0 +1,42 @@
import React from "react";
import {
TicketListSkeletonStyled,
TicketSkeletonItemStyled,
TicketSkeletonTitleStyled,
TicketSkeletonDateStyled,
TicketSkeletonImgStyled,
} from "./TicketListSkeleton.style";
const TicketsSkeleton = () => {
return (
<TicketListSkeletonStyled>
<TicketSkeletonItemStyled>
<TicketSkeletonImgStyled/>
<TicketSkeletonTitleStyled>
<div></div>
<div></div>
</TicketSkeletonTitleStyled>
<TicketSkeletonDateStyled/>
</TicketSkeletonItemStyled>
<TicketSkeletonItemStyled>
<TicketSkeletonImgStyled/>
<TicketSkeletonTitleStyled>
<div></div>
<div></div>
</TicketSkeletonTitleStyled>
<TicketSkeletonDateStyled/>
</TicketSkeletonItemStyled>
<TicketSkeletonItemStyled>
<TicketSkeletonImgStyled/>
<TicketSkeletonTitleStyled>
<div></div>
<div></div>
</TicketSkeletonTitleStyled>
<TicketSkeletonDateStyled/>
</TicketSkeletonItemStyled>
</TicketListSkeletonStyled>
);
};
export default TicketsSkeleton;

View File

@ -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;
`;

View File

@ -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 (
<TicketsManagerStyled>
<TicketsTabs
setValueTab={setValueTab}
valueTab={valueTab}
count={{ openCount, pendingCount, closedCount }}
/>
<TicketSearch
spinning={spinning}
handleSearch={handleSearch}
setNewTicketModalOpen={setNewTicketModalOpen}
/>
<TicketsList
status="open"
updateCount={(v) => setOpenCount(v)}
showAll={showAllTickets}
selectedQueueIds={selectedQueueIds}
searchParam={searchParam}
valueTab={valueTab}
/>
<TicketsList
status="pending"
updateCount={(v) => setPendingCount(v)}
selectedQueueIds={selectedQueueIds}
searchParam={searchParam}
valueTab={valueTab}
/>
<TicketsList
status="closed"
updateCount={(v) => setClosedCount(v)}
selectedQueueIds={selectedQueueIds}
searchParam={searchParam}
valueTab={valueTab}
/>
<NewTicketModal
modalOpen={newTicketModalOpen}
onClose={(e) => setNewTicketModalOpen(false)}
/>
</TicketsManagerStyled>
);
};
export default TicketsManager;

View File

@ -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

View File

@ -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 (
<TicketsTabStyled
id={id}
valueTab={valueTab}
onClick={handleClick}
className={active ? "active" : ""}
>
{text}
{id !== "open" ? (
<BadgeComponent
counter={count}
position="absolute"
right="4px"
top="6px"
fontSize="12px"
bgcolor={id === "pending" ? color.status.warning : color.status.no}
/>
) : (
""
)}
</TicketsTabStyled>
);
};
export default TicketsTab;

View File

@ -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};
}
`;

View File

@ -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 (
<TicketTabsStyled>
<TicketsTab
text="Aberto"
id="open"
setValueTab={setValueTab}
valueTab={valueTab}
count={count.openCount}
/>
<TicketsTab
text="Aguardando"
id="pending"
setValueTab={setValueTab}
valueTab={valueTab}
count={count.pendingCount}
/>
<TicketsTab
text="Fechado"
id="closed"
setValueTab={setValueTab}
valueTab={valueTab}
count={count.closedCount}
/>
</TicketTabsStyled>
);
};
export default TicketsTabs;

View File

@ -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;
`;

View File

@ -9,20 +9,19 @@ import Tab from "@material-ui/core/Tab";
import Badge from "@material-ui/core/Badge"; import Badge from "@material-ui/core/Badge";
import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"; import MoveToInboxIcon from "@material-ui/icons/MoveToInbox";
import CheckBoxIcon from "@material-ui/icons/CheckBox"; import CheckBoxIcon from "@material-ui/icons/CheckBox";
import FormControlLabel from "@material-ui/core/FormControlLabel"; import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from "@material-ui/core/Switch"; import Switch from "@material-ui/core/Switch";
import 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 { 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) => ({ const useStyles = makeStyles((theme) => ({
ticketsWrapper: { ticketsWrapper: {
position: "relative", position: "relative",
@ -112,7 +111,6 @@ const TicketsManager = () => {
if (user.profile.toUpperCase() === "ADMIN") { if (user.profile.toUpperCase() === "ADMIN") {
setShowAllTickets(true); setShowAllTickets(true);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -19,64 +19,11 @@ import toastError from "../../errors/toastError";
const drawerWidth = 320; 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 Ticket = () => {
const { ticketId } = useParams(); const { ticketId } = useParams();
const history = useHistory(); const history = useHistory();
const classes = useStyles();
const [drawerOpen, setDrawerOpen] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@ -91,13 +38,8 @@ const Ticket = () => {
const fetchTicket = async () => { const fetchTicket = async () => {
try { try {
// maria julia
const { data } = await api.get("/tickets/" + ticketId); const { data } = await api.get("/tickets/" + ticketId);
// setContact(data.contact);
// setTicket(data);
setContact(data.contact.contact); setContact(data.contact.contact);
setTicket(data.contact); setTicket(data.contact);
@ -155,23 +97,21 @@ const Ticket = () => {
}; };
return ( return (
<div className={classes.root} id="drawer-container"> <div id="drawer-container">
<Paper <Paper
variant="outlined" variant="outlined"
elevation={0} elevation={0}
className={clsx(classes.mainWrapper, {
[classes.mainWrapperShift]: drawerOpen,
})}
> >
<TicketHeader loading={loading}> <TicketHeader loading={loading}>
<div className={classes.ticketInfo}> <div >
<TicketInfo <TicketInfo
contact={contact} contact={contact}
ticket={ticket} ticket={ticket}
onClick={handleDrawerOpen} onClick={handleDrawerOpen}
/> />
</div> </div>
<div className={classes.ticketActionButtons}> <div >
<TicketActionButtons ticket={ticket} statusChatEnd={statusChatEnd}/> <TicketActionButtons ticket={ticket} statusChatEnd={statusChatEnd}/>
</div> </div>
</TicketHeader> </TicketHeader>

View File

@ -13,9 +13,9 @@ import toastError from "../../errors/toastError";
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext";
import Modal from "../ChatEnd/ModalChatEnd"; import Modal from "../ChatEnd/ModalChatEnd";
import { render } from '@testing-library/react'; import { render } from "@testing-library/react";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles((theme) => ({
actionButtons: { actionButtons: {
marginRight: 6, marginRight: 6,
flex: "none", flex: "none",
@ -35,63 +35,49 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
const ticketOptionsMenuOpen = Boolean(anchorEl); const ticketOptionsMenuOpen = Boolean(anchorEl);
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext);
const handleOpenTicketOptionsMenu = e => { const handleOpenTicketOptionsMenu = (e) => {
setAnchorEl(e.currentTarget); setAnchorEl(e.currentTarget);
}; };
const handleCloseTicketOptionsMenu = e => { const handleCloseTicketOptionsMenu = (e) => {
setAnchorEl(null); setAnchorEl(null);
}; };
const chatEndVal = (data) => { const chatEndVal = (data) => {
if (data) {
data = { ...data, ticketId: ticket.id };
if(data){ console.log("ChatEnd: ", data);
data = {...data, 'ticketId': ticket.id}
console.log('ChatEnd: ',(data));
handleUpdateTicketStatus(null, "closed", user?.id, data)
handleUpdateTicketStatus(null, "closed", user?.id, data);
} }
};
}
const handleModal = (/*status, userId*/) => { const handleModal = (/*status, userId*/) => {
render(
render(<Modal <Modal
modal_header={'Finalização de Atendimento'} modal_header={"Finalização de Atendimento"}
func={chatEndVal} func={chatEndVal}
statusChatEnd={statusChatEnd} statusChatEnd={statusChatEnd}
ticketId={ticket.id} ticketId={ticket.id}
/>) />
);
}; };
const handleUpdateTicketStatus = async (e, status, userId, schedulingData = {}) => {
const handleUpdateTicketStatus = async (e, status, userId, schedulingData={}) => {
setLoading(true); setLoading(true);
try { try {
if (status === "closed") {
if(status==='closed'){
await api.put(`/tickets/${ticket.id}`, { await api.put(`/tickets/${ticket.id}`, {
status: status, status: status,
userId: userId || null, userId: userId || null,
schedulingNotifyData: JSON.stringify(schedulingData) schedulingNotifyData: JSON.stringify(schedulingData),
}); });
} else {
}
else{
await api.put(`/tickets/${ticket.id}`, { await api.put(`/tickets/${ticket.id}`, {
status: status, status: status,
userId: userId || null userId: userId || null,
}); });
} }
setLoading(false); setLoading(false);
@ -104,10 +90,6 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
setLoading(false); setLoading(false);
toastError(err); toastError(err);
} }
}; };
return ( return (
@ -117,7 +99,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
loading={loading} loading={loading}
startIcon={<Replay />} startIcon={<Replay />}
size="small" size="small"
onClick={e => handleUpdateTicketStatus(e, "open", user?.id)} onClick={(e) => handleUpdateTicketStatus(e, "open", user?.id)}
> >
{i18n.t("messagesList.header.buttons.reopen")} {i18n.t("messagesList.header.buttons.reopen")}
</ButtonWithSpinner> </ButtonWithSpinner>
@ -128,7 +110,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
loading={loading} loading={loading}
startIcon={<Replay />} startIcon={<Replay />}
size="small" size="small"
onClick={e => handleUpdateTicketStatus(e, "pending", null)} onClick={(e) => handleUpdateTicketStatus(e, "pending", null)}
> >
{i18n.t("messagesList.header.buttons.return")} {i18n.t("messagesList.header.buttons.return")}
</ButtonWithSpinner> </ButtonWithSpinner>
@ -138,12 +120,9 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
size="small" size="small"
variant="contained" variant="contained"
color="primary" color="primary"
onClick={e => { onClick={(e) => {
handleModal();
handleModal()
// handleUpdateTicketStatus(e, "closed", user?.id) // handleUpdateTicketStatus(e, "closed", user?.id)
}} }}
> >
{i18n.t("messagesList.header.buttons.resolve")} {i18n.t("messagesList.header.buttons.resolve")}
@ -166,7 +145,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
size="small" size="small"
variant="contained" variant="contained"
color="primary" color="primary"
onClick={e => handleUpdateTicketStatus(e, "open", user?.id)} onClick={(e) => handleUpdateTicketStatus(e, "open", user?.id)}
> >
{i18n.t("messagesList.header.buttons.accept")} {i18n.t("messagesList.header.buttons.accept")}
</ButtonWithSpinner> </ButtonWithSpinner>
@ -176,3 +155,4 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
}; };
export default TicketActionButtons; export default TicketActionButtons;

View File

@ -6,12 +6,8 @@ import Select from "@material-ui/core/Select";
import { Checkbox, ListItemText } from "@material-ui/core"; import { Checkbox, ListItemText } from "@material-ui/core";
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n";
const TicketsQueueSelect = ({ const TicketsQueueSelect = ({ userQueues, selectedQueueIds = [], onChange }) => {
userQueues, const handleChange = (e) => {
selectedQueueIds = [],
onChange,
}) => {
const handleChange = e => {
onChange(e.target.value); onChange(e.target.value);
}; };
@ -38,7 +34,7 @@ const TicketsQueueSelect = ({
renderValue={() => i18n.t("ticketsQueueSelect.placeholder")} renderValue={() => i18n.t("ticketsQueueSelect.placeholder")}
> >
{userQueues?.length > 0 && {userQueues?.length > 0 &&
userQueues.map(queue => ( userQueues.map((queue) => (
<MenuItem dense key={queue.id} value={queue.id}> <MenuItem dense key={queue.id} value={queue.id}>
<Checkbox <Checkbox
style={{ style={{
@ -58,3 +54,4 @@ const TicketsQueueSelect = ({
}; };
export default TicketsQueueSelect; export default TicketsQueueSelect;

View File

@ -6,30 +6,62 @@ import api from "../../services/api";
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext";
import { Can } from "../Can"; import { Can } from "../Can";
import BtnComponent from "../Base/BTN/Btn"; import BtnComponent from "../Base/Btn/Btn";
import FormComponent from "../Base/Form/FormComponent"; import FormComponent from "../Base/Form/FormComponent";
import InputComponent from "../Base/Form/Input/InputComponent"; import InputComponent from "../Base/Form/Input/InputComponent";
import UserModalComponent from "./UserModalImg/UserModalComponent"; import UserModalComponent from "./UserModalImg/UserModalComponent";
import UserImg from "../../assets/images/User/user.jpg"; import UserImg from "../../assets/images/User/user.jpg";
const UserModal = ({ modal, click }) => { const UserModal = ({ modal, click, userId }) => {
const { user } = React.useContext(AuthContext); const { user } = React.useContext(AuthContext);
const initalData = { const InitalState = {
name: user.name, name: "",
email: user.email, email: "",
perfil:user.profile, password: "",
} profile: "",
console.log(user); };
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 ( return (
<ModalOverlayStyled modal={modal}> <ModalOverlayStyled modal={modal}>
<UserModalStyled> <UserModalStyled>
<UserModalComponent img={UserImg} desc="Clique na imagem para alterar" /> <UserModalComponent img={UserImg} desc="Clique na imagem para alterar" />
<FormComponent method="get"> <FormComponent method="get">
<InputComponent id="nome" label="Nome" type="text" value={initalData.name}/> <InputComponent
<InputComponent id="email" label="E-mail" type="email" value={initalData.email}/> id="nome"
label="Nome"
type="text"
value={userData.name}
onChange={(event) => setUserData({ name: event.target.data })}
/>
<InputComponent
id="email"
label="E-mail"
type="email"
value={userData.email}
onChange={(event) => setUserData({ email: event.target.data })}
/>
<InputComponent id="password" label="Senha" type="password" /> <InputComponent id="password" label="Senha" type="password" />
<UserBtns> <UserBtns>
<BtnComponent text="Cancelar" onClick={click} /> <BtnComponent text="Cancelar" onClick={click} />
@ -54,3 +86,4 @@ name: "teste"
profile: "master" profile: "master"
queues: [] queues: []
tokenVersion: 0 */ tokenVersion: 0 */

View File

@ -1,21 +1,16 @@
import React from "react"; import React from "react";
import Loading from "../components/LoadingScreen/Loading"; import LoadingScreen from "../components/LoadingScreen/LoadingScreen";
import MainContainer from "../components/Base/MainContainer/MainContainer"; import MainContainer from "../components/Base/MainContainer/MainContainer";
import { AuthContext } from "../context/Auth/AuthContext"; import { AuthContext } from "../context/Auth/AuthContext";
import { i18n } from "../translate/i18n"; import { i18n } from "../translate/i18n";
import MenuComponent from "../components/Menu/MenuComponent"; import MenuComponent from "../components/Menu/MenuComponent";
const LoggedInLayout = ({ children }) => { const LoggedInLayout = ({ children }) => {
const { handleLogout, loading, isAuth } = React.useContext(AuthContext); const { loading, user } = React.useContext(AuthContext);
const logout = (e) => {
handleLogout();
};
const { user } = React.useContext(AuthContext);
console.log(user.name);
if (loading) { if (loading) {
return <Loading />; return <LoadingScreen />;
} }
return ( return (

View File

@ -3,101 +3,89 @@ import MainContainer from "../../components/MainContainer";
import api from "../../services/api"; import api from "../../services/api";
import SelectField from "../../components/Report/SelectField"; import SelectField from "../../components/Report/SelectField";
//import { data } from '../../components/Report/MTable/data'; //import { data } from '../../components/Report/MTable/data';
import DatePicker1 from '../../components/Report/DatePicker' import DatePicker1 from "../../components/Report/DatePicker";
import DatePicker2 from '../../components/Report/DatePicker' import DatePicker2 from "../../components/Report/DatePicker";
import MTable from "../../components/Report/MTable"; import MTable from "../../components/Report/MTable";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import Box from '@mui/material/Box'; import Box from "@mui/material/Box";
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext";
import { Can } from "../../components/Can"; import { Can } from "../../components/Can";
import { Button } from "@material-ui/core"; import { Button } from "@material-ui/core";
import ReportModal from "../../components/ReportModal"; 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 { 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 = [ let columns = [
{ {
key: 'ticket.whatsapp.name', key: "ticket.whatsapp.name",
label: 'Loja', label: "Loja",
}, },
{ {
key: 'id', key: "id",
label: 'id Mensagem', label: "id Mensagem",
}, },
{ {
key: 'ticket.id', key: "ticket.id",
label: 'id Conversa', label: "id Conversa",
}, },
{ {
key: 'ticket.contact.name', key: "ticket.contact.name",
label: 'Cliente', label: "Cliente",
}, },
{ {
key: 'ticket.user.name', key: "ticket.user.name",
label: 'Atendente', label: "Atendente",
}, },
{ {
key: 'body', key: "body",
label: 'Mensagem', label: "Mensagem",
}, },
{ {
key: 'fromMe', key: "fromMe",
label: 'Sentido', label: "Sentido",
}, },
{ {
key: 'createdAt', key: "createdAt",
label: 'Criada', label: "Criada",
}, },
{ {
key: 'ticket.contact.number', key: "ticket.contact.number",
label: 'Telefone cliente', label: "Telefone cliente",
}, },
{ {
key: 'ticket.queue.name', key: "ticket.queue.name",
label: 'Fila', label: "Fila",
}, },
{ {
key: 'ticket.status', key: "ticket.status",
label: 'Status', label: "Status",
}, },
{ {
key: 'ticket.statusChatEnd', key: "ticket.statusChatEnd",
label: 'Status de encerramento', label: "Status de encerramento",
} },
];
]
// //
const reducerQ = (state, action) => { const reducerQ = (state, action) => {
if (action.type === "DELETE_USER_STATUS") { if (action.type === "DELETE_USER_STATUS") {
const userId = action.payload; const userId = action.payload;
//console.log('Entrou no delete user status userId: ', userId) //console.log('Entrou no delete user status userId: ', userId)
@ -113,115 +101,90 @@ const reducerQ = (state, action) => {
return [...state]; return [...state];
} }
if (action.type === "LOAD_QUERY") {
const queries = action.payload;
if (action.type === 'LOAD_QUERY') { const newQueries = [];
const queries = action.payload
const newQueries = []
queries.forEach((query) => { queries.forEach((query) => {
const queryIndex = state.findIndex((q) => q.id === query.id);
const queryIndex = state.findIndex((q) => q.id === query.id)
if (queryIndex !== -1) { if (queryIndex !== -1) {
state[queryIndex] = query state[queryIndex] = query;
} else {
newQueries.push(query);
} }
else { });
newQueries.push(query)
return [...state, ...newQueries];
} }
})
return [...state, ...newQueries]
}
if (action.type === "UPDATE_STATUS_ONLINE") { if (action.type === "UPDATE_STATUS_ONLINE") {
let onlineUser = action.payload;
let onlineUser = action.payload let index = -1;
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) { if (onlineUser.sumOpen || onlineUser.sumClosed) {
index = state.findIndex((e) => ((onlineUser.sumOpen && e.id === onlineUser.sumOpen.userId) || (onlineUser.sumClosed && e.id === onlineUser.sumClosed.userId))) index = state.findIndex(
} (e) =>
else { (onlineUser.sumOpen && e.id === onlineUser.sumOpen.userId) ||
index = state.findIndex((e) => `${e.id}` === `${onlineUser.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) { if (index !== -1) {
// console.log('ENTROU NO INDEX') // console.log('ENTROU NO INDEX')
if (!("statusOnline" in state[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 ("onlineTime" in onlineUser) {
if ("sumOnlineTime" in state[index]) { if ("sumOnlineTime" in state[index]) {
state[index].sumOnlineTime['sum'] = (onlineUser.onlineTime).split(" ")[1] state[index].sumOnlineTime["sum"] = onlineUser.onlineTime.split(" ")[1];
} } else if (!("sumOnlineTime" in state[index])) {
else if (!("sumOnlineTime" in state[index])) { state[index].sumOnlineTime = {
state[index].sumOnlineTime = { userId: onlineUser.userId, sum: (onlineUser.onlineTime).split(" ")[1] } userId: onlineUser.userId,
sum: onlineUser.onlineTime.split(" ")[1],
};
} }
} }
if (onlineUser.sumOpen) { if (onlineUser.sumOpen) {
if ("sumOpen" in state[index]) { if ("sumOpen" in state[index]) {
// console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1 | state[index].sumOpen["count"]: ', state[index].sumOpen['count'], ' | onlineUser.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 state[index].sumOpen["count"] = onlineUser.sumOpen.count;
} else if (!("sumOpen" in state[index])) { } else if (!("sumOpen" in state[index])) {
// console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1') // console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1')
state[index].sumOpen = onlineUser.sumOpen state[index].sumOpen = onlineUser.sumOpen;
} }
} }
if (onlineUser.sumClosed) { if (onlineUser.sumClosed) {
if ("sumClosed" in state[index]) { if ("sumClosed" in state[index]) {
// console.log(' >>>>>>>>>>>>>>>>>> sumClosed 1 | state[index].sumClosed["count"]: ', state[index].sumClosed['count'], ' | onlineUser.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 state[index].sumClosed["count"] = onlineUser.sumClosed.count;
} else if (!("sumClosed" in state[index])) { } else if (!("sumClosed" in state[index])) {
// console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1') // console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1')
state[index].sumClosed = onlineUser.sumClosed state[index].sumClosed = onlineUser.sumClosed;
} }
} }
} }
return [...state] return [...state];
} }
if (action.type === "RESET") { if (action.type === "RESET") {
return []; return [];
} }
};
}
const reducer = (state, action) => { const reducer = (state, action) => {
if (action.type === "LOAD_USERS") { if (action.type === "LOAD_USERS") {
const users = action.payload; const users = action.payload;
const newUsers = []; const newUsers = [];
@ -253,24 +216,20 @@ const reducer = (state, action) => {
} }
}; };
function Item(props) { function Item(props) {
const { sx, ...other } = props; const { sx, ...other } = props;
return ( return (
<Box <Box
sx={{ sx={{
bgcolor: (theme) => (theme.palette.mode === 'dark' ? '#101010' : '#fff'), bgcolor: (theme) => (theme.palette.mode === "dark" ? "#101010" : "#fff"),
color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'), color: (theme) => (theme.palette.mode === "dark" ? "grey.300" : "grey.800"),
border: '1px solid', border: "1px solid",
borderColor: (theme) => borderColor: (theme) => (theme.palette.mode === "dark" ? "grey.800" : "grey.300"),
theme.palette.mode === 'dark' ? 'grey.800' : 'grey.300',
p: 1, p: 1,
m: 1, m: 1,
borderRadius: 2, borderRadius: 2,
fontSize: '0.875rem', fontSize: "0.875rem",
fontWeight: '700', fontWeight: "700",
...sx, ...sx,
}} }}
{...other} {...other}
@ -280,33 +239,28 @@ function Item(props) {
Item.propTypes = { Item.propTypes = {
sx: PropTypes.oneOfType([ sx: PropTypes.oneOfType([
PropTypes.arrayOf( PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool]),
),
PropTypes.func, PropTypes.func,
PropTypes.object, PropTypes.object,
]), ]),
}; };
let columnsData = [ let columnsData = [
{ title: 'Unidade', field: 'whatsapp.name' }, { title: "Unidade", field: "whatsapp.name" },
{ title: 'Atendente', field: 'user.name' }, { title: "Atendente", field: "user.name" },
{ title: 'Contato', field: 'contact.number' }, { title: "Contato", field: "contact.number" },
{ title: 'Nome', field: 'contact.name' }, { title: "Nome", field: "contact.name" },
{ title: 'Assunto', field: 'queue.name' }, { title: "Assunto", field: "queue.name" },
{ title: 'Status', field: 'status' }, { title: "Status", field: "status" },
{ title: 'Criado', field: 'createdAt' }, { title: "Criado", field: "createdAt" },
//{title: 'Atualizado', field: 'updatedAt'}, //{title: 'Atualizado', field: 'updatedAt'},
{title: 'Status de encerramento', field: 'statusChatEnd'}]; { title: "Status de encerramento", field: "statusChatEnd" },
];
const Report = () => { const Report = () => {
const csvLink = useRef();
const csvLink = useRef()
const { user: userA } = useContext(AuthContext); const { user: userA } = useContext(AuthContext);
@ -317,39 +271,32 @@ const Report = () => {
const [pageNumber, setPageNumber] = useState(1); const [pageNumber, setPageNumber] = useState(1);
const [users, dispatch] = useReducer(reducer, []); const [users, dispatch] = useReducer(reducer, []);
//const [columns, setColums] = useState([]) //const [columns, setColums] = useState([])
const [startDate, setDatePicker1] = useState(new Date()) const [startDate, setDatePicker1] = useState(new Date());
const [endDate, setDatePicker2] = useState(new Date()) const [endDate, setDatePicker2] = useState(new Date());
const [userId, setUser] = useState(null) const [userId, setUser] = useState(null);
const [query, dispatchQ] = useReducer(reducerQ, []) const [query, dispatchQ] = useReducer(reducerQ, []);
const [dataCSV, setDataCSV] = useState([]) const [dataCSV, setDataCSV] = useState([]);
const [isMount, setIsMount] = useState(true); const [isMount, setIsMount] = useState(true);
const [reportOption, setReport] = useState('1') const [reportOption, setReport] = useState("1");
const [reporList,] = useState(report) const [reporList] = useState(report);
const [profile, setProfile] = useState('') const [profile, setProfile] = useState("");
const [dataRows, setData] = useState([]); const [dataRows, setData] = useState([]);
useEffect(() => { useEffect(() => {
dispatch({ type: "RESET" }); dispatch({ type: "RESET" });
dispatchQ({ type: "RESET" }) dispatchQ({ type: "RESET" });
setPageNumber(1); setPageNumber(1);
}, [searchParam, profile]); }, [searchParam, profile]);
useEffect(() => { useEffect(() => {
//setLoading(true); //setLoading(true);
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
const fetchUsers = async () => { const fetchUsers = async () => {
try { try {
//console.log('profile: ', profile) //console.log('profile: ', profile)
const { data } = await api.get("/users/", { const { data } = await api.get("/users/", {
@ -359,34 +306,27 @@ const Report = () => {
dispatch({ type: "LOAD_USERS", payload: data.users }); dispatch({ type: "LOAD_USERS", payload: data.users });
//setHasMore(data.hasMore); //setHasMore(data.hasMore);
//setLoading(false); //setLoading(false);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
}; };
fetchUsers(); fetchUsers();
}, 500); }, 500);
return () => clearTimeout(delayDebounceFn); return () => clearTimeout(delayDebounceFn);
}, [searchParam, pageNumber, reportOption, profile]); }, [searchParam, pageNumber, reportOption, profile]);
useEffect(() => { useEffect(() => {
//setLoading(true); //setLoading(true);
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
const fetchQueries = async () => { const fetchQueries = async () => {
try { try {
if (reportOption === "1") {
if (reportOption === '1') { const dataQuery = await api.get("/reports/", {
params: { userId, startDate, endDate },
const dataQuery = await api.get("/reports/", { params: { userId, startDate, endDate }, }); });
dispatchQ({ type: "RESET" }) dispatchQ({ type: "RESET" });
dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }); dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data });
//setLoading(false); //setLoading(false);
@ -394,12 +334,11 @@ const Report = () => {
// console.log('dataQuery: ', dataQuery.data) // console.log('dataQuery: ', dataQuery.data)
// console.log() // console.log()
} else if (reportOption === "2") {
} const dataQuery = await api.get("/reports/user/services", {
else if (reportOption === '2') { params: { userId, startDate, endDate },
});
const dataQuery = await api.get("/reports/user/services", { params: { userId, startDate, endDate }, }); dispatchQ({ type: "RESET" });
dispatchQ({ type: "RESET" })
dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }); dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data });
//setLoading(false); //setLoading(false);
@ -407,62 +346,46 @@ const Report = () => {
// console.log('REPORT 2 dataQuery : ', dataQuery.data) // console.log('REPORT 2 dataQuery : ', dataQuery.data)
//console.log() //console.log()
} }
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
}; };
fetchQueries(); fetchQueries();
}, 500); }, 500);
return () => clearTimeout(delayDebounceFn); return () => clearTimeout(delayDebounceFn);
}, [userId, startDate, endDate, reportOption]); }, [userId, startDate, endDate, reportOption]);
// Get from child 1 // Get from child 1
const datePicker1Value = (data) => { const datePicker1Value = (data) => {
setDatePicker1(data);
setDatePicker1(data) };
}
// Get from child 2 // Get from child 2
const datePicker2Value = (data) => { const datePicker2Value = (data) => {
setDatePicker2(data);
setDatePicker2(data) };
}
// Get from child 3 // Get from child 3
const textFieldSelectUser = (data) => { const textFieldSelectUser = (data) => {
setUser(data);
setUser(data) };
}
// Get from report option // Get from report option
const reportValue = (data) => { const reportValue = (data) => {
setReport(data);
setReport(data)
// console.log(' data: ', data) // console.log(' data: ', data)
} };
useEffect(() => { useEffect(() => {
if (reportOption === "1") {
if (reportOption === '1') { setProfile("");
setProfile('') } else if (reportOption === "2") {
setProfile("user");
} }
else if (reportOption === '2') { }, [reportOption]);
setProfile('user')
}
}, [reportOption])
// useEffect(() => { // useEffect(() => {
@ -470,106 +393,85 @@ const Report = () => {
// }, [query]) // }, [query])
// test del // test del
const handleCSVMessages = () => { const handleCSVMessages = () => {
// setLoading(true); // setLoading(true);
const fetchQueries = async () => { const fetchQueries = async () => {
try { try {
const dataQuery = await api.get("/reports/messages", {
const dataQuery = await api.get("/reports/messages", { params: { userId, startDate, endDate }, }); params: { userId, startDate, endDate },
});
// console.log('dataQuery messages: ', dataQuery.data) // console.log('dataQuery messages: ', dataQuery.data)
if (dataQuery.data.length > 0) { if (dataQuery.data.length > 0) {
let dataCSVFormat = dataQuery.data; let dataCSVFormat = dataQuery.data;
for (var i = 0; i < dataCSVFormat.length; i++) { for (var i = 0; i < dataCSVFormat.length; i++) {
if (dataCSVFormat[i].fromMe) { if (dataCSVFormat[i].fromMe) {
dataCSVFormat[i].fromMe = 'Atendente' dataCSVFormat[i].fromMe = "Atendente";
} } else {
else { dataCSVFormat[i].fromMe = "Cliente";
dataCSVFormat[i].fromMe = 'Cliente'
} }
} }
// console.log('dataCSVFormat: ', dataCSVFormat) // console.log('dataCSVFormat: ', dataCSVFormat)
setDataCSV(dataCSVFormat) setDataCSV(dataCSVFormat);
setIsMount(false); setIsMount(false);
// setDataCSV(dataQuery.data) // setDataCSV(dataQuery.data)
} }
// setLoading(false); // setLoading(false);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
}; };
fetchQueries(); fetchQueries();
};
}
useEffect(() => { useEffect(() => {
if (isMount) { if (isMount) {
return; return;
} }
csvLink.current.link.click() csvLink.current.link.click();
}, [dataCSV, isMount, csvLink]); }, [dataCSV, isMount, csvLink]);
useEffect(() => { useEffect(() => {
if (reportOption === "2") {
if (reportOption === '2') {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
socket.on("onlineStatus", (data) => { socket.on("onlineStatus", (data) => {
// setLoading(true); // setLoading(true);
let date = new Date().toLocaleDateString("pt-BR").split("/");
let date = new Date().toLocaleDateString('pt-BR').split('/') let dateToday = `${date[2]}-${date[1]}-${date[0]}`;
let dateToday = `${date[2]}-${date[1]}-${date[0]}`
// console.log('date: ', new Date(startDate).toLocaleDateString('pt-BR')) // console.log('date: ', new Date(startDate).toLocaleDateString('pt-BR'))
// console.log('date2: ', startDate) // console.log('date2: ', startDate)
if (
if (data.action === "logout" || (data.action === "update" && data.action === "logout" ||
((`${startDate}` === `${endDate}`) && (`${endDate}` === `${dateToday}`) && (`${startDate}` === `${dateToday}`)))) { (data.action === "update" &&
`${startDate}` === `${endDate}` &&
`${endDate}` === `${dateToday}` &&
`${startDate}` === `${dateToday}`)
) {
//console.log('UPDATE FROM ONLINE/OFFLINE LOGED USERS: ', data.userOnlineTime, ' | data.action : ', data.action) //console.log('UPDATE FROM ONLINE/OFFLINE LOGED USERS: ', data.userOnlineTime, ' | data.action : ', data.action)
dispatchQ({ type: "UPDATE_STATUS_ONLINE", payload: data.userOnlineTime }); 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 }); dispatchQ({ type: "DELETE_USER_STATUS", payload: data.userOnlineTime });
} }
// setLoading(false); // setLoading(false);
}); });
socket.on("user", (data) => { socket.on("user", (data) => {
if (data.action === "delete") { 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 }); dispatch({ type: "DELETE_USER", payload: +data.userId });
@ -579,13 +481,9 @@ const Report = () => {
return () => { return () => {
socket.disconnect(); socket.disconnect();
}; };
} }
}, [reportOption, startDate, endDate]); }, [reportOption, startDate, endDate]);
// const handleDeleteRows = (id) => { // const handleDeleteRows = (id) => {
// let _data = [...dataRows]; // let _data = [...dataRows];
@ -597,20 +495,20 @@ const Report = () => {
// }; // };
useEffect(() => { useEffect(() => {
//if (!loading) { //if (!loading) {
// setData(query.map(({ scheduleReminder, ...others }) => ( // setData(query.map(({ scheduleReminder, ...others }) => (
// { ...others, 'scheduleReminder': `${others.statusChatEndId}` === '3' ? 'Agendamento' : 'Lembrete' } // { ...others, 'scheduleReminder': `${others.statusChatEndId}` === '3' ? 'Agendamento' : 'Lembrete' }
// ))) // )))
// } // }
setData(query.map((column) => { return { ...column } })) setData(
query.map((column) => {
}, [query]) return { ...column };
})
);
}, [query]);
const handleLogouOnlineUser = async (userId) => { const handleLogouOnlineUser = async (userId) => {
try { try {
@ -620,19 +518,15 @@ const Report = () => {
} catch (err) { } catch (err) {
// toastError(err); // toastError(err);
} }
}; };
return ( return (
<Can <Can
role={userA.profile} role={userA.profile}
perform="ticket-report:show" perform="ticket-report:show"
yes={() => ( yes={() => (
<MainContainer> <MainContainer>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
<Item><SelectField func={textFieldSelectUser} emptyField={true} header={'Usuário'} currencies={users.map((obj) => { <Item><SelectField func={textFieldSelectUser} emptyField={true} header={'Usuário'} currencies={users.map((obj) => {
@ -642,13 +536,27 @@ const Report = () => {
<Item><DatePicker1 func={datePicker1Value} minDate={true} startEmpty={false} title={'Data inicio'} /></Item> <Item><DatePicker1 func={datePicker1Value} minDate={true} startEmpty={false} title={'Data inicio'} /></Item>
<Item><DatePicker2 func={datePicker2Value} minDate={false} startEmpty={false} title={'Data fim'} /></Item> <Item><DatePicker2 func={datePicker2Value} minDate={false} startEmpty={false} title={'Data fim'} /></Item>
<Item sx={{ display: 'grid', gridColumn: '4 / 5', }}> <Item>
<DatePicker1
func={datePicker1Value}
minDate={false}
startEmpty={false}
title={"Data inicio"}
/>
</Item>
<Item>
<DatePicker2
func={datePicker2Value}
minDate={false}
startEmpty={false}
title={"Data fim"}
/>
</Item>
<Item sx={{ display: "grid", gridColumn: "4 / 5" }}>
<ReportModal currencies={reporList} func={reportValue} reportOption={reportOption} /> <ReportModal currencies={reporList} func={reportValue} reportOption={reportOption} />
<div style={{ margin: '2px' }}></div> <div style={{ margin: "2px" }}></div>
{reportOption === '1' &&
<div> <div>
{/* <Button {/* <Button
@ -663,125 +571,95 @@ const Report = () => {
<CSVLink <CSVLink
data={dataCSV} data={dataCSV}
headers={columns} headers={columns}
filename={'Relatorio_detalhado_atendimento_atendentes.csv'} filename={"Relatorio_detalhado_atendimento_atendentes.csv"}
target={'_blank'} target={"_blank"}
ref={csvLink} /> ref={csvLink}
/>
</div> </div>
</div> </div>
)}
}
</Item> </Item>
</Box> </Box>
<Box sx={{ <Box
display: 'grid', sx={{
}}> display: "grid",
}}
<Item sx={{ gridColumn: '1', gridRow: 'span 1' }}> >
<Item sx={{ gridColumn: "1", gridRow: "span 1" }}>
{reportOption === '1' && {reportOption === "1" && (
<MTable
<MTable data={query} data={query}
columns={columnsData} columns={columnsData}
hasChild={true} hasChild={true}
removeClickRow={false} removeClickRow={false}
table_title={'Atendimento por atendentes'} /> table_title={"Atendimento por atendentes"}
} />
{reportOption === '2' && )}
{reportOption === "2" && (
<MaterialTable <MaterialTable
localization={{ localization={{
header: { header: {
actions: 'Deslogar' actions: "Deslogar",
}, },
}} }}
title="Usuários online/offline" title="Usuários online/offline"
columns={ columns={[
[
// { title: 'Foto', field: 'ticket.contact.profilePicUrl', render: rowData => <img src={rowData['ticket.contact.profilePicUrl']} alt="imagem de perfil do whatsapp" style={{ width: 40, borderRadius: '50%' }} /> }, // { title: 'Foto', field: 'ticket.contact.profilePicUrl', render: rowData => <img src={rowData['ticket.contact.profilePicUrl']} alt="imagem de perfil do whatsapp" style={{ width: 40, borderRadius: '50%' }} /> },
{ title: 'Nome', field: 'name', cellStyle: {whiteSpace: 'nowrap'}, }, { title: "Nome", field: "name", cellStyle: { whiteSpace: "nowrap" } },
{ {
title: 'Status', field: 'statusOnline.status', title: "Status",
field: "statusOnline.status",
cellStyle: (e, rowData) => { cellStyle: (e, rowData) => {
if (rowData["statusOnline"] && rowData["statusOnline"].status) {
if (rowData['statusOnline'] && rowData['statusOnline'].status) { if (rowData["statusOnline"].status === "offline") {
if (rowData['statusOnline'].status === 'offline') {
return { color: "red" }; return { color: "red" };
} } else if (rowData["statusOnline"].status === "online") {
else if (rowData['statusOnline'].status === 'online') {
return { color: "green" }; return { color: "green" };
} } else if (rowData["statusOnline"].status === "logout...") {
else if (rowData['statusOnline'].status === 'logout...') {
return { color: "orange" }; return { color: "orange" };
} } else if (rowData["statusOnline"].status === "waiting...") {
else if (rowData['statusOnline'].status === 'waiting...') {
return { color: "orange" }; return { color: "orange" };
} }
} }
},
}, },
}, { title: "Tempo online", field: "sumOnlineTime.sum" },
{ title: "Data inicio", field: "startDate" },
{ title: 'Tempo online', field: 'sumOnlineTime.sum' }, { title: "Data fim", field: "endDate" },
{ title: 'Data inicio', field: 'startDate' }, { title: "Em atendimento", field: "sumOpen.count" },
{ title: 'Data fim', field: 'endDate' }, { title: "Finalizado", field: "sumClosed.count" },
{ title: 'Em atendimento', field: 'sumOpen.count' }, ]}
{ title: 'Finalizado', field: 'sumClosed.count' },
]
}
data={dataRows} data={dataRows}
// icons={tableIcons} // icons={tableIcons}
actions={[ actions={[
(rowData) => { (rowData) => {
if (
if (rowData.statusOnline && rowData.statusOnline &&
rowData.statusOnline['status'] && rowData.statusOnline["status"] &&
rowData.statusOnline['status'] === 'online') { rowData.statusOnline["status"] === "online"
) {
return { return {
icon: LogoutIcon, icon: LogoutIcon,
tooltip: 'deslogar', tooltip: "deslogar",
disable: false, disable: false,
onClick: (event, rowData) => { onClick: (event, rowData) => {
// console.log(' ROW DATA INFO: ', rowData, ' | rowData: ', rowData.id) // console.log(' ROW DATA INFO: ', rowData, ' | rowData: ', rowData.id)
handleLogouOnlineUser(rowData.id) handleLogouOnlineUser(rowData.id);
} },
} };
}
} }
},
]} ]}
options={{
options={
{
search: true, search: true,
selection: false, selection: false,
paging: false, paging: false,
padding: 'dense', padding: "dense",
sorting: true, sorting: true,
//loadingType: 'linear', //loadingType: 'linear',
searchFieldStyle: { searchFieldStyle: {
@ -791,14 +669,13 @@ const Report = () => {
pageSize: 20, pageSize: 20,
headerStyle: { headerStyle: {
position: "sticky", position: "sticky",
top: "0" top: "0",
}, },
maxBodyHeight: "400px", maxBodyHeight: "400px",
rowStyle: { rowStyle: {
fontSize: 14, fontSize: 14,
} },
// cellStyle: (rowData) => { // cellStyle: (rowData) => {
// return { // return {
@ -807,26 +684,16 @@ const Report = () => {
// }; // };
// } // }
}} }}
/> />
)}
}
</Item> </Item>
</Box> </Box>
</MainContainer> </MainContainer>
)} )}
/> />
) );
}; };
export default Report; export default Report;

View File

@ -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 (
<TicketsStyled>
<TicketsManager />
{ticketId ? <Ticket /> : <div>Não tem nada</div>}
</TicketsStyled>
);
};
export default Tickets;

View File

@ -0,0 +1,10 @@
import styled from "styled-components";
const TicketsStyled = styled.div`
display: flex;
flex-direction: row;
min-height: auto;
`;
export default TicketsStyled

View File

@ -4,7 +4,7 @@ import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
import { makeStyles } from "@material-ui/core/styles"; 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 Ticket from "../../components/Ticket/";
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n";

View File

@ -2,7 +2,7 @@ import React, { useContext } from "react";
import { Route as RouterRoute, Redirect } from "react-router-dom"; import { Route as RouterRoute, Redirect } from "react-router-dom";
import { AuthContext } from "../context/Auth/AuthContext"; 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 Route = ({ component: Component, isPrivate = false, ...rest }) => {
const { isAuth, loading } = useContext(AuthContext); const { isAuth, loading } = useContext(AuthContext);
@ -10,7 +10,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
if (!isAuth && isPrivate) { if (!isAuth && isPrivate) {
return ( return (
<> <>
{loading && <Loading />} {loading && <LoadingScreen />}
<Redirect to={{ pathname: "/login", state: { from: rest.location } }} /> <Redirect to={{ pathname: "/login", state: { from: rest.location } }} />
</> </>
); );
@ -19,7 +19,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
if (isAuth && !isPrivate) { if (isAuth && !isPrivate) {
return ( return (
<> <>
{loading && <Loading />} {loading && <LoadingScreen />}
<Redirect to={{ pathname: "/", state: { from: rest.location } }} />; <Redirect to={{ pathname: "/", state: { from: rest.location } }} />;
</> </>
); );
@ -27,7 +27,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
return ( return (
<> <>
{loading && <Loading />} {loading && <LoadingScreen />}
<RouterRoute {...rest} component={Component} /> <RouterRoute {...rest} component={Component} />
</> </>
); );

View File

@ -10,7 +10,7 @@ import SchedulesReminder from "../pages/SchedulesReminder/";
import Login from "../pages/Login/"; import Login from "../pages/Login/";
import Signup from "../pages/Signup/"; import Signup from "../pages/Signup/";
import Tickets from "../pages/Tickets/"; import Tickets from "../pages/Tickets/Tickets";
import Connections from "../pages/Connections/"; import Connections from "../pages/Connections/";
import Settings from "../pages/Settings/"; import Settings from "../pages/Settings/";
import Users from "../pages/Users"; import Users from "../pages/Users";

View File

@ -39,22 +39,24 @@ export const color = {
grisOscuro: "#3C3C3B", grisOscuro: "#3C3C3B",
blanco: "#FFFFFF", blanco: "#FFFFFF",
}, },
complement:{ complement: {
azulCielo: "#55A5DC", azulCielo: "#55A5DC",
azulOscuro: "#212F3C", azulOscuro: "#212F3C",
crisClaro: "#F6F6F6", crisClaro: "#F6F6F6",
}, },
status:{ status: {
no: "#FF0000", no: "#FF0000",
yes: "#00BE1E", yes: "#00BE1E",
warning: "#FFC700" warning: "#ffc800c7",
}, },
gradient:{ gradient: {
bgOpacity:"#212F3CD8", bgOpacity: "#212f3cd7",
placeholder:"#ffffff83" placeholder: "#ffffff83",
border: "#55A5DC3F",
text: "#AEBAC1",
},
shadow: {
dark: "2px 2px 4px 2px",
}, },
shadow:{
dark:"2px 2px 4px 2px"
}
}; };