diff --git a/backend/src/services/TicketServices/CreateTicketService.ts b/backend/src/services/TicketServices/CreateTicketService.ts index 6985c84..f019c3e 100644 --- a/backend/src/services/TicketServices/CreateTicketService.ts +++ b/backend/src/services/TicketServices/CreateTicketService.ts @@ -28,28 +28,34 @@ interface Request { contactId: number; status: string; userId: number; - msg?: string + msg?: string, + queueId?: string | undefined } const CreateTicketService = async ({ contactId, status, userId, - msg = '' + msg = '', + queueId = undefined }: Request): Promise => { try { console.log('Create contact service........') - const defaultWhatsapp = await GetDefaultWhatsApp(userId); + const defaultWhatsapp = await GetDefaultWhatsApp(userId); - const user = await User.findByPk(userId, { raw: true, }) + const user = await User.findByPk(userId, { raw: true, }) - const matchingQueue = await whatsappQueueMatchingUserQueue(userId, defaultWhatsapp, user?.profile); + if (!queueId) { + + const matchingQueue = await whatsappQueueMatchingUserQueue(userId, defaultWhatsapp, user?.profile); + + queueId = matchingQueue ? matchingQueue.queueId : undefined + + } - const queueId = matchingQueue ? matchingQueue.queueId : undefined - await CheckContactOpenTickets(contactId, defaultWhatsapp.id); const { isGroup } = await ShowContactService(contactId); @@ -77,7 +83,7 @@ const CreateTicketService = async ({ sendWhatsAppMessageSocket(ticket, msg) }, 3000) - + } diff --git a/backend/src/services/TicketServices/ListTicketsService.ts b/backend/src/services/TicketServices/ListTicketsService.ts index 3f542d9..2363a41 100644 --- a/backend/src/services/TicketServices/ListTicketsService.ts +++ b/backend/src/services/TicketServices/ListTicketsService.ts @@ -161,6 +161,21 @@ const ListTicketsService = async ({ // } // ]; + + includeCondition = [ + ...includeCondition, + { + model: Message, + as: "messages", + attributes: ["id", "body"], + where: { + body: where(fn("LOWER", col("body")), "LIKE", `%ADRIANO ROB%`) + }, + required: false, + duplicating: false + } + ]; + whereCondition = { ...whereCondition, [Op.or]: [ @@ -173,8 +188,21 @@ const ListTicketsService = async ({ // { // "$message.body$": where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParam}%`) // } - ] + ], + "$message.body$": where(fn("LOWER", col("body")), "LIKE", `%ADRIANO ROB%`) }; + + + // whereCondition = { + // ...whereCondition, + // "$message.body$": where( + // fn("LOWER", col("body")), + // "LIKE", + // `%${sanitizedSearchParam}%` + // ) + // }; + + } if (date) { @@ -200,8 +228,6 @@ const ListTicketsService = async ({ const offset = limit * (+pageNumber - 1); - - const { count, rows: tickets } = await Ticket.findAndCountAll({ where: whereCondition, include: includeCondition, diff --git a/frontend/src/components/ContactCreateTicketModal/index.js b/frontend/src/components/ContactCreateTicketModal/index.js new file mode 100644 index 0000000..af8add1 --- /dev/null +++ b/frontend/src/components/ContactCreateTicketModal/index.js @@ -0,0 +1,119 @@ +import React, { useState, useEffect, useContext } from "react"; +import { useHistory } from "react-router-dom"; +import { toast } from "react-toastify"; + +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import Select from "@material-ui/core/Select"; +import FormControl from "@material-ui/core/FormControl"; +import InputLabel from "@material-ui/core/InputLabel"; +import MenuItem from "@material-ui/core/MenuItem"; +import { makeStyles } from "@material-ui/core"; + +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; + +import { i18n } from "../../translate/i18n"; +import ButtonWithSpinner from "../ButtonWithSpinner"; +import { AuthContext } from "../../context/Auth/AuthContext"; + +import toastError from "../../errors/toastError"; + +import api from "../../services/api"; + +const useStyles = makeStyles((theme) => ({ + maxWidth: { + width: "100%", + }, + paper: { + minWidth: "300px" + } +})); + +const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { + const { user } = useContext(AuthContext); + + const history = useHistory(); + const [queues, setQueues] = useState([]); + const [loading, setLoading] = useState(false); + const [selectedQueue, setSelectedQueue] = useState(''); + const classes = useStyles(); + + useEffect(() => { + const userQueues = user.queues.map(({ id, name, color }) => { return { id, name, color } }) + setQueues(userQueues) + }, [user]); + + const handleClose = () => { + onClose(); + }; + + const handleSaveTicket = async (e) => { + e.preventDefault() + if (!contactId) return; + if (!selectedQueue) { + toast.warning("Nenhuma Fila Selecionada") + return + } + setLoading(true); + try { + const { data: ticket } = await api.post("/tickets", { + contactId: contactId, + userId: user?.id, + queueId: selectedQueue, + status: "open", + }); + history.push(`/tickets/${ticket.id}`); + } catch (err) { + toastError(err); + onClose() + } + setLoading(false); + }; + + return ( + +
+ + {i18n.t("newTicketModal.title")} + + + + {i18n.t("Selecionar Fila")} + + + + + + + {i18n.t("newTicketModal.buttons.ok")} + + +
+
+ ); +}; + +export default ContactCreateTicketModal; \ No newline at end of file diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/TicketsList/index.js index 90e2e01..be1f699 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/TicketsList/index.js @@ -201,7 +201,8 @@ const TicketsList = (props) => { tab }); - useEffect(() => { + useEffect(() => { + if (!status && !searchParam) return; // if (searchParam) { diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/TicketsManager/index.js index 038601e..0409d48 100644 --- a/frontend/src/components/TicketsManager/index.js +++ b/frontend/src/components/TicketsManager/index.js @@ -1,14 +1,18 @@ import React, { useContext, useEffect, useRef, useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; +import { IconButton } from "@mui/material"; 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 SearchIcon from "@material-ui/icons/Search"; import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"; import CheckBoxIcon from "@material-ui/icons/CheckBox"; +import MenuIcon from "@material-ui/icons/Menu"; +import FindInPageIcon from '@material-ui/icons/FindInPage'; import FormControlLabel from "@material-ui/core/FormControlLabel"; import Switch from "@material-ui/core/Switch"; @@ -63,6 +67,25 @@ const useStyles = makeStyles((theme) => ({ }, serachInputWrapper: { + flex: 1, + display: "flex", + flexDirection: "column", + gap: "10px", + borderRadius: 40, + padding: 4, + marginRight: theme.spacing(1), + }, + + searchInputHeader: { + flex: 1, + background: "#fff", + display: "flex", + borderRadius: 40, + padding: 4, + marginRight: theme.spacing(1), + }, + + searchContentInput: { flex: 1, background: "#fff", display: "flex", @@ -78,6 +101,11 @@ const useStyles = makeStyles((theme) => ({ alignSelf: "center", }, + menuSearch: { + color: "grey", + alignSelf: "center", + }, + searchInput: { flex: 1, border: "none", @@ -95,20 +123,21 @@ const useStyles = makeStyles((theme) => ({ }, })); +const DEFAULT_SEARCH_PARAM = { searchParam: "", searchParamContent: "" } + const TicketsManager = () => { const { tabOption, setTabOption } = useContext(TabTicketContext); - const {setSearchTicket} = useContext(SearchTicketContext) + const { setSearchTicket } = useContext(SearchTicketContext) const classes = useStyles(); - const [searchParam, setSearchParam] = useState(""); + const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM); 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); @@ -117,7 +146,11 @@ const TicketsManager = () => { const userQueueIds = user.queues.map((q) => q.id); const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []); + const [showContentSearch, setShowContentSearch] = useState(false) + const searchInputRef = useRef(); + const searchContentInputRef = useRef(); const [inputSearch, setInputSearch] = useState(''); + const [inputContentSearch, setInputContentSearch] = useState("") useEffect(() => { if (user.profile.toUpperCase() === "ADMIN") { @@ -141,7 +174,7 @@ const TicketsManager = () => { if (tabOption === 'open') { setTabOption('') - setSearchParam(''); + setSearchParam(DEFAULT_SEARCH_PARAM); setInputSearch(''); setTab("open"); return; @@ -163,12 +196,12 @@ const TicketsManager = () => { setInputSearch(removeExtraSpace(searchedTerm)) - setSearchTicket(searchParam) + setSearchTicket(searchParam.searchParam) clearTimeout(searchTimeout); if (searchedTerm === "") { - setSearchParam(searchedTerm); + setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })) setInputSearch(searchedTerm) setTab("open"); return; @@ -176,11 +209,16 @@ const TicketsManager = () => { searchTimeout = setTimeout(() => { - setSearchParam(searchedTerm); + setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })); }, 500); }; + const handleContentSearch = e => { + let searchedContentText = e.target.value.toLowerCase() + setInputContentSearch(searchedContentText) + } + const handleChangeTab = (e, newValue) => { setTab(newValue); }; @@ -233,15 +271,34 @@ const TicketsManager = () => { {tab === "search" ? (
- - +
+ + + setShowContentSearch(prev => !prev)}> + + +
+ { + showContentSearch ? + (
+ + +
) : null + }
) : ( <> @@ -340,17 +397,17 @@ const TicketsManager = () => { - - - + + +
); }; -export default TicketsManager; +export default TicketsManager; \ No newline at end of file diff --git a/frontend/src/pages/Contacts/index.js b/frontend/src/pages/Contacts/index.js index 44f1265..f870374 100644 --- a/frontend/src/pages/Contacts/index.js +++ b/frontend/src/pages/Contacts/index.js @@ -37,8 +37,9 @@ import { Can } from "../../components/Can"; import apiBroker from "../../services/apiBroker"; import fileDownload from 'js-file-download' +import ContactCreateTicketModal from "../../components/ContactCreateTicketModal"; + - const reducer = (state, action) => { @@ -111,6 +112,7 @@ const Contacts = () => { const [contacts, dispatch] = useReducer(reducer, []); const [selectedContactId, setSelectedContactId] = useState(null); const [contactModalOpen, setContactModalOpen] = useState(false); + const [isCreateTicketModalOpen, setIsCreateTicketModalOpen] = useState(false) const [deletingContact, setDeletingContact] = useState(null); const [confirmOpen, setConfirmOpen] = useState(false); const [hasMore, setHasMore] = useState(false); @@ -118,17 +120,17 @@ const Contacts = () => { const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined) - const [zipfile, setZipFile] = useState() - + const [zipfile, setZipFile] = useState() + async function handleChange(event) { try { - if (event.target.files[0].size > 1024 * 1024 * 4){ + if (event.target.files[0].size > 1024 * 1024 * 4) { alert('Arquivo não pode ser maior que 4 MB!') return - } + } const formData = new FormData(); formData.append("adminId", user.id); @@ -302,6 +304,15 @@ const Contacts = () => { setContactModalOpen(false); }; + const handleOpenCreateTicketModal = (contactId) => { + setSelectedContactId(contactId) + setIsCreateTicketModalOpen(true) + } + + const handleCloseCreateTicketModal = () => { + setIsCreateTicketModalOpen(false) + } + const handleSaveTicket = async (contactId) => { if (!contactId) return; setLoading(true); @@ -415,21 +426,21 @@ const Contacts = () => { switch (param) { case 'empty': return ( - <> + <> - + - + {/*