improvement: start spliting "MessagesList" in multiple components

This commit is contained in:
canove
2020-09-26 18:53:28 -03:00
parent 1b95595db5
commit 14caaf3895
9 changed files with 492 additions and 127 deletions

View File

@@ -30,6 +30,9 @@ import whatsBackground from "../../assets/wa-background.png";
import LinkifyWithTargetBlank from "../LinkifyWithTargetBlank";
import MessageInput from "../MessageInput/";
import TicketOptionsMenu from "../TicketOptionsMenu";
import TicketHeader from "../TicketHeader";
import TicketInfo from "../TicketInfo";
import TicketActionButtons from "../TicketActionButtons";
const drawerWidth = 320;
@@ -67,24 +70,6 @@ const useStyles = makeStyles(theme => ({
marginRight: 0,
},
messagesHeader: {
display: "flex",
// cursor: "pointer",
backgroundColor: "#eee",
flex: "none",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
},
actionButtons: {
marginRight: 6,
flex: "none",
alignSelf: "center",
marginLeft: "auto",
"& > *": {
margin: theme.spacing(1),
},
},
messagesListWrapper: {
overflow: "hidden",
position: "relative",
@@ -262,14 +247,13 @@ const reducer = (state, action) => {
}
};
const MessagesList = () => {
const Ticket = () => {
const { ticketId } = useParams();
const history = useHistory();
const classes = useStyles();
const userId = +localStorage.getItem("userId");
const [loading, setLoading] = useState(true);
const [ticketLoading, setTicketLoading] = useState(true);
const [contact, setContact] = useState({});
const [ticket, setTicket] = useState({});
const [drawerOpen, setDrawerOpen] = useState(false);
@@ -279,12 +263,10 @@ const MessagesList = () => {
const [hasMore, setHasMore] = useState(false);
const [pageNumber, setPageNumber] = useState(1);
const [anchorEl, setAnchorEl] = useState(null);
const moreMenuOpen = Boolean(anchorEl);
useEffect(() => {
dispatch({ type: "RESET" });
setPageNumber(1);
setTicketLoading(true);
}, [ticketId]);
useEffect(() => {
@@ -301,6 +283,7 @@ const MessagesList = () => {
dispatch({ type: "LOAD_MESSAGES", payload: data.messages });
setHasMore(data.hasMore);
setLoading(false);
setTicketLoading(false);
if (pageNumber === 1 && data.messages.length > 1) {
scrollToBottom();
@@ -420,29 +403,6 @@ const MessagesList = () => {
}
};
const handleOpenTicketOptionsMenu = e => {
setAnchorEl(e.currentTarget);
};
const handleCloseTicketOptionsMenu = e => {
setAnchorEl(null);
};
const handleUpdateTicketStatus = async (e, status, userId) => {
try {
await api.put(`/tickets/${ticketId}`, {
status: status,
userId: userId || null,
});
} catch (err) {
console.log(err);
if (err.response && err.response.data && err.response.data.error) {
toast.error(err.response.data.error);
}
}
history.push("/tickets");
};
const handleDrawerOpen = () => {
setDrawerOpen(true);
};
@@ -575,81 +535,14 @@ const MessagesList = () => {
[classes.mainWrapperShift]: drawerOpen,
})}
>
<Card square className={classes.messagesHeader}>
<CardHeader
<TicketHeader loading={ticketLoading}>
<TicketInfo
contact={contact}
ticket={ticket}
onClick={handleDrawerOpen}
style={{ cursor: "pointer" }}
titleTypographyProps={{ noWrap: true }}
subheaderTypographyProps={{ noWrap: true }}
avatar={
loading ? (
<Skeleton animation="wave" variant="circle">
<Avatar alt="contact_image" />
</Skeleton>
) : (
<Avatar src={contact.profilePicUrl} alt="contact_image" />
)
}
title={
loading ? (
<Skeleton animation="wave" width={60} />
) : (
`${contact.name} #${ticket.id}`
)
}
subheader={
loading ? (
<Skeleton animation="wave" width={80} />
) : ticket.user ? (
`${i18n.t("messagesList.header.assignedTo")} ${
ticket.user.name
}`
) : (
"Pending"
)
}
/>
{!loading && (
<div className={classes.actionButtons}>
{ticket.status === "closed" ? (
<Button
startIcon={<ReplayIcon />}
size="small"
onClick={e => handleUpdateTicketStatus(e, "open", userId)}
>
{i18n.t("messagesList.header.buttons.reopen")}
</Button>
) : (
<>
<Button
startIcon={<ReplayIcon />}
size="small"
onClick={e => handleUpdateTicketStatus(e, "pending", null)}
>
{i18n.t("messagesList.header.buttons.return")}
</Button>
<Button
size="small"
variant="contained"
color="primary"
onClick={e => handleUpdateTicketStatus(e, "closed", userId)}
>
{i18n.t("messagesList.header.buttons.resolve")}
</Button>
</>
)}
<IconButton onClick={handleOpenTicketOptionsMenu}>
<MoreVertIcon />
</IconButton>
<TicketOptionsMenu
ticket={ticket}
anchorEl={anchorEl}
menuOpen={moreMenuOpen}
handleClose={handleCloseTicketOptionsMenu}
/>
</div>
)}
</Card>
<TicketActionButtons ticket={ticket} />
</TicketHeader>
<div className={classes.messagesListWrapper}>
<div
id="messagesList"
@@ -666,15 +559,14 @@ const MessagesList = () => {
) : null}
</div>
</Paper>
<ContactDrawer
open={drawerOpen}
handleDrawerClose={handleDrawerClose}
contact={contact}
loading={loading}
loading={ticketLoading}
/>
</div>
);
};
export default MessagesList;
export default Ticket;

View File

@@ -0,0 +1,113 @@
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { toast } from "react-toastify";
import { makeStyles } from "@material-ui/core/styles";
import { Button, IconButton } from "@material-ui/core";
import { MoreVert, Replay } from "@material-ui/icons";
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import TicketOptionsMenu from "../TicketOptionsMenu";
const useStyles = makeStyles(theme => ({
actionButtons: {
marginRight: 6,
flex: "none",
alignSelf: "center",
marginLeft: "auto",
"& > *": {
margin: theme.spacing(1),
},
},
}));
const TicketActionButtons = ({ ticket }) => {
const classes = useStyles();
const history = useHistory();
const userId = +localStorage.getItem("userId");
const [anchorEl, setAnchorEl] = useState(null);
const ticketOptionsMenuOpen = Boolean(anchorEl);
const handleOpenTicketOptionsMenu = e => {
setAnchorEl(e.currentTarget);
};
const handleCloseTicketOptionsMenu = e => {
setAnchorEl(null);
};
const handleUpdateTicketStatus = async (e, status, userId) => {
try {
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null,
});
if (status === "open") {
history.push(`/tickets/${ticket.id}`);
} else {
history.push("/tickets");
}
} catch (err) {
console.log(err);
if (err.response && err.response.data && err.response.data.error) {
toast.error(err.response.data.error);
}
}
};
return (
<div className={classes.actionButtons}>
{ticket.status === "closed" && (
<Button
startIcon={<Replay />}
size="small"
onClick={e => handleUpdateTicketStatus(e, "open", userId)}
>
{i18n.t("messagesList.header.buttons.reopen")}
</Button>
)}
{ticket.status === "open" && (
<>
<Button
startIcon={<Replay />}
size="small"
onClick={e => handleUpdateTicketStatus(e, "pending", null)}
>
{i18n.t("messagesList.header.buttons.return")}
</Button>
<Button
size="small"
variant="contained"
color="primary"
onClick={e => handleUpdateTicketStatus(e, "closed", userId)}
>
{i18n.t("messagesList.header.buttons.resolve")}
</Button>
<IconButton onClick={handleOpenTicketOptionsMenu}>
<MoreVert />
</IconButton>
<TicketOptionsMenu
ticket={ticket}
anchorEl={anchorEl}
menuOpen={ticketOptionsMenuOpen}
handleClose={handleCloseTicketOptionsMenu}
/>
</>
)}
{ticket.status === "pending" && (
<Button
size="small"
variant="contained"
color="primary"
onClick={e => handleUpdateTicketStatus(e, "open", userId)}
>
ACCEPT
</Button>
)}
</div>
);
};
export default TicketActionButtons;

View File

@@ -0,0 +1,28 @@
import React from "react";
import { Card } from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
import TicketHeaderSkeleton from "../TicketHeaderSkeleton";
const useStyles = makeStyles(theme => ({
ticketHeader: {
display: "flex",
backgroundColor: "#eee",
flex: "none",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
},
}));
const TicketHeader = ({ loading, children }) => {
const classes = useStyles();
if (loading) return <TicketHeaderSkeleton />;
return (
<Card square className={classes.ticketHeader}>
{children}
</Card>
);
};
export default TicketHeader;

View File

@@ -0,0 +1,272 @@
import React from "react";
import {
Avatar,
Button,
Card,
CardHeader,
IconButton,
} from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
import { green } from "@material-ui/core/colors";
import Skeleton from "@material-ui/lab/Skeleton";
import { Replay } from "@material-ui/icons";
import TicketOptionsMenu from "../TicketOptionsMenu";
import { i18n } from "../../translate/i18n";
const drawerWidth = 320;
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
height: "100%",
position: "relative",
overflow: "hidden",
},
mainWrapper: {
flex: 1,
height: "100%",
display: "flex",
flexDirection: "column",
overflow: "hidden",
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
borderLeft: "0",
marginRight: -drawerWidth,
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
mainWrapperShift: {
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
marginRight: 0,
},
messagesHeader: {
display: "flex",
// cursor: "pointer",
backgroundColor: "#eee",
flex: "none",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
},
actionButtons: {
marginRight: 6,
flex: "none",
alignSelf: "center",
marginLeft: "auto",
"& > *": {
margin: theme.spacing(1),
},
},
messagesListWrapper: {
overflow: "hidden",
position: "relative",
display: "flex",
flexDirection: "column",
flexGrow: 1,
},
circleLoading: {
color: green[500],
position: "absolute",
opacity: "70%",
top: 0,
left: "50%",
marginTop: 12,
},
messageLeft: {
marginRight: 20,
marginTop: 2,
minWidth: 100,
maxWidth: 600,
height: "auto",
display: "block",
position: "relative",
whiteSpace: "pre-wrap",
backgroundColor: "#ffffff",
color: "#303030",
alignSelf: "flex-start",
borderTopLeftRadius: 0,
borderTopRightRadius: 8,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
paddingLeft: 5,
paddingRight: 5,
paddingTop: 5,
paddingBottom: 0,
boxShadow: "0 1px 1px #b3b3b3",
},
messageRight: {
marginLeft: 20,
marginTop: 2,
minWidth: 100,
maxWidth: 600,
height: "auto",
display: "block",
position: "relative",
whiteSpace: "pre-wrap",
backgroundColor: "#dcf8c6",
color: "#303030",
alignSelf: "flex-end",
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 0,
paddingLeft: 5,
paddingRight: 5,
paddingTop: 5,
paddingBottom: 0,
boxShadow: "0 1px 1px #b3b3b3",
},
textContentItem: {
overflowWrap: "break-word",
padding: "3px 80px 6px 6px",
},
messageMedia: {
objectFit: "cover",
width: 250,
height: 200,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
},
timestamp: {
fontSize: 11,
position: "absolute",
bottom: 0,
right: 5,
color: "#999",
},
dailyTimestamp: {
alignItems: "center",
textAlign: "center",
alignSelf: "center",
width: "110px",
backgroundColor: "#e1f3fb",
margin: "10px",
borderRadius: "10px",
boxShadow: "0 1px 1px #b3b3b3",
},
dailyTimestampText: {
color: "#808888",
padding: 8,
alignSelf: "center",
marginLeft: "0px",
},
ackIcons: {
fontSize: 18,
verticalAlign: "middle",
marginLeft: 4,
},
ackDoneAllIcon: {
color: green[500],
fontSize: 18,
verticalAlign: "middle",
marginLeft: 4,
},
}));
const TicketHeader = ({ loading, contact, ticket }) => {
const classes = useStyles();
return (
<Card square className={classes.messagesHeader}>
<CardHeader
// onClick={handleDrawerOpen}
style={{ cursor: "pointer" }}
titleTypographyProps={{ noWrap: true }}
subheaderTypographyProps={{ noWrap: true }}
avatar={
loading ? (
<Skeleton animation="wave" variant="circle">
<Avatar alt="contact_image" />
</Skeleton>
) : (
<Avatar src={contact.profilePicUrl} alt="contact_image" />
)
}
title={
loading ? (
<Skeleton animation="wave" width={60} />
) : (
`${contact.name} #${ticket.id}`
)
}
subheader={
loading ? (
<Skeleton animation="wave" width={80} />
) : ticket.user ? (
`${i18n.t("messagesList.header.assignedTo")} ${ticket.user.name}`
) : (
"Pending"
)
}
/>
{!loading && (
<div className={classes.actionButtons}>
{ticket.status === "closed" ? (
<Button
startIcon={<Replay />}
size="small"
// onClick={e => handleUpdateTicketStatus(e, "open", userId)}
>
{i18n.t("messagesList.header.buttons.reopen")}
</Button>
) : (
<>
<Button
startIcon={<Replay />}
size="small"
// onClick={e => handleUpdateTicketStatus(e, "pending", null)}
>
{i18n.t("messagesList.header.buttons.return")}
</Button>
<Button
size="small"
variant="contained"
color="primary"
// onClick={e => handleUpdateTicketStatus(e, "closed", userId)}
>
{i18n.t("messagesList.header.buttons.resolve")}
</Button>
</>
)}
{/* <IconButton onClick={handleOpenTicketOptionsMenu}>
<MoreVertIcon />
</IconButton>
<TicketOptionsMenu
ticket={ticket}
anchorEl={anchorEl}
menuOpen={moreMenuOpen}
handleClose={handleCloseTicketOptionsMenu}
/> */}
</div>
)}
</Card>
);
};
export default TicketHeader;

View File

@@ -0,0 +1,36 @@
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import { Avatar, Card, CardHeader } from "@material-ui/core";
import Skeleton from "@material-ui/lab/Skeleton";
const useStyles = makeStyles(theme => ({
ticketHeader: {
display: "flex",
backgroundColor: "#eee",
flex: "none",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
},
}));
const TicketHeaderSkeleton = () => {
const classes = useStyles();
return (
<Card square className={classes.ticketHeader}>
<CardHeader
titleTypographyProps={{ noWrap: true }}
subheaderTypographyProps={{ noWrap: true }}
avatar={
<Skeleton animation="wave" variant="circle">
<Avatar alt="contact_image" />
</Skeleton>
}
title={<Skeleton animation="wave" width={80} />}
subheader={<Skeleton animation="wave" width={140} />}
/>
</Card>
);
};
export default TicketHeaderSkeleton;

View File

@@ -0,0 +1,24 @@
import React from "react";
import { Avatar, CardHeader } from "@material-ui/core";
import { i18n } from "../../translate/i18n";
const TicketInfo = ({ contact, ticket, onClick }) => {
return (
<CardHeader
onClick={onClick}
style={{ cursor: "pointer" }}
titleTypographyProps={{ noWrap: true }}
subheaderTypographyProps={{ noWrap: true }}
avatar={<Avatar src={contact.profilePicUrl} alt="contact_image" />}
title={`${contact.name} #${ticket.id}`}
subheader={
ticket.user &&
`${i18n.t("messagesList.header.assignedTo")} ${ticket.user.name}`
}
/>
);
};
export default TicketInfo;

View File

@@ -6,7 +6,7 @@ import List from "@material-ui/core/List";
import Paper from "@material-ui/core/Paper";
import TicketListItem from "../TicketListItem";
import TicketsSkeleton from "../TicketsSkeleton";
import TicketsListSkeleton from "../TicketsListSkeleton";
import useTickets from "../../hooks/useTickets";
import { i18n } from "../../translate/i18n";
@@ -276,7 +276,7 @@ const TicketsList = ({ status, searchParam, showAll }) => {
))}
</>
)}
{loading && <TicketsSkeleton />}
{loading && <TicketsListSkeleton />}
</List>
</Paper>
</div>

View File

@@ -5,7 +5,7 @@ import Paper from "@material-ui/core/Paper";
import { makeStyles } from "@material-ui/core/styles";
import TicketsManager from "../../components/TicketsManager/";
import MessagesList from "../../components/MessagesList/";
import Ticket from "../../components/Ticket/";
import { i18n } from "../../translate/i18n";
@@ -59,7 +59,7 @@ const Chat = () => {
<Grid item xs={8} className={classes.messagessWrapper}>
{ticketId ? (
<>
<MessagesList />
<Ticket />
</>
) : (
<Paper square variant="outlined" className={classes.welcomeMsg}>