Left painel in construction , WIP

pull/14/head^2
RenatoDiGiacomo 2022-07-25 09:59:34 -03:00
parent a66355f43b
commit b5975993be
10 changed files with 707 additions and 767 deletions

View File

@ -541,11 +541,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

@ -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);
@ -93,9 +40,6 @@ const Ticket = () => {
const { data } = await api.get("/tickets/" + ticketId);
// setContact(data.contact);
// setTicket(data);
setContact(data.contact.contact);
setTicket(data.contact);
@ -153,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

@ -12,167 +12,147 @@ import ButtonWithSpinner from "../ButtonWithSpinner";
import toastError from "../../errors/toastError";
import { AuthContext } from "../../context/Auth/AuthContext";
import Modal from "../ChatEnd/ModalChatEnd";
import { render } from '@testing-library/react';
import Modal from "../ChatEnd/ModalChatEnd";
import { render } from "@testing-library/react";
const useStyles = makeStyles(theme => ({
actionButtons: {
marginRight: 6,
flex: "none",
alignSelf: "center",
marginLeft: "auto",
"& > *": {
margin: theme.spacing(1),
},
},
const useStyles = makeStyles((theme) => ({
actionButtons: {
marginRight: 6,
flex: "none",
alignSelf: "center",
marginLeft: "auto",
"& > *": {
margin: theme.spacing(1),
},
},
}));
const TicketActionButtons = ({ ticket, statusChatEnd }) => {
const classes = useStyles();
const history = useHistory();
const [anchorEl, setAnchorEl] = useState(null);
const [loading, setLoading] = useState(false);
const ticketOptionsMenuOpen = Boolean(anchorEl);
const { user } = useContext(AuthContext);
const handleOpenTicketOptionsMenu = e => {
setAnchorEl(e.currentTarget);
};
const classes = useStyles();
const history = useHistory();
const [anchorEl, setAnchorEl] = useState(null);
const [loading, setLoading] = useState(false);
const ticketOptionsMenuOpen = Boolean(anchorEl);
const { user } = useContext(AuthContext);
const handleCloseTicketOptionsMenu = e => {
setAnchorEl(null);
};
const handleOpenTicketOptionsMenu = (e) => {
setAnchorEl(e.currentTarget);
};
const chatEndVal = (data) => {
if(data){
const handleCloseTicketOptionsMenu = (e) => {
setAnchorEl(null);
};
data = {...data, 'ticketId': ticket.id}
console.log('ChatEnd: ',(data));
const chatEndVal = (data) => {
if (data) {
data = { ...data, ticketId: ticket.id };
handleUpdateTicketStatus(null, "closed", user?.id, data)
console.log("ChatEnd: ", data);
}
}
handleUpdateTicketStatus(null, "closed", user?.id, data);
}
};
const handleModal = (/*status, userId*/) => {
render(
<Modal
modal_header={"Finalização de Atendimento"}
func={chatEndVal}
statusChatEnd={statusChatEnd}
ticketId={ticket.id}
/>
);
};
const handleModal = (/*status, userId*/) => {
const handleUpdateTicketStatus = async (e, status, userId, schedulingData = {}) => {
setLoading(true);
try {
if (status === "closed") {
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null,
schedulingNotifyData: JSON.stringify(schedulingData),
});
} else {
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null,
});
}
render(<Modal
modal_header={'Finalização de Atendimento'}
func={chatEndVal}
statusChatEnd={statusChatEnd}
ticketId={ticket.id}
/>)
};
setLoading(false);
if (status === "open") {
history.push(`/tickets/${ticket.id}`);
} else {
history.push("/tickets");
}
} catch (err) {
setLoading(false);
toastError(err);
}
};
const handleUpdateTicketStatus = async (e, status, userId, schedulingData={}) => {
setLoading(true);
try {
if(status==='closed'){
return (
<div className={classes.actionButtons}>
{ticket.status === "closed" && (
<ButtonWithSpinner
loading={loading}
startIcon={<Replay />}
size="small"
onClick={(e) => handleUpdateTicketStatus(e, "open", user?.id)}
>
{i18n.t("messagesList.header.buttons.reopen")}
</ButtonWithSpinner>
)}
{ticket.status === "open" && (
<>
<ButtonWithSpinner
loading={loading}
startIcon={<Replay />}
size="small"
onClick={(e) => handleUpdateTicketStatus(e, "pending", null)}
>
{i18n.t("messagesList.header.buttons.return")}
</ButtonWithSpinner>
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null,
schedulingNotifyData: JSON.stringify(schedulingData)
});
<ButtonWithSpinner
loading={loading}
size="small"
variant="contained"
color="primary"
onClick={(e) => {
handleModal();
// handleUpdateTicketStatus(e, "closed", user?.id)
}}
>
{i18n.t("messagesList.header.buttons.resolve")}
</ButtonWithSpinner>
}
else{
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null
});
}
setLoading(false);
if (status === "open") {
history.push(`/tickets/${ticket.id}`);
} else {
history.push("/tickets");
}
} catch (err) {
setLoading(false);
toastError(err);
}
};
return (
<div className={classes.actionButtons}>
{ticket.status === "closed" && (
<ButtonWithSpinner
loading={loading}
startIcon={<Replay />}
size="small"
onClick={e => handleUpdateTicketStatus(e, "open", user?.id)}
>
{i18n.t("messagesList.header.buttons.reopen")}
</ButtonWithSpinner>
)}
{ticket.status === "open" && (
<>
<ButtonWithSpinner
loading={loading}
startIcon={<Replay />}
size="small"
onClick={e => handleUpdateTicketStatus(e, "pending", null)}
>
{i18n.t("messagesList.header.buttons.return")}
</ButtonWithSpinner>
<ButtonWithSpinner
loading={loading}
size="small"
variant="contained"
color="primary"
onClick={e => {
handleModal()
// handleUpdateTicketStatus(e, "closed", user?.id)
}}
>
{i18n.t("messagesList.header.buttons.resolve")}
</ButtonWithSpinner>
<IconButton onClick={handleOpenTicketOptionsMenu}>
<MoreVert />
</IconButton>
<TicketOptionsMenu
ticket={ticket}
anchorEl={anchorEl}
menuOpen={ticketOptionsMenuOpen}
handleClose={handleCloseTicketOptionsMenu}
/>
</>
)}
{ticket.status === "pending" && (
<ButtonWithSpinner
loading={loading}
size="small"
variant="contained"
color="primary"
onClick={e => handleUpdateTicketStatus(e, "open", user?.id)}
>
{i18n.t("messagesList.header.buttons.accept")}
</ButtonWithSpinner>
)}
</div>
);
<IconButton onClick={handleOpenTicketOptionsMenu}>
<MoreVert />
</IconButton>
<TicketOptionsMenu
ticket={ticket}
anchorEl={anchorEl}
menuOpen={ticketOptionsMenuOpen}
handleClose={handleCloseTicketOptionsMenu}
/>
</>
)}
{ticket.status === "pending" && (
<ButtonWithSpinner
loading={loading}
size="small"
variant="contained"
color="primary"
onClick={(e) => handleUpdateTicketStatus(e, "open", user?.id)}
>
{i18n.t("messagesList.header.buttons.accept")}
</ButtonWithSpinner>
)}
</div>
);
};
export default TicketActionButtons;

View File

@ -0,0 +1,37 @@
import React from "react";
import { useHistory, useParams } from "react-router-dom";
import { parseISO, format, isSameDay } from "date-fns";
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import ButtonWithSpinner from "../ButtonWithSpinner";
import MarkdownWrapper from "../MarkdownWrapper";
import { Tooltip } from "@material-ui/core";
import { AuthContext } from "../../context/Auth/AuthContext";
import toastError from "../../errors/toastError";
const TicketListItem = ({ ticket, status }) => {
const history = useHistory();
const { ticketId } = useParams();
const isMounted = React.useRef(true);
const { user } = React.useContext(AuthContext);
const handleSelectTicket = (id) => {
history.push(`/tickets/${id}`);
};
console.log(ticket.id);
if (ticket.status !== status) return null;
return (
<React.Fragment key={ticket.id}>
<h1>{ticket.id}</h1>
<img src={ticket.contact.profilePicUrl} alt={ticket.id} style={{ width: "50px" }} />
<p>{ticket.lastMessage}</p>
</React.Fragment>
);
};
export default TicketListItem;

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";
@ -102,10 +102,10 @@ const useStyles = makeStyles(theme => ({
}));
const TicketListItem = ({ ticket }) => {
const classes = useStyles();
const history = useHistory();
const classes = ReactDOM.useStyles();
const history = ReactDOM.useHistory();
const [loading, setLoading] = useState(false);
const { ticketId } = useParams();
const { ticketId } = ReactDOM.useParams();
const isMounted = useRef(true);
const { user } = useContext(AuthContext);
@ -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,225 @@
import React, { useReducer } from "react";
import openSocket from "socket.io-client";
import useTickets from "../../hooks/useTickets";
import TicketListItem from "../TicketListItem/TicketListItem";
import TicketsListSkeleton from "../TicketsListSkeleton";
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 }) => {
const [pageNumber, setPageNumber] = React.useState(1);
const { user } = React.useContext(AuthContext);
const [ticketsList, dispatch] = useReducer(reducer, []);
const { tickets, hasMore, loading } = useTickets({
pageNumber,
searchParam,
status,
showAll,
queueIds: JSON.stringify(selectedQueueIds),
});
React.useEffect(() => {
dispatch({ type: "RESET" });
setPageNumber(1);
}, [status, searchParam, dispatch, showAll, selectedQueueIds]);
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)) {
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]);
const loadMore = () => {
setPageNumber((prevState) => prevState + 1);
};
const handleScroll = (e) => {
if (!hasMore || loading) return;
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore();
}
};
return (
<ul>
{ticketsList.length === 0 && !loading ? (
<div>
<span>{i18n.t("ticketsList.noTicketsTitle")}</span>
<p>{i18n.t("ticketsList.noTicketsMessage")}</p>
</div>
) : (
<>
{ticketsList.map((ticket) => (
<TicketListItem ticket={ticket} key={ticket.id} status={status} />
))}
</>
)}
{loading && <TicketsListSkeleton />}
</ul>
);
};
export default TicketsList;

View File

@ -12,294 +12,320 @@ import useTickets from "../../hooks/useTickets";
import { i18n } from "../../translate/i18n";
import { AuthContext } from "../../context/Auth/AuthContext";
const useStyles = makeStyles((theme) => ({
ticketsListWrapper: {
position: "relative",
display: "flex",
height: "100%",
flexDirection: "column",
overflow: "hidden",
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
},
const useStyles = makeStyles(theme => ({
ticketsListWrapper: {
position: "relative",
display: "flex",
height: "100%",
flexDirection: "column",
overflow: "hidden",
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
},
ticketsList: {
flex: 1,
overflowY: "scroll",
...theme.scrollbarStyles,
borderTop: "2px solid rgba(0, 0, 0, 0.12)",
},
ticketsList: {
flex: 1,
overflowY: "scroll",
...theme.scrollbarStyles,
borderTop: "2px solid rgba(0, 0, 0, 0.12)",
},
ticketsListHeader: {
color: "rgb(67, 83, 105)",
zIndex: 2,
backgroundColor: "white",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
},
ticketsListHeader: {
color: "rgb(67, 83, 105)",
zIndex: 2,
backgroundColor: "white",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
},
ticketsCount: {
fontWeight: "normal",
color: "rgb(104, 121, 146)",
marginLeft: "8px",
fontSize: "14px",
},
ticketsCount: {
fontWeight: "normal",
color: "rgb(104, 121, 146)",
marginLeft: "8px",
fontSize: "14px",
},
noTicketsText: {
textAlign: "center",
color: "rgb(104, 121, 146)",
fontSize: "14px",
lineHeight: "1.4",
},
noTicketsText: {
textAlign: "center",
color: "rgb(104, 121, 146)",
fontSize: "14px",
lineHeight: "1.4",
},
noTicketsTitle: {
textAlign: "center",
fontSize: "16px",
fontWeight: "600",
margin: "0px",
},
noTicketsTitle: {
textAlign: "center",
fontSize: "16px",
fontWeight: "600",
margin: "0px",
},
noTicketsDiv: {
display: "flex",
height: "100px",
margin: 40,
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
},
noTicketsDiv: {
display: "flex",
height: "100px",
margin: 40,
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
},
}));
const reducer = (state, action) => {
if (action.type === "LOAD_TICKETS") {
const newTickets = action.payload;
if (action.type === "LOAD_TICKETS") {
const newTickets = action.payload;
newTickets.forEach(ticket => {
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);
}
});
// console.log('* ticket.unreadMessages: ',ticket.unreadMessages)
return [...state];
}
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);
}
});
if (action.type === "RESET_UNREAD") {
const ticketId = action.payload;
return [...state];
}
const ticketIndex = state.findIndex((t) => t.id === ticketId);
if (ticketIndex !== -1) {
state[ticketIndex].unreadMessages = 0;
}
if (action.type === "RESET_UNREAD") {
const ticketId = action.payload;
return [...state];
}
const ticketIndex = state.findIndex(t => t.id === ticketId);
if (ticketIndex !== -1) {
state[ticketIndex].unreadMessages = 0;
}
if (action.type === "UPDATE_TICKET") {
const ticket = action.payload;
return [...state];
}
const ticketIndex = state.findIndex((t) => t.id === ticket.id);
if (ticketIndex !== -1) {
state[ticketIndex] = ticket;
} else {
state.unshift(ticket);
}
if (action.type === "UPDATE_TICKET") {
const ticket = action.payload;
return [...state];
}
// console.log('++++++++++++ UPDATE_TICKET: ',ticket)
if (action.type === "UPDATE_TICKET_UNREAD_MESSAGES") {
const message = action.payload.message;
const ticketIndex = state.findIndex(t => t.id === ticket.id);
if (ticketIndex !== -1) {
state[ticketIndex] = ticket;
} else {
state.unshift(ticket);
}
const ticket = action.payload.ticket;
return [...state];
}
const ticketIndex = state.findIndex((t) => t.id === ticket.id);
if (action.type === "UPDATE_TICKET_UNREAD_MESSAGES") {
if (ticketIndex !== -1) {
if (!message.fromMe) {
ticket.unreadMessages += 1;
}
const message = action.payload.message
state[ticketIndex] = ticket;
state.unshift(state.splice(ticketIndex, 1)[0]);
} else {
state.unshift(ticket);
}
const ticket = action.payload.ticket;
return [...state];
}
const ticketIndex = state.findIndex(t => t.id === ticket.id);
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 (ticketIndex !== -1) {
// console.log('>>>>>> ticketIndex: ', ticketIndex)
if (action.type === "DELETE_TICKET") {
const ticketId = action.payload;
const ticketIndex = state.findIndex((t) => t.id === ticketId);
if (ticketIndex !== -1) {
state.splice(ticketIndex, 1);
}
// console.log('&&&&&&& UPDATE_TICKET_UNREAD_MESSAGES ticket: ',ticket, ' |\n MESSAGE: ', message)
if(!message.fromMe){
ticket.unreadMessages +=1
}
return [...state];
}
state[ticketIndex] = ticket;
state.unshift(state.splice(ticketIndex, 1)[0]);
} else {
state.unshift(ticket);
}
if (action.type === "RESET") {
return [];
}
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, style }) => {
const classes = useStyles();
const [pageNumber, setPageNumber] = useState(1);
const [ticketsList, dispatch] = useReducer(reducer, []);
const { user } = useContext(AuthContext);
const TicketsList = (props) => {
const { status, searchParam, showAll, selectedQueueIds, updateCount, style } =
props;
const classes = useStyles();
const [pageNumber, setPageNumber] = useState(1);
const [ticketsList, dispatch] = useReducer(reducer, []);
const { user } = useContext(AuthContext);
useEffect(() => {
dispatch({ type: "RESET" });
setPageNumber(1);
}, [status, searchParam, dispatch, showAll, selectedQueueIds]);
useEffect(() => {
dispatch({ type: "RESET" });
setPageNumber(1);
}, [status, searchParam, dispatch, showAll, selectedQueueIds]);
const { tickets, hasMore, loading } = useTickets({
pageNumber,
searchParam,
status,
showAll,
queueIds: JSON.stringify(selectedQueueIds),
});
const { tickets, hasMore, loading } = useTickets({
pageNumber,
searchParam,
status,
showAll,
queueIds: JSON.stringify(selectedQueueIds),
});
useEffect(() => {
if (!status && !searchParam) return;
dispatch({
type: "LOAD_TICKETS",
payload: tickets,
});
}, [tickets, status, searchParam]);
useEffect(() => {
if (!status && !searchParam) return;
dispatch({
type: "LOAD_TICKETS",
payload: tickets,
});
}, [tickets, status, searchParam]);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
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 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;
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("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,
});
}
socket.on("ticket", data => {
if (data.action === "updateUnread") {
dispatch({
type: "RESET_UNREAD",
payload: data.ticketId,
});
}
if (data.action === "update" && notBelongsToUserQueues(data.ticket)) {
dispatch({ type: "DELETE_TICKET", payload: data.ticket.id });
}
if (data.action === "update" && shouldUpdateTicket(data.ticket)) {
dispatch({
type: "UPDATE_TICKET",
payload: data.ticket,
});
}
if (data.action === "delete") {
dispatch({ type: "DELETE_TICKET", payload: data.ticketId });
}
});
if (data.action === "update" && notBelongsToUserQueues(data.ticket)) {
dispatch({ type: "DELETE_TICKET", payload: data.ticket.id });
}
socket.on("appMessage", (data) => {
if (data.action === "create" && shouldUpdateTicket(data.ticket)) {
dispatch({
type: "UPDATE_TICKET_UNREAD_MESSAGES",
// payload: data.ticket,
payload: data,
});
}
});
if (data.action === "delete") {
dispatch({ type: "DELETE_TICKET", payload: data.ticketId });
}
});
socket.on("contact", (data) => {
if (data.action === "update") {
dispatch({
type: "UPDATE_TICKET_CONTACT",
payload: data.contact,
});
}
});
socket.on("appMessage", data => {
if (data.action === "create" && shouldUpdateTicket(data.ticket)) {
return () => {
socket.disconnect();
};
}, [status, showAll, user, selectedQueueIds]);
// console.log('((((((((((((((((((( DATA.MESSAGE: ', data.message)
useEffect(() => {
if (typeof updateCount === "function") {
updateCount(ticketsList.length);
}
}, [ticketsList]);
dispatch({
type: "UPDATE_TICKET_UNREAD_MESSAGES",
// payload: data.ticket,
payload: data,
});
}
});
const loadMore = () => {
setPageNumber((prevState) => prevState + 1);
};
socket.on("contact", data => {
if (data.action === "update") {
dispatch({
type: "UPDATE_TICKET_CONTACT",
payload: data.contact,
});
}
});
const handleScroll = (e) => {
if (!hasMore || loading) return;
return () => {
socket.disconnect();
};
}, [status, showAll, user, selectedQueueIds]);
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
useEffect(() => {
if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore();
}
};
return (
<Paper className={classes.ticketsListWrapper} style={style}>
<Paper
square
name="closed"
elevation={0}
className={classes.ticketsList}
onScroll={handleScroll}
>
<List style={{ paddingTop: 0 }}>
{ticketsList.length === 0 && !loading ? (
<div className={classes.noTicketsDiv}>
<span className={classes.noTicketsTitle}>{i18n.t("ticketsList.noTicketsTitle")}</span>
<p className={classes.noTicketsText}>{i18n.t("ticketsList.noTicketsMessage")}</p>
</div>
) : (
<>
{ticketsList.map((ticket) => (
<TicketListItem ticket={ticket} key={ticket.id} />
))}
</>
)}
{loading && <TicketsListSkeleton />}
</List>
</Paper>
</Paper>
);
if (typeof updateCount === "function") {
updateCount(ticketsList.length);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ticketsList]);
const loadMore = () => {
setPageNumber(prevState => prevState + 1);
};
const handleScroll = e => {
if (!hasMore || loading) return;
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore();
}
};
return (
<Paper className={classes.ticketsListWrapper} style={style}>
<Paper
square
name="closed"
elevation={0}
className={classes.ticketsList}
onScroll={handleScroll}
>
<List style={{ paddingTop: 0 }}>
{ticketsList.length === 0 && !loading ? (
<div className={classes.noTicketsDiv}>
<span className={classes.noTicketsTitle}>
{i18n.t("ticketsList.noTicketsTitle")}
</span>
<p className={classes.noTicketsText}>
{i18n.t("ticketsList.noTicketsMessage")}
</p>
</div>
) : (
<>
{ticketsList.map(ticket => (
<TicketListItem ticket={ticket} key={ticket.id} />
))}
</>
)}
{loading && <TicketsListSkeleton />}
</List>
</Paper>
</Paper>
);
};
export default TicketsList;

View File

@ -1,337 +1,64 @@
import React, { useContext, useEffect, useRef, useState } from "react";
import openSocket from "socket.io-client"
import { makeStyles } from "@material-ui/core/styles";
import Paper from "@material-ui/core/Paper";
import SearchIcon from "@material-ui/icons/Search";
import InputBase from "@material-ui/core/InputBase";
import Tabs from "@material-ui/core/Tabs";
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 { Button } from "@material-ui/core";
import React from "react";
import NewTicketModal from "../NewTicketModal";
import TicketsList from "../TicketsList";
import TabPanel from "../TabPanel";
import TicketsList from "../TicketsList/TicketsList";
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",
display: "flex",
height: "100%",
flexDirection: "column",
overflow: "hidden",
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
},
tabsHeader: {
flex: "none",
backgroundColor: "#eee",
},
settingsIcon: {
alignSelf: "center",
marginLeft: "auto",
padding: 8,
},
tab: {
minWidth: 120,
width: 120,
},
ticketOptionsBox: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
background: "#fafafa",
padding: theme.spacing(1),
},
serachInputWrapper: {
flex: 1,
background: "#fff",
display: "flex",
borderRadius: 40,
padding: 4,
marginRight: theme.spacing(1),
},
searchIcon: {
color: "grey",
marginLeft: 6,
marginRight: 6,
alignSelf: "center",
},
searchInput: {
flex: 1,
border: "none",
borderRadius: 30,
},
badge: {
right: "-10px",
},
show: {
display: "block",
},
hide: {
display: "none !important",
},
}));
import TicketsManagerStyled from "./TicketsManager.style";
const TicketsManager = () => {
const classes = useStyles();
const [valueTab, setValueTab] = React.useState("open");
// Old New State
const [newPage, setNewPage] = React.useState(true);
// Old New State
const [newTicketModalOpen, setNewTicketModalOpen] = React.useState(false);
const [showAllTickets, setShowAllTickets] = React.useState(false);
const { user } = React.useContext(AuthContext);
const [searchParam, setSearchParam] = useState("");
const [tab, setTab] = useState("open");
const [tabOpen, setTabOpen] = useState("open");
const [newTicketModalOpen, setNewTicketModalOpen] = useState(false);
const [showAllTickets, setShowAllTickets] = useState(false);
const searchInputRef = useRef();
const { user } = useContext(AuthContext);
const [openCount, setOpenCount] = useState(0);
const [pendingCount, setPendingCount] = useState(0);
const userQueueIds = user.queues.map((q) => q.id);
const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []);
const socket = openSocket.open(process.env.REACT_APP_BACKEND_URL)
const [selectedQueueIds, setSelectedQueueIds] = React.useState(userQueueIds || []);
console.log(user.queues)
console.log(selectedQueueIds)
socket.on("connect", () => {
if ("open") {
socket.emit("joinTickets", "open");
} else {
socket.emit("joinNotification");
}
});
socket.on("connect", ()=>{
})
useEffect(() => {
React.useEffect(() => {
if (user.profile.toUpperCase() === "ADMIN") {
setShowAllTickets(true);
}
}, [user.profile]);
useEffect(() => {
if (tab === "search") {
searchInputRef.current.focus();
}
}, [tab]);
let searchTimeout;
const handleSearch = (e) => {
const searchedTerm = e.target.value.toLowerCase();
clearTimeout(searchTimeout);
if (searchedTerm === "") {
setSearchParam(searchedTerm);
setTab("open");
return;
}
searchTimeout = setTimeout(() => {
setSearchParam(searchedTerm);
}, 500);
};
const handleChangeTab = (e, newValue) => {
setTab(newValue);
};
const handleChangeTabOpen = (e, newValue) => {
setTabOpen(newValue);
};
const applyPanelStyle = (status) => {
if (tabOpen !== status) {
return { width: 0, height: 0 };
}
};
if (newPage) {
return (
<>
<NewTicketModal
modalOpen={newTicketModalOpen}
onClose={(e) => setNewTicketModalOpen(false)}
/>
<div>
<TicketsQueueSelect
style={{ marginLeft: 6 }}
selectedQueueIds={selectedQueueIds}
userQueues={user?.queues}
onChange={(values) => setSelectedQueueIds(values)}
/>
<TicketsList
status="open"
showAll={showAllTickets}
selectedQueueIds={selectedQueueIds}
updateCount={(val) => setOpenCount(val)}
style={applyPanelStyle("open")}
/>
<TicketsList
status="pending"
selectedQueueIds={selectedQueueIds}
updateCount={(val) => setPendingCount(val)}
style={applyPanelStyle("pending")}
/>
</div>
</>
);
const styleTmp = {
padding:"12px 0 ",
cursor: "pointer",
}
console.log(valueTab);
return (
<Paper elevation={0} variant="outlined" className={classes.ticketsWrapper}>
<TicketsManagerStyled>
<ul>
<li style={styleTmp} id="open" onClick={(e) => setValueTab(e.target.id)}>
Abertos
</li>
<li style={styleTmp} id="pending" onClick={(e) => setValueTab(e.target.id)}>
Pendentes
</li>
<li style={styleTmp} id="closed" onClick={(e) => setValueTab(e.target.id)}>
Fechados
</li>
</ul>
<TicketsList
status={valueTab}
selectedQueueIds={selectedQueueIds}
updateCount={(val) => setPendingCount(val)}
/>
<NewTicketModal
modalOpen={newTicketModalOpen}
onClose={(e) => setNewTicketModalOpen(false)}
onClose={(e) => setNewTicketModalOpen(true)}
/>
<Paper elevation={0} square className={classes.tabsHeader}>
<Tabs
value={tab}
onChange={handleChangeTab}
variant="fullWidth"
indicatorColor="primary"
textColor="primary"
aria-label="icon label tabs example"
>
<Tab
value={"open"}
icon={<MoveToInboxIcon />}
label={i18n.t("tickets.tabs.open.title")}
classes={{ root: classes.tab }}
/>
<Tab
value={"closed"}
icon={<CheckBoxIcon />}
label={i18n.t("tickets.tabs.closed.title")}
classes={{ root: classes.tab }}
/>
<Tab
value={"search"}
icon={<SearchIcon />}
label={i18n.t("tickets.tabs.search.title")}
classes={{ root: classes.tab }}
/>
</Tabs>
</Paper>
<Paper square elevation={0} className={classes.ticketOptionsBox}>
{tab === "search" ? (
<div className={classes.serachInputWrapper}>
<SearchIcon className={classes.searchIcon} />
<InputBase
className={classes.searchInput}
inputRef={searchInputRef}
placeholder={i18n.t("tickets.search.placeholder")}
type="search"
onChange={handleSearch}
/>
</div>
) : (
<>
<Button variant="outlined" color="primary" onClick={() => setNewTicketModalOpen(true)}>
{i18n.t("ticketsManager.buttons.newTicket")}
</Button>
<Can
role={user.profile}
perform="tickets-manager:showall"
yes={() => (
<FormControlLabel
label={i18n.t("tickets.buttons.showAll")}
labelPlacement="start"
control={
<Switch
size="small"
checked={showAllTickets}
onChange={() => setShowAllTickets((prevState) => !prevState)}
name="showAllTickets"
color="primary"
/>
}
/>
)}
/>
</>
)}
<TicketsQueueSelect
style={{ marginLeft: 6 }}
selectedQueueIds={selectedQueueIds}
userQueues={user?.queues}
onChange={(values) => setSelectedQueueIds(values)}
/>
</Paper>
<TabPanel value={tab} name="open" className={classes.ticketsWrapper}>
<Tabs
value={tabOpen}
onChange={handleChangeTabOpen}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
>
<Tab
label={
<Badge className={classes.badge} badgeContent={openCount} color="primary">
{i18n.t("ticketsList.assignedHeader")}
</Badge>
}
value={"open"}
/>
<Tab
label={
<Badge className={classes.badge} badgeContent={pendingCount} color="secondary">
{i18n.t("ticketsList.pendingHeader")}
</Badge>
}
value={"pending"}
/>
</Tabs>
<Paper className={classes.ticketsWrapper}>
<TicketsList
status="open"
showAll={showAllTickets}
selectedQueueIds={selectedQueueIds}
updateCount={(val) => setOpenCount(val)}
style={applyPanelStyle("open")}
/>
<TicketsList
status="pending"
selectedQueueIds={selectedQueueIds}
updateCount={(val) => setPendingCount(val)}
style={applyPanelStyle("pending")}
/>
</Paper>
</TabPanel>
<TabPanel value={tab} name="closed" className={classes.ticketsWrapper}>
<TicketsList status="closed" showAll={true} selectedQueueIds={selectedQueueIds} />
</TabPanel>
<TabPanel value={tab} name="search" className={classes.ticketsWrapper}>
<TicketsList searchParam={searchParam} showAll={true} selectedQueueIds={selectedQueueIds} />
</TabPanel>
</Paper>
</TicketsManagerStyled>
);
};

View File

@ -0,0 +1,7 @@
import styled from "styled-components"
const TicketsManagerStyled = styled.div`
display:flex;
flex-direction: column;
`
export default TicketsManagerStyled

View File

@ -3,7 +3,8 @@ import styled from "styled-components";
const TicketsStyled = styled.div`
display: flex;
flex-direction: row;
height: 86vh;
min-height: auto;
`;
export default TicketsStyled