Atualização basica para envio de template do Whatsapp cloud API

pull/21/head
adriano 2023-09-19 09:41:15 -03:00
parent 72c489a27b
commit c151473d52
12 changed files with 667 additions and 221 deletions

View File

@ -18,7 +18,9 @@ import {
} from "../services/WbotServices/wbotMessageListener";
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
import sendWhatsAppMessageOfficialAPI from "../helpers/sendWhatsAppMessageOfficialAPI";
import Whatsapp from "../models/Whatsapp";
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> => {
@ -40,18 +43,98 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
pageNumber,
ticketId
});
return res.json({ count, messages, ticket, hasMore });
};
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(
@ -70,7 +153,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
media:`,
media,
"\n"
);
);
await SendWhatsAppMedia({ media, ticket, mic_audio });
})
);

View File

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

View File

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

View File

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

View File

@ -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,14 +23,27 @@ async function sendWhatsAppMessageOfficialAPI(
let data: any = {
messaging_product: "whatsapp",
recipient_type: "individual",
to: number,
type: "text",
text: {
preview_url: true,
body
}
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 => {

View File

@ -22,7 +22,8 @@ const isAuth = (req: Request, res: Response, next: NextFunction): void => {
const [, token] = authHeader.split(" ");
try {
const decoded = verify(token, authConfig.secret);
const decoded = verify(token, authConfig.secret);
const { id, profile } = decoded as TokenPayload;
req.user = {

View File

@ -81,7 +81,7 @@ const SendWhatsAppMessage = async ({
}
if (!listWhatsapp) {
listWhatsapp = await ListWhatsAppsNumber(ticket.whatsappId, "CONNECTED");
listWhatsapp = await ListWhatsAppsNumber(ticket.whatsappId, "CONNECTED");
}
if (
@ -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."
);
}
}
}

View File

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

View File

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

View File

@ -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,107 +206,116 @@ 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()
if (tabOption === 'search') {
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,108 +325,166 @@ 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);
const { data } = await api.post(`/messages/${ticketId}`, message)
setParams(null)
if (data && data?.data && Array.isArray(data.data)) {
setTemplates(data.data)
}
} catch (err) {
toastError(err);
toastError(err)
}
setInputMessage("");
setShowEmoji(false);
setLoading(false);
setReplyingMessage(null);
};
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)
if (tabOption === 'search') {
setTabOption('open')
}
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

View File

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

View File

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