Atualização basica para envio de template do Whatsapp cloud API
parent
72c489a27b
commit
c151473d52
|
@ -19,6 +19,8 @@ import {
|
|||
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
|
||||
import sendWhatsAppMessageOfficialAPI from "../helpers/sendWhatsAppMessageOfficialAPI";
|
||||
import Whatsapp from "../models/Whatsapp";
|
||||
import checkLastClientMsg24hs from "../helpers/CheckLastClientMsg24hs";
|
||||
import AppError from "../errors/AppError";
|
||||
|
||||
type IndexQuery = {
|
||||
pageNumber: string;
|
||||
|
@ -29,7 +31,8 @@ type MessageData = {
|
|||
fromMe: boolean;
|
||||
read: boolean;
|
||||
quotedMsg?: Message;
|
||||
mic_audio?: boolean
|
||||
mic_audio?: boolean;
|
||||
params: any;
|
||||
};
|
||||
|
||||
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||
|
@ -46,12 +49,92 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
|
|||
|
||||
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||
const { ticketId } = req.params;
|
||||
const { body, quotedMsg, mic_audio }: MessageData = req.body;
|
||||
const { body, quotedMsg, mic_audio, params }: MessageData = req.body;
|
||||
const medias = req.files as Express.Multer.File[];
|
||||
const ticket = await ShowTicketService(ticketId);
|
||||
|
||||
const { queueId } = ticket;
|
||||
console.log("-----------> queueId: ", queueId, " | quotedMsg: ", quotedMsg);
|
||||
console.log(
|
||||
"-----------> queueId: ",
|
||||
queueId,
|
||||
" | quotedMsg: ",
|
||||
quotedMsg,
|
||||
" | params: ",
|
||||
params
|
||||
);
|
||||
|
||||
const { phoneNumberId, whatsappId } = ticket;
|
||||
|
||||
if (phoneNumberId) {
|
||||
const into24hs = await checkLastClientMsg24hs(ticket);
|
||||
|
||||
if (into24hs && into24hs.length == 0) {
|
||||
if (params) {
|
||||
console.log("SEND TEMPLATE PARAMS: ", params);
|
||||
|
||||
// return res.send()
|
||||
|
||||
let payloadComponents = [];
|
||||
|
||||
try {
|
||||
for (let i in params) {
|
||||
const { parameters, language, type } = params[i];
|
||||
if (type == "BODY") {
|
||||
if (parameters && parameters.length > 0) {
|
||||
let components: any = [{ type: "body", parameters: [] }];
|
||||
for (let x in parameters) {
|
||||
const { type, text, index } = parameters[x];
|
||||
console.log(text);
|
||||
components[0].parameters.splice(index - 1, 0, {
|
||||
type,
|
||||
text
|
||||
});
|
||||
}
|
||||
payloadComponents.push(components[0]);
|
||||
}
|
||||
} else if (type == "BUTTONS") {
|
||||
}
|
||||
}
|
||||
|
||||
const name = params.find((p: any) => p?.template_name);
|
||||
const { language }: any = params.find((p: any) => p?.language);
|
||||
|
||||
const { template_name } = name;
|
||||
|
||||
if (template_name && language) {
|
||||
const template: any = {
|
||||
template: {
|
||||
name: template_name,
|
||||
language: { code: language },
|
||||
components: payloadComponents
|
||||
}
|
||||
};
|
||||
|
||||
sendWhatsAppMessageOfficialAPI(ticket, body, null, template);
|
||||
|
||||
console.log("TEMPLATE: ", template);
|
||||
return res.send();
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new AppError(error.message);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const { wabaId }: any = await Whatsapp.findByPk(whatsappId);
|
||||
|
||||
const { data } = await whatsappOfficialAPI.get(
|
||||
`/${process.env.VERSION}/${wabaId}/message_templates?language=pt_BR`
|
||||
);
|
||||
|
||||
return res.status(200).json(data);
|
||||
} catch (error) {
|
||||
return res
|
||||
.status(500)
|
||||
.json({ message: "Não foi possível baixar os templates!" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (medias) {
|
||||
await Promise.all(
|
||||
|
|
|
@ -14,6 +14,8 @@ export const wbotMonitorRemote = async (req: Request, res: Response): Promise<Re
|
|||
|
||||
const { action, whatsappId, reason } = req.body
|
||||
|
||||
console.log('action: ', action, ' | whatsappId: ', whatsappId, ' | reason: ', reason)
|
||||
|
||||
console.log('-----------> ACTION: ', req.body['action'])
|
||||
|
||||
const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true })
|
||||
|
|
|
@ -80,7 +80,10 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error on try update classification number from oficial whatsapp in WhatsappController.ts: ', error)
|
||||
console.log(
|
||||
"error on try update classification number from oficial whatsapp in WhatsappController.ts: ",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -244,7 +247,8 @@ export const weebhook = async (
|
|||
type = "chat";
|
||||
msg = {
|
||||
...msg,
|
||||
body: message.text.body // extract the message text from the webhook payload,
|
||||
body: message.text.body, // extract the message text from the webhook payload,
|
||||
type
|
||||
};
|
||||
} else {
|
||||
const mediaId = message[message.type].id;
|
||||
|
@ -265,6 +269,8 @@ export const weebhook = async (
|
|||
wbot = { ...wbot, media: { filename, mimetype } };
|
||||
}
|
||||
|
||||
msg = { ...msg, phoneNumberId: whatsapp.phoneNumberId };
|
||||
|
||||
console.log("from: ", contact_from);
|
||||
console.log("to: ", contact_to);
|
||||
console.log("msg type: ", type);
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { Op } from "sequelize";
|
||||
import { sub, subHours } from "date-fns";
|
||||
import Message from "../models/Message";
|
||||
import Ticket from "../models/Ticket";
|
||||
|
||||
async function checkLastClientMsg24hs(ticket: Ticket) {
|
||||
return await Message.findAll({
|
||||
attributes: ["createdAt", "body"],
|
||||
where: {
|
||||
contactId: ticket.contactId,
|
||||
phoneNumberId: ticket.phoneNumberId,
|
||||
fromMe: false,
|
||||
createdAt: {
|
||||
[Op.between]: [+subHours(new Date(), 24), +new Date()]
|
||||
}
|
||||
},
|
||||
order: [["createdAt", "DESC"]],
|
||||
limit: 1
|
||||
});
|
||||
}
|
||||
|
||||
export default checkLastClientMsg24hs;
|
|
@ -11,7 +11,8 @@ import whatsappOfficialAPI from "./WhatsappOfficialAPI";
|
|||
async function sendWhatsAppMessageOfficialAPI(
|
||||
ticket: Ticket,
|
||||
body: string,
|
||||
quotedMsgSerializedId?: any | undefined
|
||||
quotedMsgSerializedId?: any | undefined,
|
||||
_template?: any
|
||||
) {
|
||||
const { contactId, phoneNumberId } = ticket;
|
||||
|
||||
|
@ -22,13 +23,26 @@ async function sendWhatsAppMessageOfficialAPI(
|
|||
let data: any = {
|
||||
messaging_product: "whatsapp",
|
||||
recipient_type: "individual",
|
||||
to: number,
|
||||
to: number
|
||||
};
|
||||
|
||||
if (_template) {
|
||||
const { template } = _template;
|
||||
data = {
|
||||
...data,
|
||||
type: "template",
|
||||
template
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
...data,
|
||||
type: "text",
|
||||
text: {
|
||||
preview_url: true,
|
||||
body
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (quotedMsgSerializedId) {
|
||||
data = { ...data, context: { message_id: quotedMsgSerializedId.id } };
|
||||
|
@ -38,6 +52,8 @@ async function sendWhatsAppMessageOfficialAPI(
|
|||
return;
|
||||
}
|
||||
|
||||
console.log("SEND MESSAGE: ", JSON.stringify(data, null,2));
|
||||
|
||||
whatsappOfficialAPI
|
||||
.post(`/${process.env.VERSION}/${phoneNumberId}/messages`, data)
|
||||
.then(response => {
|
||||
|
|
|
@ -23,6 +23,7 @@ const isAuth = (req: Request, res: Response, next: NextFunction): void => {
|
|||
|
||||
try {
|
||||
const decoded = verify(token, authConfig.secret);
|
||||
|
||||
const { id, profile } = decoded as TokenPayload;
|
||||
|
||||
req.user = {
|
||||
|
|
|
@ -116,9 +116,10 @@ const SendWhatsAppMessage = async ({
|
|||
});
|
||||
} else if (whatsapps && whatsapps.length == 1) {
|
||||
await ticket.update({ whatsappId: whatsapps[0].id });
|
||||
}
|
||||
else{
|
||||
throw new Error('Sessão de Whatsapp desconectada! Entre em contato com o suporte.')
|
||||
} else {
|
||||
throw new Error(
|
||||
"Sessão de Whatsapp desconectada! Entre em contato com o suporte."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,7 +162,8 @@ const verifyMediaMessage = async (
|
|||
read: msg.fromMe,
|
||||
mediaUrl: media.filename,
|
||||
mediaType: media.mimetype.split("/")[0],
|
||||
quotedMsgId: quotedMsg
|
||||
quotedMsgId: quotedMsg,
|
||||
phoneNumberId: msg?.phoneNumberId
|
||||
};
|
||||
|
||||
|
||||
|
@ -443,6 +444,7 @@ const mediaTypeWhatsappOfficial = (mimetype: string): object => {
|
|||
const isValidMsg = (msg: any): boolean => {
|
||||
if (msg.from === "status@broadcast") return false;
|
||||
if (
|
||||
msg.type === "template" ||
|
||||
msg.type === "text" ||
|
||||
msg.type === "hsm" ||
|
||||
msg.type === "chat" ||
|
||||
|
|
|
@ -178,7 +178,7 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => {
|
|||
useEffect(() => {
|
||||
console.log('selectedWhatsId: ', selectedWhatsId)
|
||||
console.log('whatsQuee: ', whatsQueue)
|
||||
}, [whatsQueue])
|
||||
}, [whatsQueue, selectedWhatsId])
|
||||
|
||||
return (
|
||||
<Dialog open={modalOpen} onClose={handleClose} maxWidth="xs" scroll="paper" classes={{ paper: classes.paper }}>
|
||||
|
|
|
@ -1,47 +1,50 @@
|
|||
import React, { useState, useEffect, useContext, useRef } from "react";
|
||||
import "emoji-mart/css/emoji-mart.css";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Picker } from "emoji-mart";
|
||||
import MicRecorder from "mic-recorder-to-mp3";
|
||||
import clsx from "clsx";
|
||||
import React, { useState, useEffect, useContext, useRef } from "react"
|
||||
import "emoji-mart/css/emoji-mart.css"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { Picker } from "emoji-mart"
|
||||
import MicRecorder from "mic-recorder-to-mp3"
|
||||
import clsx from "clsx"
|
||||
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import InputBase from "@material-ui/core/InputBase";
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import { green } from "@material-ui/core/colors";
|
||||
import AttachFileIcon from "@material-ui/icons/AttachFile";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import MoreVert from "@material-ui/icons/MoreVert";
|
||||
import MoodIcon from "@material-ui/icons/Mood";
|
||||
import SendIcon from "@material-ui/icons/Send";
|
||||
import CancelIcon from "@material-ui/icons/Cancel";
|
||||
import ClearIcon from "@material-ui/icons/Clear";
|
||||
import MicIcon from "@material-ui/icons/Mic";
|
||||
import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline";
|
||||
import HighlightOffIcon from "@material-ui/icons/HighlightOff";
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import Paper from "@material-ui/core/Paper"
|
||||
import InputBase from "@material-ui/core/InputBase"
|
||||
import CircularProgress from "@material-ui/core/CircularProgress"
|
||||
import { green } from "@material-ui/core/colors"
|
||||
import AttachFileIcon from "@material-ui/icons/AttachFile"
|
||||
import IconButton from "@material-ui/core/IconButton"
|
||||
import MoreVert from "@material-ui/icons/MoreVert"
|
||||
import MoodIcon from "@material-ui/icons/Mood"
|
||||
import SendIcon from "@material-ui/icons/Send"
|
||||
import CancelIcon from "@material-ui/icons/Cancel"
|
||||
import ClearIcon from "@material-ui/icons/Clear"
|
||||
import MicIcon from "@material-ui/icons/Mic"
|
||||
import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline"
|
||||
import HighlightOffIcon from "@material-ui/icons/HighlightOff"
|
||||
import {
|
||||
FormControlLabel,
|
||||
Hidden,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Switch,
|
||||
} from "@material-ui/core";
|
||||
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
|
||||
} from "@material-ui/core"
|
||||
import ClickAwayListener from "@material-ui/core/ClickAwayListener"
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
import api from "../../services/api";
|
||||
import RecordingTimer from "./RecordingTimer";
|
||||
import { ReplyMessageContext } from "../../context/ReplyingMessage/ReplyingMessageContext";
|
||||
import { AuthContext } from "../../context/Auth/AuthContext";
|
||||
import { useLocalStorage } from "../../hooks/useLocalStorage";
|
||||
import toastError from "../../errors/toastError";
|
||||
import { i18n } from "../../translate/i18n"
|
||||
import api from "../../services/api"
|
||||
import RecordingTimer from "./RecordingTimer"
|
||||
import { ReplyMessageContext } from "../../context/ReplyingMessage/ReplyingMessageContext"
|
||||
import { AuthContext } from "../../context/Auth/AuthContext"
|
||||
import { useLocalStorage } from "../../hooks/useLocalStorage"
|
||||
import toastError from "../../errors/toastError"
|
||||
|
||||
// import TicketsManager from "../../components/TicketsManager/";
|
||||
|
||||
import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption";
|
||||
import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption"
|
||||
import ModalTemplate from "../ModalTemplate"
|
||||
|
||||
const Mp3Recorder = new MicRecorder({ bitRate: 128 });
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
const Mp3Recorder = new MicRecorder({ bitRate: 128 })
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
mainWrapper: {
|
||||
|
@ -203,78 +206,83 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
}))
|
||||
|
||||
const MessageInput = ({ ticketStatus }) => {
|
||||
|
||||
const { tabOption, setTabOption } = useContext(TabTicketContext);
|
||||
const { tabOption, setTabOption } = useContext(TabTicketContext)
|
||||
|
||||
const classes = useStyles();
|
||||
const { ticketId } = useParams();
|
||||
const classes = useStyles()
|
||||
const { ticketId } = useParams()
|
||||
|
||||
const [medias, setMedias] = useState([]);
|
||||
const [inputMessage, setInputMessage] = useState("");
|
||||
const [showEmoji, setShowEmoji] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [recording, setRecording] = useState(false);
|
||||
const [quickAnswers, setQuickAnswer] = useState([]);
|
||||
const [typeBar, setTypeBar] = useState(false);
|
||||
const inputRef = useRef();
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const { setReplyingMessage, replyingMessage } = useContext(ReplyMessageContext);
|
||||
const { user } = useContext(AuthContext);
|
||||
const [medias, setMedias] = useState([])
|
||||
const [inputMessage, setInputMessage] = useState("")
|
||||
const [showEmoji, setShowEmoji] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [recording, setRecording] = useState(false)
|
||||
const [quickAnswers, setQuickAnswer] = useState([])
|
||||
const [typeBar, setTypeBar] = useState(false)
|
||||
const inputRef = useRef()
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
const { setReplyingMessage, replyingMessage } = useContext(ReplyMessageContext)
|
||||
const { user } = useContext(AuthContext)
|
||||
const [templates, setTemplates] = useState(null)
|
||||
const [params, setParams] = useState(null)
|
||||
|
||||
const [signMessage, setSignMessage] = useLocalStorage("signOption", true)
|
||||
|
||||
const isRun = useRef(false)
|
||||
|
||||
const [signMessage, setSignMessage] = useLocalStorage("signOption", true);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current.focus();
|
||||
}, [replyingMessage]);
|
||||
inputRef.current.focus()
|
||||
}, [replyingMessage])
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.focus()
|
||||
return () => {
|
||||
setInputMessage("");
|
||||
setShowEmoji(false);
|
||||
setMedias([]);
|
||||
setReplyingMessage(null);
|
||||
};
|
||||
}, [ticketId, setReplyingMessage]);
|
||||
setInputMessage("")
|
||||
setShowEmoji(false)
|
||||
setMedias([])
|
||||
setReplyingMessage(null)
|
||||
}
|
||||
}, [ticketId, setReplyingMessage])
|
||||
|
||||
const handleChangeInput = (e) => {
|
||||
setInputMessage(e.target.value);
|
||||
handleLoadQuickAnswer(e.target.value);
|
||||
};
|
||||
setInputMessage(e.target.value)
|
||||
handleLoadQuickAnswer(e.target.value)
|
||||
}
|
||||
|
||||
const handleQuickAnswersClick = (value) => {
|
||||
setInputMessage(value);
|
||||
setTypeBar(false);
|
||||
};
|
||||
setInputMessage(value)
|
||||
setTypeBar(false)
|
||||
}
|
||||
|
||||
const handleAddEmoji = (e) => {
|
||||
let emoji = e.native;
|
||||
setInputMessage((prevState) => prevState + emoji);
|
||||
};
|
||||
let emoji = e.native
|
||||
setInputMessage((prevState) => prevState + emoji)
|
||||
}
|
||||
|
||||
const handleChangeMedias = (e) => {
|
||||
if (!e.target.files) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const selectedMedias = Array.from(e.target.files);
|
||||
setMedias(selectedMedias);
|
||||
};
|
||||
const selectedMedias = Array.from(e.target.files)
|
||||
setMedias(selectedMedias)
|
||||
}
|
||||
|
||||
const handleInputPaste = (e) => {
|
||||
if (e.clipboardData.files[0]) {
|
||||
|
||||
console.log('clipboardData: ', e.clipboardData.files[0])
|
||||
setMedias([e.clipboardData.files[0]]);
|
||||
setMedias([e.clipboardData.files[0]])
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadMedia = async (e) => {
|
||||
setLoading(true);
|
||||
e.preventDefault();
|
||||
setLoading(true)
|
||||
e.preventDefault()
|
||||
|
||||
|
||||
|
||||
|
@ -282,28 +290,32 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
setTabOption('open')
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("fromMe", true);
|
||||
const formData = new FormData()
|
||||
formData.append("fromMe", true)
|
||||
medias.forEach((media) => {
|
||||
formData.append("medias", media);
|
||||
formData.append("body", media.name);
|
||||
});
|
||||
formData.append("medias", media)
|
||||
formData.append("body", media.name)
|
||||
})
|
||||
|
||||
try {
|
||||
await api.post(`/messages/${ticketId}`, formData);
|
||||
const { data } = await api.post(`/messages/${ticketId}`, formData)
|
||||
|
||||
console.log('DATA FROM SEND MESSAGE MEDIA: ', data)
|
||||
|
||||
} catch (err) {
|
||||
toastError(err);
|
||||
toastError(err)
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
setMedias([]);
|
||||
};
|
||||
setLoading(false)
|
||||
setMedias([])
|
||||
}
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
if (inputMessage.trim() === "") return;
|
||||
setLoading(true);
|
||||
const handleSendMessage = async (templateParams = null) => {
|
||||
|
||||
console.log('templateParams: ', templateParams, ' | inputMessage: ', inputMessage)
|
||||
|
||||
if (inputMessage.trim() === "") return
|
||||
setLoading(true)
|
||||
|
||||
if (tabOption === 'search') {
|
||||
setTabOption('open')
|
||||
|
@ -313,61 +325,119 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
read: 1,
|
||||
fromMe: true,
|
||||
mediaUrl: "",
|
||||
body: signMessage
|
||||
body: (signMessage && !templateParams)
|
||||
? `*${user?.name}:*\n${inputMessage.trim()}`
|
||||
: inputMessage.trim(),
|
||||
quotedMsg: replyingMessage,
|
||||
};
|
||||
params: templateParams
|
||||
}
|
||||
try {
|
||||
|
||||
// console.log('message: ', message)
|
||||
await api.post(`/messages/${ticketId}`, message);
|
||||
} catch (err) {
|
||||
toastError(err);
|
||||
const { data } = await api.post(`/messages/${ticketId}`, message)
|
||||
setParams(null)
|
||||
if (data && data?.data && Array.isArray(data.data)) {
|
||||
setTemplates(data.data)
|
||||
}
|
||||
|
||||
setInputMessage("");
|
||||
setShowEmoji(false);
|
||||
setLoading(false);
|
||||
setReplyingMessage(null);
|
||||
};
|
||||
} catch (err) {
|
||||
toastError(err)
|
||||
}
|
||||
|
||||
setInputMessage("")
|
||||
setShowEmoji(false)
|
||||
setLoading(false)
|
||||
setReplyingMessage(null)
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!params) return
|
||||
|
||||
const body_params = params.find(p => p?.type === 'BODY')
|
||||
|
||||
let { text } = body_params
|
||||
|
||||
console.log('PARAMS FROM MESSAGE INPUT: ', params, ' | text: ', text)
|
||||
|
||||
let body = text.match(/{{\d+}}/g)
|
||||
|
||||
if (body && body.length > 0) {
|
||||
|
||||
const { parameters } = body_params
|
||||
|
||||
for (const key in parameters) {
|
||||
if (!isNaN(key)) {
|
||||
const { index, text: body_text } = parameters[key]
|
||||
text = text.replace(`{{${index}}}`, body_text)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
console.log('NEW TEXT: ', text)
|
||||
setInputMessage(text)
|
||||
|
||||
}, [params])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (params) {
|
||||
handleSendMessage(params)
|
||||
}
|
||||
|
||||
}, [inputMessage, params])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!templates) return
|
||||
|
||||
return render(<ModalTemplate
|
||||
modal_header={'Escolha um template para iniciar o Atendimento'}
|
||||
func={setParams}
|
||||
templates={templates.map(({ id, name, components, language, }) => {
|
||||
return { id, name, components, language, }
|
||||
})}
|
||||
ticketId={ticketId}
|
||||
/>)
|
||||
|
||||
}, [templates])
|
||||
|
||||
const handleStartRecording = async () => {
|
||||
|
||||
setLoading(true);
|
||||
setLoading(true)
|
||||
try {
|
||||
await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
await Mp3Recorder.start();
|
||||
setRecording(true);
|
||||
setLoading(false);
|
||||
await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
await Mp3Recorder.start()
|
||||
setRecording(true)
|
||||
setLoading(false)
|
||||
} catch (err) {
|
||||
toastError(err);
|
||||
setLoading(false);
|
||||
toastError(err)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadQuickAnswer = async (value) => {
|
||||
if (value && value.indexOf("/") === 0) {
|
||||
try {
|
||||
const { data } = await api.get("/quickAnswers/", {
|
||||
params: { searchParam: inputMessage.substring(1) },
|
||||
});
|
||||
setQuickAnswer(data.quickAnswers);
|
||||
})
|
||||
setQuickAnswer(data.quickAnswers)
|
||||
if (data.quickAnswers.length > 0) {
|
||||
setTypeBar(true);
|
||||
setTypeBar(true)
|
||||
} else {
|
||||
setTypeBar(false);
|
||||
setTypeBar(false)
|
||||
}
|
||||
} catch (err) {
|
||||
setTypeBar(false);
|
||||
setTypeBar(false)
|
||||
}
|
||||
} else {
|
||||
setTypeBar(false);
|
||||
setTypeBar(false)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadAudio = async () => {
|
||||
setLoading(true);
|
||||
setLoading(true)
|
||||
|
||||
|
||||
|
||||
|
@ -376,45 +446,45 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const [, blob] = await Mp3Recorder.stop().getMp3();
|
||||
const [, blob] = await Mp3Recorder.stop().getMp3()
|
||||
if (blob.size < 10000) {
|
||||
setLoading(false);
|
||||
setRecording(false);
|
||||
return;
|
||||
setLoading(false)
|
||||
setRecording(false)
|
||||
return
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
const filename = `${new Date().getTime()}.mp3`;
|
||||
formData.append("medias", blob, filename);
|
||||
formData.append("body", filename);
|
||||
formData.append("fromMe", true);
|
||||
const formData = new FormData()
|
||||
const filename = `${new Date().getTime()}.mp3`
|
||||
formData.append("medias", blob, filename)
|
||||
formData.append("body", filename)
|
||||
formData.append("fromMe", true)
|
||||
formData.append("mic_audio", true)
|
||||
|
||||
await api.post(`/messages/${ticketId}`, formData);
|
||||
await api.post(`/messages/${ticketId}`, formData)
|
||||
} catch (err) {
|
||||
toastError(err);
|
||||
toastError(err)
|
||||
}
|
||||
|
||||
setRecording(false);
|
||||
setLoading(false);
|
||||
};
|
||||
setRecording(false)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const handleCancelAudio = async () => {
|
||||
try {
|
||||
await Mp3Recorder.stop().getMp3();
|
||||
setRecording(false);
|
||||
await Mp3Recorder.stop().getMp3()
|
||||
setRecording(false)
|
||||
} catch (err) {
|
||||
toastError(err);
|
||||
toastError(err)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenMenuClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
|
||||
const handleMenuItemClick = (event) => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const renderReplyingMessage = (message) => {
|
||||
return (
|
||||
|
@ -443,8 +513,8 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
<ClearIcon className={classes.sendMessageIcons} />
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
if (medias.length > 0)
|
||||
return (
|
||||
|
@ -476,7 +546,7 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
<SendIcon className={classes.sendMessageIcons} />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
);
|
||||
)
|
||||
else {
|
||||
return (
|
||||
<Paper square elevation={0} className={classes.mainWrapper}>
|
||||
|
@ -530,7 +600,7 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
size="small"
|
||||
checked={signMessage}
|
||||
onChange={(e) => {
|
||||
setSignMessage(e.target.checked);
|
||||
setSignMessage(e.target.checked)
|
||||
}}
|
||||
name="showAllTickets"
|
||||
color="primary"
|
||||
|
@ -592,7 +662,7 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
size="small"
|
||||
checked={signMessage}
|
||||
onChange={(e) => {
|
||||
setSignMessage(e.target.checked);
|
||||
setSignMessage(e.target.checked)
|
||||
}}
|
||||
name="showAllTickets"
|
||||
color="primary"
|
||||
|
@ -605,8 +675,8 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
<div className={classes.messageInputWrapper}>
|
||||
<InputBase
|
||||
inputRef={(input) => {
|
||||
input && input.focus();
|
||||
input && (inputRef.current = input);
|
||||
input && input.focus()
|
||||
input && (inputRef.current = input)
|
||||
}}
|
||||
className={classes.messageInput}
|
||||
placeholder={
|
||||
|
@ -621,12 +691,12 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
onChange={handleChangeInput}
|
||||
disabled={recording || loading || ticketStatus !== "open"}
|
||||
onPaste={(e) => {
|
||||
ticketStatus === "open" && handleInputPaste(e);
|
||||
ticketStatus === "open" && handleInputPaste(e)
|
||||
}}
|
||||
onKeyPress={(e) => {
|
||||
if (loading || e.shiftKey) return;
|
||||
if (loading || e.shiftKey) return
|
||||
else if (e.key === "Enter") {
|
||||
handleSendMessage();
|
||||
handleSendMessage()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -643,7 +713,7 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
{`${value.shortcut} - ${value.message}`}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
|
@ -699,8 +769,8 @@ const MessageInput = ({ ticketStatus }) => {
|
|||
)}
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default MessageInput;
|
||||
export default MessageInput
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
|
||||
import React, { useState, useEffect, useRef, } from 'react'
|
||||
import Button from '@mui/material/Button'
|
||||
import Dialog from '@mui/material/Dialog'
|
||||
import DialogActions from '@mui/material/DialogActions'
|
||||
import DialogContent from '@mui/material/DialogContent'
|
||||
import DialogContentText from '@mui/material/DialogContentText'
|
||||
import DialogTitle from '@mui/material/DialogTitle'
|
||||
import SelectField from "../Report/SelectField"
|
||||
|
||||
import TextField from '@mui/material/TextField'
|
||||
|
||||
|
||||
|
||||
const ModalTemplate = ({ templates, modal_header, func }) => {
|
||||
|
||||
templates = [{}, ...templates]
|
||||
|
||||
const [open, setOpen] = useState(true)
|
||||
const [scroll, /*setScroll*/] = useState('body')
|
||||
const [templateId, setTemplateId] = useState(null)
|
||||
const [templateComponents, setTemplateComponents] = useState(null)
|
||||
const [language, setLanguage] = useState(null)
|
||||
const [params, setParams] = useState([])
|
||||
|
||||
const handleCancel = (event, reason) => {
|
||||
|
||||
if (reason && reason === "backdropClick")
|
||||
return
|
||||
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleChatEnd = () => {
|
||||
|
||||
console.log('PARAMS TO SEND TO MESSAGE INPUT: ', params)
|
||||
func(params)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const descriptionElementRef = useRef(null)
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const { current: descriptionElement } = descriptionElementRef
|
||||
if (descriptionElement !== null) {
|
||||
descriptionElement.focus()
|
||||
}
|
||||
}
|
||||
}, [open])
|
||||
|
||||
|
||||
// Get from child 1
|
||||
const changedTextFieldSelect = (data) => {
|
||||
setTemplateId(data)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const index = templates.findIndex(t => t.id === templateId)
|
||||
setParams([])
|
||||
if (index !== -1) {
|
||||
|
||||
const { components, language, name } = templates[index]
|
||||
|
||||
setParams((params) => [...params, { 'template_name': name }])
|
||||
|
||||
const buttons = components.find(c => c.type === 'BUTTONS')
|
||||
|
||||
if (buttons) {
|
||||
handleButtons(buttons?.buttons)
|
||||
}
|
||||
|
||||
setTemplateComponents(components)
|
||||
setLanguage(language)
|
||||
}
|
||||
else {
|
||||
setTemplateComponents(null)
|
||||
setLanguage(null)
|
||||
}
|
||||
|
||||
}, [templateId])
|
||||
|
||||
const handleButtons = (buttons) => {
|
||||
|
||||
let buttonsParams = {
|
||||
type: 'BUTTONS',
|
||||
parameters: []
|
||||
}
|
||||
|
||||
if (buttons && buttons.length > 0) {
|
||||
for (let i in buttons) {
|
||||
const { text, type: sub_type } = buttons[i]
|
||||
buttonsParams.parameters.push({ sub_type, text, index: i, })
|
||||
}
|
||||
}
|
||||
|
||||
setParams((params) => [...params, buttonsParams])
|
||||
|
||||
}
|
||||
|
||||
const handleTextChange = (value, index, type, text, language,) => {
|
||||
|
||||
if (!params) return
|
||||
|
||||
setParams((params) => {
|
||||
|
||||
const _index = params.findIndex(({ type }) => type === 'BODY')
|
||||
|
||||
if (_index !== -1) {
|
||||
|
||||
const indexParameter = params[_index].parameters.findIndex((param) => param.index === index)
|
||||
|
||||
if (indexParameter !== -1) {
|
||||
params[_index].parameters[indexParameter] = { ...params[_index].parameters[indexParameter], type: 'text', text: value, index }
|
||||
}
|
||||
else {
|
||||
params[_index].parameters = [...params[_index].parameters, { type: 'text', text: value, index }]
|
||||
}
|
||||
|
||||
return params
|
||||
|
||||
}
|
||||
return [...params, { type, text, language, parameters: [{ type: 'text', text: value, index }] }]
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log('---------> PARAMS: ', params)
|
||||
}, [params])
|
||||
|
||||
const dinamicTextField = (replicateItems, func, type, text, language) => {
|
||||
|
||||
let textFields = Array.from({ length: replicateItems }, (_, index) => index)
|
||||
|
||||
return textFields.map((t) => {
|
||||
return <TextField
|
||||
key={t}
|
||||
label={`{{${t + 1}}}`}
|
||||
variant="outlined"
|
||||
style={{ margin: '4px', }} // Adjust the height as needed
|
||||
onChange={(e) => func(e.target.value, (t + 1), type, text, language)}
|
||||
/>
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleCancel}
|
||||
// fullWidth={true}
|
||||
// maxWidth={true}
|
||||
disableEscapeKeyDown
|
||||
|
||||
scroll={scroll}
|
||||
aria-labelledby="scroll-dialog-title"
|
||||
aria-describedby="scroll-dialog-description"
|
||||
>
|
||||
|
||||
<DialogTitle id="scroll-dialog-title">{modal_header}</DialogTitle>
|
||||
<DialogContent dividers={scroll === 'body'}>
|
||||
|
||||
<DialogContentText
|
||||
id="scroll-dialog-description"
|
||||
ref={descriptionElementRef}
|
||||
tabIndex={-1}
|
||||
>
|
||||
|
||||
</DialogContentText>
|
||||
|
||||
<>
|
||||
|
||||
<SelectField func={changedTextFieldSelect}
|
||||
emptyField={false}
|
||||
textBoxFieldSelected={'1'}
|
||||
header={'Selecione um template'}
|
||||
currencies={templates.map((template,) => {
|
||||
const { name, id } = template
|
||||
return { 'value': id, 'label': name }
|
||||
})} />
|
||||
|
||||
{templateComponents &&
|
||||
templateComponents.map((components,) => {
|
||||
const { type, format, text, buttons } = components
|
||||
|
||||
let body_params = 0
|
||||
|
||||
if (type === 'BODY') {
|
||||
body_params = text.match(/{{\d+}}/g)
|
||||
}
|
||||
|
||||
const titleCss = {
|
||||
margin: 0,
|
||||
fontSize: 12
|
||||
}
|
||||
const valueCss = { margin: 0, padding: 0, 'marginBottom': '15px', fontSize: 12 }
|
||||
const valueCssButton = { margin: 0, padding: 0, fontSize: 11 }
|
||||
|
||||
return <div key={text}>
|
||||
{type && <strong style={titleCss}>{type}</strong>}
|
||||
{format && format !== 'TEXT' && <p style={valueCss}>TYPE {format}</p>}
|
||||
{text &&
|
||||
<div style={{ margin: 0, padding: 0, 'marginBottom': '15px' }}>
|
||||
<p style={{ margin: 0, padding: 0, fontSize: 12 }}>{text}</p>
|
||||
{type && (type === 'BODY') && dinamicTextField(body_params.length, handleTextChange, type, text, language)}
|
||||
</div>}
|
||||
{buttons && <div>{buttons.map((b) => {
|
||||
const { type, text, url } = b
|
||||
return <div style={{ margin: 0, padding: 0, 'marginBottom': '10px' }}>
|
||||
{type && <p style={valueCssButton}>TYPE {type}</p>}
|
||||
{text && <p style={valueCssButton}>{text}</p>}
|
||||
{url && <p style={valueCssButton}>{url}</p>}
|
||||
</div>
|
||||
})} </div>}
|
||||
|
||||
</div>
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
</>
|
||||
|
||||
|
||||
|
||||
</DialogContent>
|
||||
|
||||
|
||||
<DialogActions>
|
||||
<div style={{ marginRight: '50px' }}>
|
||||
<Button onClick={handleCancel}>Cancelar</Button>
|
||||
</div>
|
||||
<Button onClick={handleChatEnd}>Ok</Button>
|
||||
</DialogActions>
|
||||
</Dialog >
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalTemplate
|
|
@ -1,27 +1,27 @@
|
|||
import React, { useState, useEffect, useRef, useContext } from "react";
|
||||
import React, { useState, useEffect, useRef, useContext } from "react"
|
||||
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { parseISO, format, isSameDay } from "date-fns";
|
||||
import clsx from "clsx";
|
||||
import { useHistory, useParams } from "react-router-dom"
|
||||
import { parseISO, format, isSameDay } from "date-fns"
|
||||
import clsx from "clsx"
|
||||
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { green } from "@material-ui/core/colors";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Avatar from "@material-ui/core/Avatar";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { green } from "@material-ui/core/colors"
|
||||
import ListItem from "@material-ui/core/ListItem"
|
||||
import ListItemText from "@material-ui/core/ListItemText"
|
||||
import ListItemAvatar from "@material-ui/core/ListItemAvatar"
|
||||
import Typography from "@material-ui/core/Typography"
|
||||
import Avatar from "@material-ui/core/Avatar"
|
||||
import Divider from "@material-ui/core/Divider"
|
||||
import Badge from "@material-ui/core/Badge"
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
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";
|
||||
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 useStyles = makeStyles(theme => ({
|
||||
ticket: {
|
||||
|
@ -99,46 +99,46 @@ const useStyles = makeStyles(theme => ({
|
|||
top: "0%",
|
||||
left: "0%",
|
||||
},
|
||||
}));
|
||||
}))
|
||||
|
||||
const TicketListItem = ({ ticket }) => {
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { ticketId } = useParams();
|
||||
const isMounted = useRef(true);
|
||||
const { user } = useContext(AuthContext);
|
||||
const classes = useStyles()
|
||||
const history = useHistory()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { ticketId } = useParams()
|
||||
const isMounted = useRef(true)
|
||||
const { user } = useContext(AuthContext)
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
isMounted.current = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleAcepptTicket = async id => {
|
||||
setLoading(true);
|
||||
setLoading(true)
|
||||
try {
|
||||
|
||||
await api.put(`/tickets/${id}`, {
|
||||
status: "open",
|
||||
userId: user?.id,
|
||||
});
|
||||
})
|
||||
} catch (err) {
|
||||
setLoading(false);
|
||||
toastError(err);
|
||||
setLoading(false)
|
||||
toastError(err)
|
||||
}
|
||||
if (isMounted.current) {
|
||||
setLoading(false);
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
|
||||
|
||||
history.push(`/tickets/${id}`);
|
||||
};
|
||||
history.push(`/tickets/${id}`)
|
||||
}
|
||||
|
||||
const handleSelectTicket = id => {
|
||||
history.push(`/tickets/${id}`);
|
||||
};
|
||||
history.push(`/tickets/${id}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment key={ticket.id}>
|
||||
|
@ -146,8 +146,8 @@ const TicketListItem = ({ ticket }) => {
|
|||
dense
|
||||
button
|
||||
onClick={e => {
|
||||
if (ticket.status === "pending") return;
|
||||
handleSelectTicket(ticket.id);
|
||||
if (ticket.status === "pending") return
|
||||
handleSelectTicket(ticket.id)
|
||||
}}
|
||||
selected={ticketId && +ticketId === ticket.id}
|
||||
className={clsx(classes.ticket, {
|
||||
|
@ -193,6 +193,7 @@ const TicketListItem = ({ ticket }) => {
|
|||
variant="body2"
|
||||
color="textSecondary"
|
||||
>
|
||||
{ticket?.phoneNumberId && <span style={{ 'fontWeight': 'bold' }}>Oficial</span>}{" "}
|
||||
{isSameDay(parseISO(ticket.updatedAt), new Date()) ? (
|
||||
<>{format(parseISO(ticket.updatedAt), "HH:mm")}</>
|
||||
) : (
|
||||
|
@ -251,7 +252,7 @@ const TicketListItem = ({ ticket }) => {
|
|||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default TicketListItem;
|
||||
export default TicketListItem
|
||||
|
|
Loading…
Reference in New Issue