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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import React from "react";
import BtnComponent from "../Base/BTN/Btn";
import BtnComponent from "../Base/Btn/Btn";
import {
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`
width: 100%;
background: transparent;
background: ${color.complement.azulOscuro};
border: none;
color: ${color.complement.azulCielo};
`;

View File

@ -63,7 +63,7 @@ const MenuComponent = () => {
<MenuItem icon={<Tickets />} text="Tickets" to="/tickets" hover={hover} />
<MenuItem icon={<Contact />} text="Contatos" to="/contacts" 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 />
<MenuItem icon={<Users />} text="Usuários" to="/users" hover={hover} />
<MenuItem icon={<Rows />} text="Filas" to="/Queues" hover={hover} />

View File

@ -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 }) => {
})}
></span>
<div className={classes.quotedMsg}>
{!message.quotedMsg?.fromMe && (
<span className={classes.messageContactName}>
{message.quotedMsg?.contact?.name}
</span>
)}
{message.quotedMsg?.body}
</div>
</div>

View File

@ -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 (
<PageTitleStyled>
<h1>PageTitle</h1>
<h1>{title}</h1>
<UserBtn user={user} img={logo} modal={modal} modalSet={handleModal} />
</PageTitleStyled>
);

View File

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

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

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 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(() => {

View File

@ -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 (
<div className={classes.root} id="drawer-container">
<div id="drawer-container">
<Paper
variant="outlined"
elevation={0}
className={clsx(classes.mainWrapper, {
[classes.mainWrapperShift]: drawerOpen,
})}
>
<TicketHeader loading={loading}>
<div className={classes.ticketInfo}>
<div >
<TicketInfo
contact={contact}
ticket={ticket}
onClick={handleDrawerOpen}
/>
</div>
<div className={classes.ticketActionButtons}>
<div >
<TicketActionButtons ticket={ticket} statusChatEnd={statusChatEnd}/>
</div>
</TicketHeader>

View File

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

View File

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

View File

@ -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 (
<ModalOverlayStyled modal={modal}>
<UserModalStyled>
<UserModalComponent img={UserImg} desc="Clique na imagem para alterar" />
<FormComponent method="get">
<InputComponent id="nome" label="Nome" type="text" value={initalData.name}/>
<InputComponent id="email" label="E-mail" type="email" value={initalData.email}/>
<InputComponent
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" />
<UserBtns>
<BtnComponent text="Cancelar" onClick={click} />
@ -54,3 +86,4 @@ name: "teste"
profile: "master"
queues: []
tokenVersion: 0 */

View File

@ -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 <Loading />;
return <LoadingScreen />;
}
return (

View File

@ -3,101 +3,89 @@ 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 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";
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)
@ -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
state[queryIndex] = query;
} else {
newQueries.push(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)
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)
if (index !== -1) {
// 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
state[index].sumOpen["count"] = onlineUser.sumOpen.count;
} else if (!("sumOpen" in state[index])) {
// console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1')
state[index].sumOpen = onlineUser.sumOpen
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
state[index].sumClosed["count"] = onlineUser.sumClosed.count;
} else if (!("sumClosed" in state[index])) {
// console.log(' >>>>>>>>>>>>>>>>>> sumOpen 1')
state[index].sumClosed = onlineUser.sumClosed
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 (
<Box
sx={{
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',
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,33 +239,28 @@ 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: "Status", field: "status" },
{ title: 'Criado', field: 'createdAt' },
{ 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);
@ -317,39 +271,32 @@ const Report = () => {
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 [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);
const delayDebounceFn = setTimeout(() => {
const fetchUsers = async () => {
try {
//console.log('profile: ', profile)
const { data } = await api.get("/users/", {
@ -359,34 +306,27 @@ const Report = () => {
dispatch({ type: "LOAD_USERS", payload: data.users });
//setHasMore(data.hasMore);
//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);
@ -394,12 +334,11 @@ const Report = () => {
// 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" })
} 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);
@ -407,62 +346,46 @@ const Report = () => {
// 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)
}
};
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,106 +393,85 @@ const Report = () => {
// }, [query])
// test del
const handleCSVMessages = () => {
// 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)
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);
} 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);
let date = new Date().toLocaleDateString('pt-BR').split('/')
let dateToday = `${date[2]}-${date[1]}-${date[0]}`
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)
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];
@ -597,20 +495,20 @@ const Report = () => {
// };
useEffect(() => {
//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 {
@ -620,19 +518,15 @@ const Report = () => {
} catch (err) {
// toastError(err);
}
};
return (
<Can
role={userA.profile}
perform="ticket-report:show"
yes={() => (
<MainContainer>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
<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><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} />
<div style={{ margin: '2px' }}></div>
{reportOption === '1' &&
<div style={{ margin: "2px" }}></div>
<div>
{/* <Button
@ -663,125 +571,95 @@ const Report = () => {
<CSVLink
data={dataCSV}
headers={columns}
filename={'Relatorio_detalhado_atendimento_atendentes.csv'}
target={'_blank'}
ref={csvLink} />
filename={"Relatorio_detalhado_atendimento_atendentes.csv"}
target={"_blank"}
ref={csvLink}
/>
</div>
</div>
}
)}
</Item>
</Box>
<Box sx={{
display: 'grid',
}}>
<Item sx={{ gridColumn: '1', gridRow: 'span 1' }}>
{reportOption === '1' &&
<MTable data={query}
<Box
sx={{
display: "grid",
}}
>
<Item sx={{ gridColumn: "1", gridRow: "span 1" }}>
{reportOption === "1" && (
<MTable
data={query}
columns={columnsData}
hasChild={true}
removeClickRow={false}
table_title={'Atendimento por atendentes'} />
}
{reportOption === '2' &&
table_title={"Atendimento por atendentes"}
/>
)}
{reportOption === "2" && (
<MaterialTable
localization={{
header: {
actions: 'Deslogar'
actions: "Deslogar",
},
}}
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: '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) => {
if (rowData['statusOnline'] && rowData['statusOnline'].status) {
if (rowData['statusOnline'].status === 'offline') {
if (rowData["statusOnline"] && rowData["statusOnline"].status) {
if (rowData["statusOnline"].status === "offline") {
return { color: "red" };
}
else if (rowData['statusOnline'].status === 'online') {
} else if (rowData["statusOnline"].status === "online") {
return { color: "green" };
}
else if (rowData['statusOnline'].status === 'logout...') {
} else if (rowData["statusOnline"].status === "logout...") {
return { color: "orange" };
}
else if (rowData['statusOnline'].status === 'waiting...') {
} 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)
}
}
}
handleLogouOnlineUser(rowData.id);
},
};
}
},
]}
options={
{
options={{
search: true,
selection: false,
paging: false,
padding: 'dense',
padding: "dense",
sorting: true,
//loadingType: 'linear',
searchFieldStyle: {
@ -791,14 +669,13 @@ const Report = () => {
pageSize: 20,
headerStyle: {
position: "sticky",
top: "0"
top: "0",
},
maxBodyHeight: "400px",
rowStyle: {
fontSize: 14,
}
},
// cellStyle: (rowData) => {
// return {
@ -807,26 +684,16 @@ const Report = () => {
// };
// }
}}
/>
}
)}
</Item>
</Box>
</MainContainer>
)}
/>
)
);
};
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 { 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";

View File

@ -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 />}
{loading && <LoadingScreen />}
<Redirect to={{ pathname: "/login", state: { from: rest.location } }} />
</>
);
@ -19,7 +19,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
if (isAuth && !isPrivate) {
return (
<>
{loading && <Loading />}
{loading && <LoadingScreen />}
<Redirect to={{ pathname: "/", state: { from: rest.location } }} />;
</>
);
@ -27,7 +27,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
return (
<>
{loading && <Loading />}
{loading && <LoadingScreen />}
<RouterRoute {...rest} component={Component} />
</>
);

View File

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

View File

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