mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-19 20:29:17 +00:00
feat: tickets and messages search concluded
This commit is contained in:
@@ -8,7 +8,15 @@ const Message = require("../models/Message");
|
|||||||
const { getIO } = require("../libs/socket");
|
const { getIO } = require("../libs/socket");
|
||||||
|
|
||||||
exports.index = async (req, res) => {
|
exports.index = async (req, res) => {
|
||||||
const { status = "", date = "", searchParam = "" } = req.query;
|
const {
|
||||||
|
pageNumber = 1,
|
||||||
|
status = "",
|
||||||
|
date = "",
|
||||||
|
searchParam = "",
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
|
let limit = 20;
|
||||||
|
let offset = limit * (pageNumber - 1);
|
||||||
|
|
||||||
let whereCondition = {};
|
let whereCondition = {};
|
||||||
let includeCondition = [
|
let includeCondition = [
|
||||||
@@ -41,6 +49,7 @@ exports.index = async (req, res) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
|
duplicating: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -78,13 +87,16 @@ exports.index = async (req, res) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const tickets = await Ticket.findAll({
|
const { count, rows: tickets } = await Ticket.findAndCountAll({
|
||||||
where: whereCondition,
|
where: whereCondition,
|
||||||
|
distinct: true,
|
||||||
include: includeCondition,
|
include: includeCondition,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
order: [["updatedAt", "DESC"]],
|
order: [["updatedAt", "DESC"]],
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json(tickets);
|
return res.json({ count, tickets });
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.store = async (req, res) => {
|
exports.store = async (req, res) => {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-infinite-scroll-reverse": "^1.0.3",
|
"react-infinite-scroll-reverse": "^1.0.3",
|
||||||
|
"react-infinite-scroller": "^1.2.4",
|
||||||
"react-linkify": "^1.0.0-alpha",
|
"react-linkify": "^1.0.0-alpha",
|
||||||
"react-modal-image": "^2.5.0",
|
"react-modal-image": "^2.5.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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 { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import InfiniteScroll from "react-infinite-scroller";
|
||||||
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import { green } from "@material-ui/core/colors";
|
import { green } from "@material-ui/core/colors";
|
||||||
@@ -60,7 +61,7 @@ const useStyles = makeStyles(theme => ({
|
|||||||
width: 120, // a number of your choice
|
width: 120, // a number of your choice
|
||||||
},
|
},
|
||||||
|
|
||||||
openTicketsList: {
|
halfTicketsList: {
|
||||||
height: "50%",
|
height: "50%",
|
||||||
overflowY: "scroll",
|
overflowY: "scroll",
|
||||||
"&::-webkit-scrollbar": {
|
"&::-webkit-scrollbar": {
|
||||||
@@ -74,7 +75,7 @@ const useStyles = makeStyles(theme => ({
|
|||||||
borderTop: "1px solid rgba(0, 0, 0, 0.12)",
|
borderTop: "1px solid rgba(0, 0, 0, 0.12)",
|
||||||
},
|
},
|
||||||
|
|
||||||
closedTicketsList: {
|
fullHeightTicketsList: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
overflowY: "scroll",
|
overflowY: "scroll",
|
||||||
"&::-webkit-scrollbar": {
|
"&::-webkit-scrollbar": {
|
||||||
@@ -245,6 +246,9 @@ const TicketsList = () => {
|
|||||||
const [newTicketModalOpen, setNewTicketModalOpen] = useState(false);
|
const [newTicketModalOpen, setNewTicketModalOpen] = useState(false);
|
||||||
const [showAllTickets, setShowAllTickets] = useState(false);
|
const [showAllTickets, setShowAllTickets] = useState(false);
|
||||||
|
|
||||||
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
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");
|
||||||
@@ -255,17 +259,21 @@ const TicketsList = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTickets([]);
|
setTickets([]);
|
||||||
}, [searchParam]);
|
setPageNumber(1);
|
||||||
|
}, [searchParam, tab]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const delayDebounceFn = setTimeout(() => {
|
const delayDebounceFn = setTimeout(() => {
|
||||||
const fetchContacts = async () => {
|
const fetchContacts = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get("/tickets", {
|
const { data } = await api.get("/tickets", {
|
||||||
params: { searchParam, status: tab },
|
params: { searchParam, pageNumber, status: tab },
|
||||||
});
|
});
|
||||||
setTickets(res.data);
|
setTickets(prevState => {
|
||||||
|
return [...prevState, ...data.tickets];
|
||||||
|
});
|
||||||
|
setCount(data.count);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -274,7 +282,9 @@ const TicketsList = () => {
|
|||||||
fetchContacts();
|
fetchContacts();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
return () => clearTimeout(delayDebounceFn);
|
return () => clearTimeout(delayDebounceFn);
|
||||||
}, [searchParam, token, tab]);
|
}, [searchParam, pageNumber, token, tab]);
|
||||||
|
|
||||||
|
console.log(pageNumber);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||||
@@ -316,6 +326,11 @@ const TicketsList = () => {
|
|||||||
};
|
};
|
||||||
}, [ticketId, userId, history]);
|
}, [ticketId, userId, history]);
|
||||||
|
|
||||||
|
const loadMore = () => {
|
||||||
|
if (loading) return;
|
||||||
|
setPageNumber(prevPageNumber => prevPageNumber + 1);
|
||||||
|
};
|
||||||
|
|
||||||
const updateTickets = ({ ticket }) => {
|
const updateTickets = ({ ticket }) => {
|
||||||
setTickets(prevState => {
|
setTickets(prevState => {
|
||||||
const ticketIndex = prevState.findIndex(t => t.id === ticket.id);
|
const ticketIndex = prevState.findIndex(t => t.id === ticket.id);
|
||||||
@@ -388,7 +403,6 @@ const TicketsList = () => {
|
|||||||
|
|
||||||
const handleSearchContact = e => {
|
const handleSearchContact = e => {
|
||||||
if (e.target.value === "") {
|
if (e.target.value === "") {
|
||||||
// setTab("open");
|
|
||||||
setSearchParam(e.target.value.toLowerCase());
|
setSearchParam(e.target.value.toLowerCase());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -423,8 +437,7 @@ const TicketsList = () => {
|
|||||||
return ticketsFound;
|
return ticketsFound;
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(tickets);
|
const RenderTickets = ({ status, userId }) => {
|
||||||
const renderTickets = (status, userId) => {
|
|
||||||
const viewTickets = tickets.map(ticket => {
|
const viewTickets = tickets.map(ticket => {
|
||||||
if (
|
if (
|
||||||
(ticket.status === status && ticket.userId === userId) ||
|
(ticket.status === status && ticket.userId === userId) ||
|
||||||
@@ -521,7 +534,7 @@ const TicketsList = () => {
|
|||||||
else return null;
|
else return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading) {
|
if (loading && status !== "all" && status !== "closed") {
|
||||||
return <TicketsSkeleton />;
|
return <TicketsSkeleton />;
|
||||||
} else if (
|
} else if (
|
||||||
countTickets(status, userId) === "" &&
|
countTickets(status, userId) === "" &&
|
||||||
@@ -546,6 +559,22 @@ const TicketsList = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RenderInfiniteScroll = ({ children, loadingKey }) => {
|
||||||
|
return (
|
||||||
|
<InfiniteScroll
|
||||||
|
pageStart={0}
|
||||||
|
loadMore={loadMore}
|
||||||
|
hasMore={!(tickets.length === count)}
|
||||||
|
useWindow={false}
|
||||||
|
initialLoad={false}
|
||||||
|
threshold={100}
|
||||||
|
loader={<TicketsSkeleton key={loadingKey} />}
|
||||||
|
>
|
||||||
|
<List style={{ paddingTop: 0 }}>{children}</List>
|
||||||
|
</InfiniteScroll>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper elevation={0} variant="outlined" className={classes.contactsWrapper}>
|
<Paper elevation={0} variant="outlined" className={classes.contactsWrapper}>
|
||||||
<NewTicketModal
|
<NewTicketModal
|
||||||
@@ -586,55 +615,55 @@ const TicketsList = () => {
|
|||||||
<SearchIcon className={classes.searchIcon} />
|
<SearchIcon className={classes.searchIcon} />
|
||||||
<InputBase
|
<InputBase
|
||||||
className={classes.contactsSearchInput}
|
className={classes.contactsSearchInput}
|
||||||
placeholder="Buscar contatos"
|
placeholder="Pesquisar tickets e mensagens"
|
||||||
type="search"
|
type="search"
|
||||||
onChange={handleSearchContact}
|
onChange={handleSearchContact}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
<TabPanel value={tab} index={"open"} className={classes.contactsWrapper}>
|
<TabPanel value={tab} index={"open"} className={classes.contactsWrapper}>
|
||||||
<Paper square elevation={0} className={classes.openTicketsList}>
|
<Paper square elevation={0} className={classes.halfTicketsList}>
|
||||||
<List style={{ paddingTop: 0 }}>
|
<div className={classes.ticketsListHeader}>
|
||||||
<div className={classes.ticketsListHeader}>
|
Atendendo
|
||||||
Atendendo
|
<span className={classes.ticketsCount}>
|
||||||
<span className={classes.ticketsCount}>
|
{countTickets("open", userId)}
|
||||||
{countTickets("open", userId)}
|
</span>
|
||||||
</span>
|
<div className={classes.ticketsListActions}>
|
||||||
<div className={classes.ticketsListActions}>
|
<FormControlLabel
|
||||||
<FormControlLabel
|
label="Todos"
|
||||||
label="Todos"
|
labelPlacement="start"
|
||||||
labelPlacement="start"
|
control={
|
||||||
control={
|
<Switch
|
||||||
<Switch
|
size="small"
|
||||||
size="small"
|
checked={showAllTickets}
|
||||||
checked={showAllTickets}
|
onChange={e => setShowAllTickets(prevState => !prevState)}
|
||||||
onChange={e => setShowAllTickets(prevState => !prevState)}
|
name="showAllTickets"
|
||||||
name="showAllTickets"
|
color="primary"
|
||||||
color="primary"
|
/>
|
||||||
/>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
<IconButton
|
||||||
<IconButton
|
aria-label="add ticket"
|
||||||
aria-label="add ticket"
|
onClick={e => setNewTicketModalOpen(true)}
|
||||||
onClick={e => setNewTicketModalOpen(true)}
|
style={{ marginLeft: 20 }}
|
||||||
style={{ marginLeft: 20 }}
|
>
|
||||||
>
|
<AddIcon />
|
||||||
<AddIcon />
|
</IconButton>
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{renderTickets("open", userId)}
|
</div>
|
||||||
|
<List style={{ paddingTop: 0 }}>
|
||||||
|
<RenderTickets status="open" userId={userId} />
|
||||||
</List>
|
</List>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Paper square elevation={0} className={classes.openTicketsList}>
|
<Paper square elevation={0} className={classes.halfTicketsList}>
|
||||||
|
<div className={classes.ticketsListHeader}>
|
||||||
|
Aguardando
|
||||||
|
<span className={classes.ticketsCount}>
|
||||||
|
{countTickets("pending", null)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<List style={{ paddingTop: 0 }}>
|
<List style={{ paddingTop: 0 }}>
|
||||||
<div className={classes.ticketsListHeader}>
|
<RenderTickets status="pending" userId={null} />
|
||||||
Aguardando
|
|
||||||
<span className={classes.ticketsCount}>
|
|
||||||
{countTickets("pending", null)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{renderTickets("pending", null)}
|
|
||||||
</List>
|
</List>
|
||||||
</Paper>
|
</Paper>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@@ -643,8 +672,10 @@ const TicketsList = () => {
|
|||||||
index={"closed"}
|
index={"closed"}
|
||||||
className={classes.contactsWrapper}
|
className={classes.contactsWrapper}
|
||||||
>
|
>
|
||||||
<Paper square elevation={0} className={classes.closedTicketsList}>
|
<Paper square elevation={0} className={classes.fullHeightTicketsList}>
|
||||||
<List>{renderTickets("closed", userId)}</List>
|
<RenderInfiniteScroll loadingKey="loading-closed">
|
||||||
|
<RenderTickets status="closed" userId={null} />
|
||||||
|
</RenderInfiniteScroll>
|
||||||
</Paper>
|
</Paper>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel
|
<TabPanel
|
||||||
@@ -652,25 +683,21 @@ const TicketsList = () => {
|
|||||||
index={"search"}
|
index={"search"}
|
||||||
className={classes.contactsWrapper}
|
className={classes.contactsWrapper}
|
||||||
>
|
>
|
||||||
<Paper square elevation={0} className={classes.closedTicketsList}>
|
<Paper square elevation={0} className={classes.fullHeightTicketsList}>
|
||||||
{loading ? (
|
<>
|
||||||
<TicketsSkeleton />
|
{tickets.length === 0 && !loading ? (
|
||||||
) : (
|
<div className={classes.noTicketsDiv}>
|
||||||
<>
|
<span className={classes.noTicketsTitle}>Nada encontrado</span>
|
||||||
{tickets.length === 0 ? (
|
<p className={classes.noTicketsText}>
|
||||||
<div className={classes.noTicketsDiv}>
|
Tente buscar por outro termo.
|
||||||
<span className={classes.noTicketsTitle}>
|
</p>
|
||||||
"Nada encontrado"
|
</div>
|
||||||
</span>
|
) : (
|
||||||
<p className={classes.noTicketsText}>
|
<RenderInfiniteScroll loadingKey="loading-all">
|
||||||
Tente buscar por outro termo.
|
<RenderTickets status="all" />
|
||||||
</p>
|
</RenderInfiniteScroll>
|
||||||
</div>
|
)}
|
||||||
) : (
|
</>
|
||||||
<List>{renderTickets("all")}</List>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<audio id="sound" preload="auto">
|
<audio id="sound" preload="auto">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const TicketsSkeleton = () => {
|
|||||||
secondary={<Skeleton animation="wave" height={20} width={90} />}
|
secondary={<Skeleton animation="wave" height={20} width={90} />}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider />
|
<Divider variant="inset" />
|
||||||
<ListItem dense>
|
<ListItem dense>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Skeleton animation="wave" variant="circle" width={40} height={40} />
|
<Skeleton animation="wave" variant="circle" width={40} height={40} />
|
||||||
@@ -28,7 +28,7 @@ const TicketsSkeleton = () => {
|
|||||||
secondary={<Skeleton animation="wave" height={20} width={120} />}
|
secondary={<Skeleton animation="wave" height={20} width={120} />}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider />
|
<Divider variant="inset" />
|
||||||
<ListItem dense>
|
<ListItem dense>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Skeleton animation="wave" variant="circle" width={40} height={40} />
|
<Skeleton animation="wave" variant="circle" width={40} height={40} />
|
||||||
@@ -38,7 +38,7 @@ const TicketsSkeleton = () => {
|
|||||||
secondary={<Skeleton animation="wave" height={20} width={90} />}
|
secondary={<Skeleton animation="wave" height={20} width={90} />}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider />
|
<Divider variant="inset" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
4
frontend/src/pages/Dashboard/Chart.js
vendored
4
frontend/src/pages/Dashboard/Chart.js
vendored
@@ -36,10 +36,10 @@ const Chart = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const feathTickets = async () => {
|
const feathTickets = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get("/tickets", {
|
const { data } = await api.get("/tickets", {
|
||||||
params: { date: new Date().toISOString() },
|
params: { date: new Date().toISOString() },
|
||||||
});
|
});
|
||||||
let ticketsData = res.data;
|
const ticketsData = data.tickets;
|
||||||
setTickets(ticketsData);
|
setTickets(ticketsData);
|
||||||
setChartData(prevState => {
|
setChartData(prevState => {
|
||||||
let aux = [...prevState];
|
let aux = [...prevState];
|
||||||
|
|||||||
@@ -9012,7 +9012,7 @@ prompts@^2.0.1:
|
|||||||
kleur "^3.0.3"
|
kleur "^3.0.3"
|
||||||
sisteransi "^1.0.4"
|
sisteransi "^1.0.4"
|
||||||
|
|
||||||
prop-types@15.7.2, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
|
prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||||
version "15.7.2"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||||
@@ -9249,6 +9249,13 @@ react-infinite-scroll-reverse@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prop-types "15.7.2"
|
prop-types "15.7.2"
|
||||||
|
|
||||||
|
react-infinite-scroller@^1.2.4:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-infinite-scroller/-/react-infinite-scroller-1.2.4.tgz#f67eaec4940a4ce6417bebdd6e3433bfc38826e9"
|
||||||
|
integrity sha512-/oOa0QhZjXPqaD6sictN2edFMsd3kkMiE19Vcz5JDgHpzEJVqYcmq+V3mkwO88087kvKGe1URNksHEOt839Ubw==
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.5.8"
|
||||||
|
|
||||||
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4:
|
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
|
|||||||
Reference in New Issue
Block a user