mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-20 20:59:16 +00:00
Merge pull request #342 from dantecvip/master
Fix error CONTACT_NOT_FIND in older VCards and Add Receive Localizations Cards and Others Features
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import AppError from "../../errors/AppError";
|
import AppError from "../../errors/AppError";
|
||||||
import Contact from "../../models/Contact";
|
import Contact from "../../models/Contact";
|
||||||
|
import CreateContactService from "./CreateContactService";
|
||||||
|
|
||||||
interface ExtraInfo {
|
interface ExtraInfo {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -20,10 +21,18 @@ const GetContactService = async ({ name, number }: Request): Promise<Contact> =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!numberExists) {
|
if (!numberExists) {
|
||||||
throw new AppError("CONTACT_NOT_FIND");
|
const contact = await CreateContactService({
|
||||||
|
name,
|
||||||
|
number,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (contact == null)
|
||||||
|
throw new AppError("CONTACT_NOT_FIND")
|
||||||
|
else
|
||||||
|
return contact
|
||||||
}
|
}
|
||||||
|
|
||||||
return numberExists;
|
return numberExists
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GetContactService;
|
export default GetContactService;
|
||||||
@@ -2,6 +2,7 @@ import { join } from "path";
|
|||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import { writeFile } from "fs";
|
import { writeFile } from "fs";
|
||||||
import * as Sentry from "@sentry/node";
|
import * as Sentry from "@sentry/node";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Contact as WbotContact,
|
Contact as WbotContact,
|
||||||
Message as WbotMessage,
|
Message as WbotMessage,
|
||||||
@@ -114,6 +115,10 @@ const verifyMessage = async (
|
|||||||
ticket: Ticket,
|
ticket: Ticket,
|
||||||
contact: Contact
|
contact: Contact
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
|
if (msg.type === 'location')
|
||||||
|
msg = prepareLocation(msg);
|
||||||
|
|
||||||
const quotedMsg = await verifyQuotedMessage(msg);
|
const quotedMsg = await verifyQuotedMessage(msg);
|
||||||
const messageData = {
|
const messageData = {
|
||||||
id: msg.id.id,
|
id: msg.id.id,
|
||||||
@@ -126,11 +131,21 @@ const verifyMessage = async (
|
|||||||
quotedMsgId: quotedMsg?.id
|
quotedMsgId: quotedMsg?.id
|
||||||
};
|
};
|
||||||
|
|
||||||
await ticket.update({ lastMessage: msg.body });
|
await ticket.update({ lastMessage: msg.type === "location" ? msg.location.description ? "Localization - " + msg.location.description.split('\\n')[0] : "Localization" : msg.body });
|
||||||
|
|
||||||
await CreateMessageService({ messageData });
|
await CreateMessageService({ messageData });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const prepareLocation = (msg: WbotMessage): WbotMessage => {
|
||||||
|
let gmapsUrl = "https://maps.google.com/maps?q=" + msg.location.latitude + "%2C" + msg.location.longitude + "&z=17&hl=pt-BR";
|
||||||
|
|
||||||
|
msg.body = "data:image/png;base64," + msg.body + "|" + gmapsUrl;
|
||||||
|
|
||||||
|
msg.body += "|" + (msg.location.description ? msg.location.description : (msg.location.latitude + ", " + msg.location.longitude))
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
};
|
||||||
|
|
||||||
const verifyQueue = async (
|
const verifyQueue = async (
|
||||||
wbot: Session,
|
wbot: Session,
|
||||||
msg: WbotMessage,
|
msg: WbotMessage,
|
||||||
@@ -199,7 +214,8 @@ const isValidMsg = (msg: WbotMessage): boolean => {
|
|||||||
msg.type === "document" ||
|
msg.type === "document" ||
|
||||||
msg.type === "vcard" ||
|
msg.type === "vcard" ||
|
||||||
//msg.type === "multi_vcard" ||
|
//msg.type === "multi_vcard" ||
|
||||||
msg.type === "sticker"
|
msg.type === "sticker" ||
|
||||||
|
msg.type === "location"
|
||||||
)
|
)
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
@@ -225,13 +241,9 @@ const handleMessage = async (
|
|||||||
// media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc"
|
// media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc"
|
||||||
// in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true"
|
// in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true"
|
||||||
|
|
||||||
if (
|
if (!msg.hasMedia && msg.type !== "location" && msg.type !== "chat" && msg.type !== "vcard"
|
||||||
!msg.hasMedia &&
|
|
||||||
msg.type !== "chat" &&
|
|
||||||
msg.type !== "vcard"
|
|
||||||
//&& msg.type !== "multi_vcard"
|
//&& msg.type !== "multi_vcard"
|
||||||
)
|
) return;
|
||||||
return;
|
|
||||||
|
|
||||||
msgContact = await wbot.getContactById(msg.to);
|
msgContact = await wbot.getContactById(msg.to);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
REACT_APP_BACKEND_URL = http://localhost:8080/
|
REACT_APP_BACKEND_URL = http://localhost:8080/
|
||||||
|
REACT_APP_HOURS_CLOSE_TICKETS_AUTO =
|
||||||
53
frontend/src/components/LocationPreview/index.js
Normal file
53
frontend/src/components/LocationPreview/index.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import toastError from "../../errors/toastError";
|
||||||
|
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import Grid from "@material-ui/core/Grid";
|
||||||
|
|
||||||
|
import { Button, Divider, } from "@material-ui/core";
|
||||||
|
|
||||||
|
const LocationPreview = ({ image, link, description }) => {
|
||||||
|
useEffect(() => {}, [image, link, description]);
|
||||||
|
|
||||||
|
const handleLocation = async() => {
|
||||||
|
try {
|
||||||
|
window.open(link);
|
||||||
|
} catch (err) {
|
||||||
|
toastError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{
|
||||||
|
minWidth: "250px",
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<div style={{ float: "left" }}>
|
||||||
|
<img src={image} onClick={handleLocation} style={{ width: "100px" }} />
|
||||||
|
</div>
|
||||||
|
{ description && (
|
||||||
|
<div style={{ display: "flex", flexWrap: "wrap" }}>
|
||||||
|
<Typography style={{ marginTop: "12px", marginLeft: "15px", marginRight: "15px", float: "left" }} variant="subtitle1" color="primary" gutterBottom>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: description.replace('\\n', '<br />') }}></div>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div style={{ display: "block", content: "", clear: "both" }}></div>
|
||||||
|
<div>
|
||||||
|
<Divider />
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
color="primary"
|
||||||
|
onClick={handleLocation}
|
||||||
|
disabled={!link}
|
||||||
|
>Visualizar</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LocationPreview;
|
||||||
@@ -153,10 +153,13 @@ const MarkdownWrapper = ({ children }) => {
|
|||||||
const boldRegex = /\*(.*?)\*/g;
|
const boldRegex = /\*(.*?)\*/g;
|
||||||
const tildaRegex = /~(.*?)~/g;
|
const tildaRegex = /~(.*?)~/g;
|
||||||
|
|
||||||
if(children.includes('BEGIN:VCARD'))
|
if(children && children.includes('BEGIN:VCARD'))
|
||||||
//children = "Diga olá ao seu novo contato clicando em *conversar*!";
|
//children = "Diga olá ao seu novo contato clicando em *conversar*!";
|
||||||
children = null;
|
children = null;
|
||||||
|
|
||||||
|
if(children && children.includes('data:image/'))
|
||||||
|
children = null;
|
||||||
|
|
||||||
if (children && boldRegex.test(children)) {
|
if (children && boldRegex.test(children)) {
|
||||||
children = children.replace(boldRegex, "**$1**");
|
children = children.replace(boldRegex, "**$1**");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
|
|
||||||
import MarkdownWrapper from "../MarkdownWrapper";
|
import MarkdownWrapper from "../MarkdownWrapper";
|
||||||
import VcardPreview from "../VcardPreview";
|
import VcardPreview from "../VcardPreview";
|
||||||
|
import LocationPreview from "../LocationPreview";
|
||||||
import ModalImageCors from "../ModalImageCors";
|
import ModalImageCors from "../ModalImageCors";
|
||||||
import MessageOptionsMenu from "../MessageOptionsMenu";
|
import MessageOptionsMenu from "../MessageOptionsMenu";
|
||||||
import whatsBackground from "../../assets/wa-background.png";
|
import whatsBackground from "../../assets/wa-background.png";
|
||||||
@@ -414,7 +415,19 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const checkMessageMedia = (message) => {
|
const checkMessageMedia = (message) => {
|
||||||
if (message.mediaType === "vcard") {
|
if(message.mediaType === "location" && message.body.split('|').length >= 2) {
|
||||||
|
let locationParts = message.body.split('|')
|
||||||
|
let imageLocation = locationParts[0]
|
||||||
|
let linkLocation = locationParts[1]
|
||||||
|
|
||||||
|
let descriptionLocation = null
|
||||||
|
|
||||||
|
if(locationParts.length > 2)
|
||||||
|
descriptionLocation = message.body.split('|')[2]
|
||||||
|
|
||||||
|
return <LocationPreview image={imageLocation} link={linkLocation} description={descriptionLocation} />
|
||||||
|
}
|
||||||
|
else if (message.mediaType === "vcard") {
|
||||||
//console.log("vcard")
|
//console.log("vcard")
|
||||||
//console.log(message)
|
//console.log(message)
|
||||||
let array = message.body.split("\n");
|
let array = message.body.split("\n");
|
||||||
@@ -604,7 +617,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
|||||||
{message.contact?.name}
|
{message.contact?.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{(message.mediaUrl || message.mediaType === "vcard"
|
{(message.mediaUrl || message.mediaType === "location" || message.mediaType === "vcard"
|
||||||
//|| message.mediaType === "multi_vcard"
|
//|| message.mediaType === "multi_vcard"
|
||||||
) && checkMessageMedia(message)}
|
) && checkMessageMedia(message)}
|
||||||
<div className={classes.textContentItem}>
|
<div className={classes.textContentItem}>
|
||||||
@@ -633,7 +646,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
|||||||
>
|
>
|
||||||
<ExpandMore />
|
<ExpandMore />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{(message.mediaUrl || message.mediaType === "vcard"
|
{(message.mediaUrl || message.mediaType === "location" || message.mediaType === "vcard"
|
||||||
//|| message.mediaType === "multi_vcard"
|
//|| message.mediaType === "multi_vcard"
|
||||||
) && checkMessageMedia(message)}
|
) && checkMessageMedia(message)}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const useTickets = ({
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [hasMore, setHasMore] = useState(false);
|
const [hasMore, setHasMore] = useState(false);
|
||||||
const [tickets, setTickets] = useState([]);
|
const [tickets, setTickets] = useState([]);
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -31,18 +32,45 @@ const useTickets = ({
|
|||||||
queueIds,
|
queueIds,
|
||||||
withUnreadMessages,
|
withUnreadMessages,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
setTickets(data.tickets);
|
setTickets(data.tickets)
|
||||||
setHasMore(data.hasMore);
|
|
||||||
setLoading(false);
|
let horasFecharAutomaticamente = process.env.REACT_APP_HOURS_CLOSE_TICKETS_AUTO
|
||||||
} catch (err) {
|
|
||||||
setLoading(false);
|
if (status === "open" && horasFecharAutomaticamente && horasFecharAutomaticamente !== "" &&
|
||||||
toastError(err);
|
horasFecharAutomaticamente !== "0" && Number(horasFecharAutomaticamente) > 0) {
|
||||||
|
|
||||||
|
let dataLimite = new Date()
|
||||||
|
dataLimite.setHours(dataLimite.getHours() - Number(horasFecharAutomaticamente))
|
||||||
|
|
||||||
|
data.tickets.forEach(ticket => {
|
||||||
|
if (ticket.status !== "closed") {
|
||||||
|
let dataUltimaInteracaoChamado = new Date(ticket.updatedAt)
|
||||||
|
if (dataUltimaInteracaoChamado < dataLimite)
|
||||||
|
closeTicket(ticket)
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
fetchTickets();
|
}
|
||||||
}, 500);
|
|
||||||
return () => clearTimeout(delayDebounceFn);
|
setHasMore(data.hasMore)
|
||||||
|
setCount(data.count)
|
||||||
|
setLoading(false)
|
||||||
|
} catch (err) {
|
||||||
|
setLoading(false)
|
||||||
|
toastError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeTicket = async(ticket) => {
|
||||||
|
await api.put(`/tickets/${ticket.id}`, {
|
||||||
|
status: "closed",
|
||||||
|
userId: ticket.userId || null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTickets()
|
||||||
|
}, 500)
|
||||||
|
return () => clearTimeout(delayDebounceFn)
|
||||||
}, [
|
}, [
|
||||||
searchParam,
|
searchParam,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
@@ -51,9 +79,9 @@ const useTickets = ({
|
|||||||
showAll,
|
showAll,
|
||||||
queueIds,
|
queueIds,
|
||||||
withUnreadMessages,
|
withUnreadMessages,
|
||||||
]);
|
])
|
||||||
|
|
||||||
return { tickets, loading, hasMore };
|
return { tickets, loading, hasMore, count };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useTickets;
|
export default useTickets;
|
||||||
@@ -54,13 +54,13 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
const GetTickets = (status, showAll, withUnreadMessages) => {
|
const GetTickets = (status, showAll, withUnreadMessages) => {
|
||||||
|
|
||||||
const { tickets } = useTickets({
|
const { count } = useTickets({
|
||||||
status: status,
|
status: status,
|
||||||
showAll: showAll,
|
showAll: showAll,
|
||||||
withUnreadMessages: withUnreadMessages,
|
withUnreadMessages: withUnreadMessages,
|
||||||
queueIds: JSON.stringify(userQueueIds)
|
queueIds: JSON.stringify(userQueueIds)
|
||||||
});
|
});
|
||||||
return tickets.length;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user