mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-19 20:29:17 +00:00
feat: added delete tickets option
This commit is contained in:
@@ -120,3 +120,23 @@ exports.update = async (req, res) => {
|
|||||||
|
|
||||||
res.status(200).json(ticket);
|
res.status(200).json(ticket);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.delete = async (req, res) => {
|
||||||
|
const io = getIO();
|
||||||
|
const { ticketId } = req.params;
|
||||||
|
|
||||||
|
const ticket = await Ticket.findByPk(ticketId);
|
||||||
|
|
||||||
|
if (!ticket) {
|
||||||
|
return res.status(400).json({ error: "No ticket found with this ID" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await ticket.destroy();
|
||||||
|
|
||||||
|
io.to("notification").emit("ticket", {
|
||||||
|
action: "delete",
|
||||||
|
ticketId: ticket.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({ message: "ticket deleted" });
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,4 +11,6 @@ routes.post("/tickets", isAuth, TicketController.store);
|
|||||||
|
|
||||||
routes.put("/tickets/:ticketId", isAuth, TicketController.update);
|
routes.put("/tickets/:ticketId", isAuth, TicketController.update);
|
||||||
|
|
||||||
|
routes.delete("/tickets/:ticketId", isAuth, TicketController.delete);
|
||||||
|
|
||||||
module.exports = routes;
|
module.exports = routes;
|
||||||
|
|||||||
73
frontend/src/components/MessagesList/TicketOptionsMenu.js
Normal file
73
frontend/src/components/MessagesList/TicketOptionsMenu.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
import MenuItem from "@material-ui/core/MenuItem";
|
||||||
|
import Menu from "@material-ui/core/Menu";
|
||||||
|
|
||||||
|
import api from "../../services/api";
|
||||||
|
import ConfirmationModal from "../ConfirmationModal";
|
||||||
|
|
||||||
|
const TicketOptionsMenu = ({ ticket, menuOpen, handleClose, anchorEl }) => {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const [confirmationOpen, setConfirmationOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleDeleteTicket = async () => {
|
||||||
|
try {
|
||||||
|
await api.delete(`/tickets/${ticket.id}`);
|
||||||
|
toast.success("Ticket deletado com sucesso.");
|
||||||
|
history.push("/chat");
|
||||||
|
} catch (err) {
|
||||||
|
toast.error("Erro ao deletar o ticket");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("deleted");
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTransferTicket = e => {
|
||||||
|
console.log("transfered");
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenConfirmationModal = e => {
|
||||||
|
setConfirmationOpen(true);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(ticket);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Menu
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
open={menuOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={handleOpenConfirmationModal}>Deletar</MenuItem>
|
||||||
|
<MenuItem onClick={handleTransferTicket}>Transferir</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
<ConfirmationModal
|
||||||
|
title={`Deletar o ticket #${ticket.id} do contato ${ticket.contact.name}?`}
|
||||||
|
open={confirmationOpen}
|
||||||
|
setOpen={setConfirmationOpen}
|
||||||
|
onConfirm={handleDeleteTicket}
|
||||||
|
>
|
||||||
|
Atenção, todas as mensagens relacionadas a este ticket serão apagadas.
|
||||||
|
</ConfirmationModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TicketOptionsMenu;
|
||||||
@@ -20,12 +20,15 @@ import Button from "@material-ui/core/Button";
|
|||||||
import Paper from "@material-ui/core/Paper";
|
import Paper from "@material-ui/core/Paper";
|
||||||
import { green } from "@material-ui/core/colors";
|
import { green } from "@material-ui/core/colors";
|
||||||
import Skeleton from "@material-ui/lab/Skeleton";
|
import Skeleton from "@material-ui/lab/Skeleton";
|
||||||
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
|
import MoreVertIcon from "@material-ui/icons/MoreVert";
|
||||||
|
|
||||||
import api from "../../services/api";
|
import api from "../../services/api";
|
||||||
import ContactDrawer from "../ContactDrawer";
|
import ContactDrawer from "../ContactDrawer";
|
||||||
import whatsBackground from "../../assets/wa-background.png";
|
import whatsBackground from "../../assets/wa-background.png";
|
||||||
import LinkifyWithTargetBlank from "../LinkifyWithTargetBlank";
|
import LinkifyWithTargetBlank from "../LinkifyWithTargetBlank";
|
||||||
import MessageInput from "../MessageInput/";
|
import MessageInput from "../MessageInput/";
|
||||||
|
import TicketOptionsMenu from "./TicketOptionsMenu";
|
||||||
|
|
||||||
const drawerWidth = 320;
|
const drawerWidth = 320;
|
||||||
|
|
||||||
@@ -65,7 +68,7 @@ const useStyles = makeStyles(theme => ({
|
|||||||
|
|
||||||
messagesHeader: {
|
messagesHeader: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
cursor: "pointer",
|
// cursor: "pointer",
|
||||||
backgroundColor: "#eee",
|
backgroundColor: "#eee",
|
||||||
flex: "none",
|
flex: "none",
|
||||||
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
|
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
|
||||||
@@ -233,6 +236,9 @@ const MessagesList = () => {
|
|||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
const lastMessageRef = useRef();
|
const lastMessageRef = useRef();
|
||||||
|
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
const moreMenuOpen = Boolean(anchorEl);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMessagesList([]);
|
setMessagesList([]);
|
||||||
}, [searchParam]);
|
}, [searchParam]);
|
||||||
@@ -366,9 +372,15 @@ const MessagesList = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenTicketOptionsMenu = e => {
|
||||||
|
setAnchorEl(e.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseTicketOptionsMenu = e => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleUpdateTicketStatus = async (e, status, userId) => {
|
const handleUpdateTicketStatus = async (e, status, userId) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
try {
|
try {
|
||||||
await api.put(`/tickets/${ticketId}`, {
|
await api.put(`/tickets/${ticketId}`, {
|
||||||
status: status,
|
status: status,
|
||||||
@@ -380,6 +392,14 @@ const MessagesList = () => {
|
|||||||
history.push("/chat");
|
history.push("/chat");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDrawerOpen = () => {
|
||||||
|
setDrawerOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrawerClose = () => {
|
||||||
|
setDrawerOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const renderMessageAck = message => {
|
const renderMessageAck = message => {
|
||||||
if (message.ack === 0) {
|
if (message.ack === 0) {
|
||||||
return <AccessTimeIcon fontSize="small" className={classes.ackIcons} />;
|
return <AccessTimeIcon fontSize="small" className={classes.ackIcons} />;
|
||||||
@@ -480,13 +500,6 @@ const MessagesList = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDrawerOpen = () => {
|
|
||||||
setDrawerOpen(true);
|
|
||||||
};
|
|
||||||
const handleDrawerClose = () => {
|
|
||||||
setDrawerOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root} id="drawer-container">
|
<div className={classes.root} id="drawer-container">
|
||||||
<Paper
|
<Paper
|
||||||
@@ -496,12 +509,10 @@ const MessagesList = () => {
|
|||||||
[classes.mainWrapperShift]: drawerOpen,
|
[classes.mainWrapperShift]: drawerOpen,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Card
|
<Card square className={classes.messagesHeader}>
|
||||||
square
|
|
||||||
className={classes.messagesHeader}
|
|
||||||
onClick={handleDrawerOpen}
|
|
||||||
>
|
|
||||||
<CardHeader
|
<CardHeader
|
||||||
|
onClick={handleDrawerOpen}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
titleTypographyProps={{ noWrap: true }}
|
titleTypographyProps={{ noWrap: true }}
|
||||||
subheaderTypographyProps={{ noWrap: true }}
|
subheaderTypographyProps={{ noWrap: true }}
|
||||||
avatar={
|
avatar={
|
||||||
@@ -557,6 +568,15 @@ const MessagesList = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<IconButton onClick={handleOpenTicketOptionsMenu}>
|
||||||
|
<MoreVertIcon />
|
||||||
|
</IconButton>
|
||||||
|
<TicketOptionsMenu
|
||||||
|
ticket={ticket}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
menuOpen={moreMenuOpen}
|
||||||
|
handleClose={handleCloseTicketOptionsMenu}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -251,11 +251,14 @@ const TicketsList = () => {
|
|||||||
|
|
||||||
socket.on("ticket", data => {
|
socket.on("ticket", data => {
|
||||||
if (data.action === "updateUnread") {
|
if (data.action === "updateUnread") {
|
||||||
resetUnreadMessages(data.ticketId);
|
resetUnreadMessages(data);
|
||||||
}
|
}
|
||||||
if (data.action === "updateStatus" || data.action === "create") {
|
if (data.action === "updateStatus" || data.action === "create") {
|
||||||
updateTickets(data);
|
updateTickets(data);
|
||||||
}
|
}
|
||||||
|
if (data.action === "delete") {
|
||||||
|
deleteTicket(data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("appMessage", data => {
|
socket.on("appMessage", data => {
|
||||||
@@ -277,57 +280,64 @@ const TicketsList = () => {
|
|||||||
};
|
};
|
||||||
}, [ticketId, userId]);
|
}, [ticketId, userId]);
|
||||||
|
|
||||||
const updateUnreadMessagesCount = data => {
|
const updateUnreadMessagesCount = ({ message, ticket }) => {
|
||||||
setTickets(prevState => {
|
setTickets(prevState => {
|
||||||
const ticketIndex = prevState.findIndex(
|
const ticketIndex = prevState.findIndex(t => t.id === message.ticketId);
|
||||||
ticket => ticket.id === data.message.ticketId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ticketIndex !== -1) {
|
if (ticketIndex !== -1) {
|
||||||
let aux = [...prevState];
|
let aux = [...prevState];
|
||||||
if (!data.message.fromMe) {
|
if (!message.fromMe) {
|
||||||
aux[ticketIndex].unreadMessages++;
|
aux[ticketIndex].unreadMessages++;
|
||||||
}
|
}
|
||||||
aux[ticketIndex].lastMessage = data.message.body;
|
aux[ticketIndex].lastMessage = message.body;
|
||||||
aux[ticketIndex].status = data.ticket.status;
|
aux[ticketIndex].status = ticket.status;
|
||||||
aux.unshift(aux.splice(ticketIndex, 1)[0]);
|
aux.unshift(aux.splice(ticketIndex, 1)[0]);
|
||||||
return aux;
|
return aux;
|
||||||
} else {
|
} else {
|
||||||
return [data.ticket, ...prevState];
|
return [ticket, ...prevState];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateTickets = data => {
|
const updateTickets = ({ ticket }) => {
|
||||||
setTickets(prevState => {
|
setTickets(prevState => {
|
||||||
const ticketIndex = prevState.findIndex(
|
const ticketIndex = prevState.findIndex(t => t.id === ticket.id);
|
||||||
ticket => ticket.id === data.ticket.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ticketIndex === -1) {
|
if (ticketIndex === -1) {
|
||||||
return [data.ticket, ...prevState];
|
return [ticket, ...prevState];
|
||||||
} else {
|
} else {
|
||||||
let aux = [...prevState];
|
let aux = [...prevState];
|
||||||
aux[ticketIndex] = data.ticket;
|
aux[ticketIndex] = ticket;
|
||||||
return aux;
|
return aux;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const showDesktopNotification = data => {
|
const deleteTicket = ({ ticketId }) => {
|
||||||
|
setTickets(prevState => {
|
||||||
|
const ticketIndex = prevState.findIndex(ticket => ticket.id === ticketId);
|
||||||
|
|
||||||
|
if (ticketIndex === -1) {
|
||||||
|
return prevState;
|
||||||
|
} else {
|
||||||
|
let aux = [...prevState];
|
||||||
|
aux.splice(ticketIndex, 1);
|
||||||
|
return aux;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDesktopNotification = ({ message, contact, ticket }) => {
|
||||||
const options = {
|
const options = {
|
||||||
body: `${data.message.body} - ${format(new Date(), "HH:mm")}`,
|
body: `${message.body} - ${format(new Date(), "HH:mm")}`,
|
||||||
icon: data.contact.profilePicUrl,
|
icon: contact.profilePicUrl,
|
||||||
tag: data.ticket.id,
|
tag: ticket.id,
|
||||||
};
|
};
|
||||||
let notification = new Notification(
|
let notification = new Notification(`Mensagem de ${contact.name}`, options);
|
||||||
`Mensagem de ${data.contact.name}`,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
|
|
||||||
notification.onclick = function (event) {
|
notification.onclick = function (event) {
|
||||||
event.preventDefault(); //
|
event.preventDefault(); //
|
||||||
window.open(`/chat/${data.ticket.id}`, "_self");
|
window.open(`/chat/${ticket.id}`, "_self");
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", () => {
|
document.addEventListener("visibilitychange", () => {
|
||||||
@@ -339,7 +349,7 @@ const TicketsList = () => {
|
|||||||
document.getElementById("sound").play();
|
document.getElementById("sound").play();
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetUnreadMessages = ticketId => {
|
const resetUnreadMessages = ({ ticketId }) => {
|
||||||
setTickets(prevState => {
|
setTickets(prevState => {
|
||||||
const ticketIndex = prevState.findIndex(
|
const ticketIndex = prevState.findIndex(
|
||||||
ticket => ticket.id === +ticketId
|
ticket => ticket.id === +ticketId
|
||||||
|
|||||||
Reference in New Issue
Block a user