Finished Contact Drawer styles and func

This commit is contained in:
canove
2020-07-27 16:51:54 -03:00
parent 66a1dc5fd2
commit 9477e46c24
12 changed files with 212 additions and 128 deletions

View File

@@ -48,8 +48,9 @@ exports.index = async (req, res, next) => {
include: [ include: [
{ {
model: Contact, model: Contact,
as: "contact",
include: "extraInfo", include: "extraInfo",
attributes: ["name", "number", "profilePicUrl"], attributes: ["id", "name", "number", "profilePicUrl"],
}, },
], ],
}); });
@@ -81,7 +82,6 @@ exports.index = async (req, res, next) => {
return res.json({ return res.json({
messages: serializedMessages.reverse(), messages: serializedMessages.reverse(),
ticket: ticket, ticket: ticket,
contact: ticket.Contact,
}); });
}; };
@@ -98,6 +98,7 @@ exports.store = async (req, res, next) => {
include: [ include: [
{ {
model: Contact, model: Contact,
as: "contact",
attributes: ["number"], attributes: ["number"],
}, },
], ],
@@ -113,12 +114,12 @@ exports.store = async (req, res, next) => {
} }
sentMessage = await wbot.sendMessage( sentMessage = await wbot.sendMessage(
`${ticket.Contact.number}@c.us`, `${ticket.contact.number}@c.us`,
newMedia newMedia
); );
} else { } else {
sentMessage = await wbot.sendMessage( sentMessage = await wbot.sendMessage(
`${ticket.Contact.number}@c.us`, `${ticket.contact.number}@c.us`,
message.body message.body
); );
} }

View File

@@ -22,6 +22,7 @@ exports.index = async (req, res) => {
include: [ include: [
{ {
model: Contact, model: Contact,
as: "contact",
attributes: ["name", "number", "profilePicUrl"], attributes: ["name", "number", "profilePicUrl"],
}, },
], ],
@@ -60,6 +61,7 @@ exports.update = async (req, res) => {
include: [ include: [
{ {
model: Contact, model: Contact,
as: "contact",
attributes: ["name", "number", "profilePicUrl"], attributes: ["name", "number", "profilePicUrl"],
}, },
], ],

View File

@@ -18,7 +18,7 @@ class Contact extends Sequelize.Model {
} }
static associate(models) { static associate(models) {
this.hasMany(models.Ticket, { foreignKey: "contactId" }); this.hasMany(models.Ticket, { foreignKey: "contactId", as: "contact" });
this.hasMany(models.ContactCustomField, { this.hasMany(models.ContactCustomField, {
foreignKey: "contactId", foreignKey: "contactId",
as: "extraInfo", as: "extraInfo",

View File

@@ -5,6 +5,7 @@ class Ticket extends Sequelize.Model {
super.init( super.init(
{ {
status: { type: Sequelize.STRING, defaultValue: "pending" }, status: { type: Sequelize.STRING, defaultValue: "pending" },
userId: { type: Sequelize.INTEGER, defaultValue: null },
lastMessage: { type: Sequelize.STRING }, lastMessage: { type: Sequelize.STRING },
}, },
{ {
@@ -16,7 +17,7 @@ class Ticket extends Sequelize.Model {
} }
static associate(models) { static associate(models) {
this.belongsTo(models.Contact, { foreignKey: "contactId" }); this.belongsTo(models.Contact, { foreignKey: "contactId", as: "contact" });
this.belongsTo(models.User, { foreignKey: "userId" }); this.belongsTo(models.User, { foreignKey: "userId" });
this.hasMany(models.Message, { foreignKey: "ticketId" }); this.hasMany(models.Message, { foreignKey: "ticketId" });
} }

View File

@@ -28,22 +28,16 @@ const verifyContact = async (msgContact, profilePicUrl) => {
}; };
const verifyTicket = async contact => { const verifyTicket = async contact => {
let ticket = await Ticket.findOne({ const [ticket, created] = await Ticket.findOrCreate({
where: { where: {
status: { status: {
[Op.or]: ["open", "pending"], [Op.or]: ["open", "pending"],
}, },
contactId: contact.id, contactId: contact.id,
}, },
defaults: { contactId: contact.id, status: "pending" },
}); });
if (!ticket) {
ticket = await Ticket.create({
contactId: contact.id,
status: "pending",
});
}
return ticket; return ticket;
}; };
@@ -127,6 +121,7 @@ const wbotMessageListener = () => {
...ticket.dataValues, ...ticket.dataValues,
unreadMessages: 1, unreadMessages: 1,
lastMessage: newMessage.body, lastMessage: newMessage.body,
contact: contact,
}; };
io.to(ticket.id).to("notification").emit("appMessage", { io.to(ticket.id).to("notification").emit("appMessage", {

View File

@@ -1,10 +1,19 @@
import React from "react"; import React, { useState } from "react";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import CloseIcon from "@material-ui/icons/Close"; import CloseIcon from "@material-ui/icons/Close";
import Drawer from "@material-ui/core/Drawer"; import Drawer from "@material-ui/core/Drawer";
import Link from "@material-ui/core/Link";
import InputLabel from "@material-ui/core/InputLabel";
import Avatar from "@material-ui/core/Avatar";
import Button from "@material-ui/core/Button";
import Paper from "@material-ui/core/Paper";
import LinkifyWithTargetBlank from "../LinkifyWithTargetBlank";
import ContactModal from "../ContactModal";
import ContactDrawerSkeleton from "../ContactDrawerSkeleton";
const drawerWidth = 320; const drawerWidth = 320;
@@ -21,7 +30,7 @@ const useStyles = makeStyles(theme => ({
borderBottomRightRadius: 4, borderBottomRightRadius: 4,
backgroundColor: "#eee", backgroundColor: "#eee",
}, },
drawerHeader: { header: {
display: "flex", display: "flex",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)", borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
backgroundColor: "#eee", backgroundColor: "#eee",
@@ -30,11 +39,75 @@ const useStyles = makeStyles(theme => ({
minHeight: "73px", minHeight: "73px",
justifyContent: "flex-start", justifyContent: "flex-start",
}, },
content: {
display: "flex",
flexDirection: "column",
padding: "8px 0px 8px 8px",
// backgroundColor: "red",
height: "100%",
overflow: "scroll",
"&::-webkit-scrollbar": {
width: "8px",
height: "8px",
},
"&::-webkit-scrollbar-thumb": {
// borderRadius: "2px",
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8",
},
},
contactAvatar: {
margin: 15,
width: 160,
height: 160,
},
contactHeader: {
display: "flex",
padding: 8,
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
"& > *": {
margin: 4,
},
},
contactDetails: {
marginTop: 8,
padding: 8,
display: "flex",
flexDirection: "column",
// overflowX: "scroll",
// flex: 1,
// "&::-webkit-scrollbar": {
// width: "8px",
// height: "8px",
// },
// "&::-webkit-scrollbar-thumb": {
// // borderRadius: "2px",
// boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
// backgroundColor: "#e8e8e8",
// },
},
contactExtraInfo: {
marginTop: 4,
padding: 6,
},
})); }));
const ContactDrawer = ({ open, children, handleDrawerClose }) => { const ContactDrawer = ({
open,
children,
handleDrawerClose,
contact,
loading,
}) => {
const classes = useStyles(); const classes = useStyles();
const [modalOpen, setModalOpen] = useState(false);
return ( return (
<Drawer <Drawer
className={classes.drawer} className={classes.drawer}
@@ -51,7 +124,7 @@ const ContactDrawer = ({ open, children, handleDrawerClose }) => {
paper: classes.drawerPaper, paper: classes.drawerPaper,
}} }}
> >
<div className={classes.drawerHeader}> <div className={classes.header}>
<IconButton onClick={handleDrawerClose}> <IconButton onClick={handleDrawerClose}>
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
@@ -59,7 +132,57 @@ const ContactDrawer = ({ open, children, handleDrawerClose }) => {
Dados do contato Dados do contato
</Typography> </Typography>
</div> </div>
{children} {loading ? (
<ContactDrawerSkeleton classes={classes} />
) : (
<div className={classes.content}>
<Paper square variant="outlined" className={classes.contactHeader}>
<Avatar
alt={contact.name}
src={contact.profilePicUrl}
className={classes.contactAvatar}
></Avatar>
<Typography>{contact.name}</Typography>
<Typography>
<Link href={`tel:${contact.number}`}>{contact.number}</Link>
</Typography>
<Button
variant="outlined"
color="primary"
onClick={e => setModalOpen(true)}
>
Editar contato
</Button>
</Paper>
<Paper square variant="outlined" className={classes.contactDetails}>
<ContactModal
modalOpen={modalOpen}
onClose={e => setModalOpen(false)}
aria-labelledby="form-dialog-title"
contactId={contact.id}
></ContactModal>
<Typography variant="subtitle1">Outras informações</Typography>
{contact &&
contact.extraInfo &&
contact.extraInfo.map(info => (
<Paper
key={info.id}
square
variant="outlined"
className={classes.contactExtraInfo}
>
<InputLabel>{info.name}</InputLabel>
<LinkifyWithTargetBlank>
<Typography noWrap style={{ paddingTop: 2 }}>
{info.value}
</Typography>
</LinkifyWithTargetBlank>
</Paper>
))}
</Paper>
</div>
)}
</Drawer> </Drawer>
); );
}; };

View File

@@ -0,0 +1,40 @@
import React from "react";
import Skeleton from "@material-ui/lab/Skeleton";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
const ContactDrawerSkeleton = ({ classes }) => {
return (
<div className={classes.content}>
<Paper square variant="outlined" className={classes.contactHeader}>
<Skeleton
animation="wave"
variant="circle"
width={160}
height={160}
className={classes.contactAvatar}
/>
<Skeleton animation="wave" height={25} width={90} />
<Skeleton animation="wave" height={25} width={80} />
<Skeleton animation="wave" height={25} width={80} />
</Paper>
<Paper square className={classes.contactDetails}>
<Typography variant="subtitle1">Outras informações</Typography>
<Paper square variant="outlined" className={classes.contactExtraInfo}>
<Skeleton animation="wave" height={20} width={60} />
<Skeleton animation="wave" height={20} width={160} />
</Paper>
<Paper square variant="outlined" className={classes.contactExtraInfo}>
<Skeleton animation="wave" height={20} width={60} />
<Skeleton animation="wave" height={20} width={160} />
</Paper>
<Paper square variant="outlined" className={classes.contactExtraInfo}>
<Skeleton animation="wave" height={20} width={60} />
<Skeleton animation="wave" height={20} width={160} />
</Paper>
</Paper>
</div>
);
};
export default ContactDrawerSkeleton;

View File

@@ -77,7 +77,7 @@ const ContactModal = ({ modalOpen, onClose, contactId }) => {
}; };
fetchContact(); fetchContact();
}, [contactId]); }, [contactId, modalOpen]);
const handleClose = () => { const handleClose = () => {
onClose(); onClose();

View File

@@ -16,29 +16,15 @@ import Card from "@material-ui/core/Card";
import CardHeader from "@material-ui/core/CardHeader"; import CardHeader from "@material-ui/core/CardHeader";
import ReplayIcon from "@material-ui/icons/Replay"; import ReplayIcon from "@material-ui/icons/Replay";
import Avatar from "@material-ui/core/Avatar"; import Avatar from "@material-ui/core/Avatar";
import Divider from "@material-ui/core/Divider";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";
import Link from "@material-ui/core/Link";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
import { green } from "@material-ui/core/colors"; import { green } from "@material-ui/core/colors";
import Skeleton from "@material-ui/lab/Skeleton"; import Skeleton from "@material-ui/lab/Skeleton";
// import Drawer from "@material-ui/core/Drawer"; import api from "../../services/api";
import ContactDrawer from "../ContactDrawer"; import ContactDrawer from "../ContactDrawer";
import whatsBackground from "../../assets/wa-background.png"; import whatsBackground from "../../assets/wa-background.png";
import LinkifyWithTargetBlank from "../LinkifyWithTargetBlank"; import LinkifyWithTargetBlank from "../LinkifyWithTargetBlank";
import api from "../../services/api";
import MessageInput from "../MessageInput/"; import MessageInput from "../MessageInput/";
const drawerWidth = 320; const drawerWidth = 320;
@@ -111,6 +97,7 @@ const useStyles = makeStyles(theme => ({
overflowY: "scroll", overflowY: "scroll",
"&::-webkit-scrollbar": { "&::-webkit-scrollbar": {
width: "8px", width: "8px",
height: "8px",
}, },
"&::-webkit-scrollbar-thumb": { "&::-webkit-scrollbar-thumb": {
// borderRadius: "2px", // borderRadius: "2px",
@@ -126,7 +113,6 @@ const useStyles = makeStyles(theme => ({
top: 0, top: 0,
left: "50%", left: "50%",
marginTop: 12, marginTop: 12,
// marginLeft: -12,
}, },
messageLeft: { messageLeft: {
@@ -226,44 +212,6 @@ const useStyles = makeStyles(theme => ({
verticalAlign: "middle", verticalAlign: "middle",
marginLeft: 4, marginLeft: 4,
}, },
drawerContent: {
display: "flex",
flexDirection: "column",
padding: 8,
// backgroundColor: "red",
height: "100%",
overflow: "hidden",
},
drawerAvatar: {
margin: 15,
width: 160,
height: 160,
},
drawerHeader: {
// margin: 8,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
"& > *": {
margin: 4,
},
},
drawerDetails: {
marginTop: 8,
padding: 8,
display: "flex",
flexDirection: "column",
// overflow: "hidden",
// whiteSpace: "nowrap",
// textOverflow: "ellipsis",
// overflow: "scroll",
flex: 1,
},
})); }));
const MessagesList = () => { const MessagesList = () => {
@@ -297,7 +245,7 @@ const MessagesList = () => {
const res = await api.get("/messages/" + ticketId, { const res = await api.get("/messages/" + ticketId, {
params: { searchParam, pageNumber }, params: { searchParam, pageNumber },
}); });
setContact(res.data.contact); setContact(res.data.ticket.contact);
setTicket(res.data.ticket); setTicket(res.data.ticket);
setMessagesList(prevMessages => { setMessagesList(prevMessages => {
return [...res.data.messages, ...prevMessages]; return [...res.data.messages, ...prevMessages];
@@ -331,6 +279,12 @@ const MessagesList = () => {
} }
}); });
socket.on("contact", data => {
if (data.action === "update") {
setContact(data.contact);
}
});
return () => { return () => {
socket.disconnect(); socket.disconnect();
setSearchParam(""); setSearchParam("");
@@ -531,8 +485,6 @@ const MessagesList = () => {
setOpen(false); setOpen(false);
}; };
console.log(contact);
return ( return (
<div className={classes.root} id="drawer-container"> <div className={classes.root} id="drawer-container">
<Paper <Paper
@@ -627,46 +579,13 @@ const MessagesList = () => {
) : null} ) : null}
</div> </div>
</Paper> </Paper>
<ContactDrawer open={open} handleDrawerClose={handleDrawerClose}>
<div className={classes.drawerContent}>
<div className={classes.drawerHeader}>
<Avatar
alt={contact.name}
src={contact.profilePicUrl}
className={classes.drawerAvatar}
></Avatar>
<Typography>{contact.name}</Typography> <ContactDrawer
<Typography> open={open}
<Link href={`tel:${contact.number}`}>{contact.number}</Link> handleDrawerClose={handleDrawerClose}
</Typography> contact={contact}
<Button variant="outlined" color="primary"> loading={loading}
Editar contato />
</Button>
</div>
<Paper className={classes.drawerDetails}>
<Typography variant="subtitle1">Informações</Typography>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Nome</TableCell>
<TableCell>Valor</TableCell>
</TableRow>
</TableHead>
<TableBody>
{contact &&
contact.extraInfo &&
contact.extraInfo.map(info => (
<TableRow key={info.id}>
<TableCell>{info.name}</TableCell>
<TableCell>{info.value}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Paper>
</div>
</ContactDrawer>
</div> </div>
); );
}; };

View File

@@ -1,7 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useHistory, useParams } from "react-router-dom"; import { useHistory, useParams } from "react-router-dom";
import openSocket from "socket.io-client"; import openSocket from "socket.io-client";
import { parseISO, format } from "date-fns"; import { parseISO, format } from "date-fns";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
@@ -18,11 +17,11 @@ import Badge from "@material-ui/core/Badge";
import SearchIcon from "@material-ui/icons/Search"; import SearchIcon from "@material-ui/icons/Search";
import InputBase from "@material-ui/core/InputBase"; import InputBase from "@material-ui/core/InputBase";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Tabs from "@material-ui/core/Tabs"; import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab"; import Tab from "@material-ui/core/Tab";
import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"; import MoveToInboxIcon from "@material-ui/icons/MoveToInbox";
import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline"; import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline";
import TicketsSkeleton from "../TicketsSkeleton"; import TicketsSkeleton from "../TicketsSkeleton";
import api from "../../services/api"; import api from "../../services/api";
@@ -55,6 +54,7 @@ const useStyles = makeStyles(theme => ({
overflowY: "scroll", overflowY: "scroll",
"&::-webkit-scrollbar": { "&::-webkit-scrollbar": {
width: "8px", width: "8px",
height: "8px",
}, },
"&::-webkit-scrollbar-thumb": { "&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)", boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
@@ -68,6 +68,7 @@ const useStyles = makeStyles(theme => ({
overflowY: "scroll", overflowY: "scroll",
"&::-webkit-scrollbar": { "&::-webkit-scrollbar": {
width: "8px", width: "8px",
height: "8px",
}, },
"&::-webkit-scrollbar-thumb": { "&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)", boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
@@ -194,6 +195,8 @@ const useStyles = makeStyles(theme => ({
const TicketsList = () => { const TicketsList = () => {
const classes = useStyles(); const classes = useStyles();
const history = useHistory();
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
const userId = +localStorage.getItem("userId"); const userId = +localStorage.getItem("userId");
const { ticketId } = useParams(); const { ticketId } = useParams();
@@ -202,10 +205,6 @@ const TicketsList = () => {
const [searchParam, setSearchParam] = useState(""); const [searchParam, setSearchParam] = useState("");
const [tab, setTab] = useState("open"); const [tab, setTab] = useState("open");
// const [modalOpen, setModalOpen] = useState(false);
const history = useHistory();
useEffect(() => { useEffect(() => {
if (!("Notification" in window)) { if (!("Notification" in window)) {
console.log("This browser doesn't support notifications"); console.log("This browser doesn't support notifications");
@@ -301,8 +300,6 @@ const TicketsList = () => {
}); });
}; };
console.log(tickets);
const showDesktopNotification = data => { const showDesktopNotification = data => {
const options = { const options = {
body: `${data.message.body} - ${format(new Date(), "HH:mm")}`, body: `${data.message.body} - ${format(new Date(), "HH:mm")}`,
@@ -382,7 +379,7 @@ const TicketsList = () => {
<ListItemAvatar> <ListItemAvatar>
<Avatar <Avatar
src={ src={
ticket.Contact.profilePicUrl && ticket.Contact.profilePicUrl ticket.contact.profilePicUrl && ticket.contact.profilePicUrl
} }
></Avatar> ></Avatar>
</ListItemAvatar> </ListItemAvatar>
@@ -395,7 +392,7 @@ const TicketsList = () => {
variant="body2" variant="body2"
color="textPrimary" color="textPrimary"
> >
{ticket.Contact.name} {ticket.contact.name}
</Typography> </Typography>
{ticket.lastMessage && ( {ticket.lastMessage && (
<Typography <Typography

View File

@@ -57,6 +57,7 @@ const useStyles = makeStyles(theme => ({
overflowY: "scroll", overflowY: "scroll",
"&::-webkit-scrollbar": { "&::-webkit-scrollbar": {
width: "8px", width: "8px",
height: "8px",
}, },
"&::-webkit-scrollbar-thumb": { "&::-webkit-scrollbar-thumb": {
// borderRadius: "2px", // borderRadius: "2px",
@@ -191,7 +192,6 @@ const Contacts = () => {
<ContactModal <ContactModal
modalOpen={modalOpen} modalOpen={modalOpen}
onClose={handleClose} onClose={handleClose}
setModalOpen={setModalOpen}
aria-labelledby="form-dialog-title" aria-labelledby="form-dialog-title"
contactId={selectedContactId} contactId={selectedContactId}
></ContactModal> ></ContactModal>

View File

@@ -3,7 +3,13 @@ import React from "react";
const Dashboard = () => { const Dashboard = () => {
return ( return (
<div> <div>
<h1>Todo Dashboard</h1> <a
rel="bookmark"
target="_parent"
href="https://economicros.ddns.com.br:4043/"
>
teste
</a>
</div> </div>
); );
}; };