diff --git a/backend/.wwebjs_cache/2.3000.1017202422.html b/backend/.wwebjs_cache/2.3000.1017202422.html new file mode 100644 index 000000000..8e645f3fe --- /dev/null +++ b/backend/.wwebjs_cache/2.3000.1017202422.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + +WhatsApp Web + +
WhatsApp
 Protegida com a criptografia de ponta a ponta
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/.wwebjs_cache/2.3000.1017228115.html b/backend/.wwebjs_cache/2.3000.1017228115.html new file mode 100644 index 000000000..556e8c870 --- /dev/null +++ b/backend/.wwebjs_cache/2.3000.1017228115.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + +WhatsApp Web + +
WhatsApp
 Protegida com a criptografia de ponta a ponta
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/.wwebjs_cache/2.3000.1017262717.html b/backend/.wwebjs_cache/2.3000.1017262717.html new file mode 100644 index 000000000..96411ab1f --- /dev/null +++ b/backend/.wwebjs_cache/2.3000.1017262717.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + +WhatsApp Web + +
WhatsApp
 Protegida com a criptografia de ponta a ponta
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 95db251f3..8178ce9e1 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -3,6 +3,7 @@ "target": "es6", "module": "commonjs", "outDir": "./dist", + "rootDir": "./src", "strict": true, "strictPropertyInitialization": false, "esModuleInterop": true, @@ -10,5 +11,7 @@ "emitDecoratorMetadata": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true - } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] } diff --git a/frontend/src/components/MessageInput/index.js b/frontend/src/components/MessageInput/index.js index 6de3d9f1c..dd34fc463 100644 --- a/frontend/src/components/MessageInput/index.js +++ b/frontend/src/components/MessageInput/index.js @@ -54,7 +54,7 @@ const useStyles = makeStyles((theme) => ({ }, newMessageBox: { - background: "#eee", + background: theme.palette.background.default, width: "100%", display: "flex", padding: "7px", @@ -64,7 +64,7 @@ const useStyles = makeStyles((theme) => ({ messageInputWrapper: { padding: 6, marginRight: 7, - background: "#fff", + background: theme.palette.background.paper, display: "flex", borderRadius: 20, flex: 1, diff --git a/frontend/src/components/TicketHeader/index.js b/frontend/src/components/TicketHeader/index.js index 453d52b61..905dba854 100644 --- a/frontend/src/components/TicketHeader/index.js +++ b/frontend/src/components/TicketHeader/index.js @@ -9,7 +9,7 @@ import { useHistory } from "react-router-dom"; const useStyles = makeStyles((theme) => ({ ticketHeader: { display: "flex", - backgroundColor: "#eee", + backgroundColor: theme.palette.background.default, flex: "none", borderBottom: "1px solid rgba(0, 0, 0, 0.12)", [theme.breakpoints.down("sm")]: { diff --git a/frontend/src/components/TicketListItem/index.js b/frontend/src/components/TicketListItem/index.js index a4a0fd1f2..ba4ab0311 100644 --- a/frontend/src/components/TicketListItem/index.js +++ b/frontend/src/components/TicketListItem/index.js @@ -26,6 +26,8 @@ import toastError from "../../errors/toastError"; const useStyles = makeStyles(theme => ({ ticket: { position: "relative", + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.primary, }, pendingTicket: { @@ -43,7 +45,7 @@ const useStyles = makeStyles(theme => ({ noTicketsText: { textAlign: "center", - color: "rgb(104, 121, 146)", + color: theme.palette.text.secondary, fontSize: "14px", lineHeight: "1.4", }, diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/TicketsList/index.js index 37e5de5de..13d111480 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/TicketsList/index.js @@ -21,20 +21,21 @@ const useStyles = makeStyles(theme => ({ overflow: "hidden", borderTopRightRadius: 0, borderBottomRightRadius: 0, + backgroundColor: theme.palette.background.default, }, ticketsList: { flex: 1, overflowY: "scroll", ...theme.scrollbarStyles, - borderTop: "2px solid rgba(0, 0, 0, 0.12)", + borderTop: "2px solid rgba(255, 255, 255, 0.12)", }, ticketsListHeader: { - color: "rgb(67, 83, 105)", + color: theme.palette.text.primary, zIndex: 2, - backgroundColor: "white", - borderBottom: "1px solid rgba(0, 0, 0, 0.12)", + backgroundColor: theme.palette.background.paper, + borderBottom: "1px solid rgba(255, 255, 255, 0.12)", display: "flex", alignItems: "center", justifyContent: "space-between", @@ -42,14 +43,14 @@ const useStyles = makeStyles(theme => ({ ticketsCount: { fontWeight: "normal", - color: "rgb(104, 121, 146)", + color: theme.palette.text.secondary, marginLeft: "8px", fontSize: "14px", }, noTicketsText: { textAlign: "center", - color: "rgb(104, 121, 146)", + color: theme.palette.text.secondary, fontSize: "14px", lineHeight: "1.4", }, diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/TicketsManager/index.js index 68883226a..4fc95d38d 100644 --- a/frontend/src/components/TicketsManager/index.js +++ b/frontend/src/components/TicketsManager/index.js @@ -32,11 +32,12 @@ const useStyles = makeStyles((theme) => ({ overflow: "hidden", borderTopRightRadius: 0, borderBottomRightRadius: 0, + backgroundColor: theme.palette.background.paper, }, tabsHeader: { flex: "none", - backgroundColor: "#eee", + backgroundColor: theme.palette.background.paper, }, settingsIcon: { @@ -54,13 +55,13 @@ const useStyles = makeStyles((theme) => ({ display: "flex", justifyContent: "space-between", alignItems: "center", - background: "#fafafa", + background: theme.palette.background.default, padding: theme.spacing(1), }, serachInputWrapper: { flex: 1, - background: "#fff", + background: theme.palette.background.default, display: "flex", borderRadius: 40, padding: 4, @@ -78,6 +79,7 @@ const useStyles = makeStyles((theme) => ({ flex: 1, border: "none", borderRadius: 30, + color: theme.palette.text.primary, }, badge: { diff --git a/frontend/src/hooks/useTickets/index.js b/frontend/src/hooks/useTickets/index.js index 2518dc248..74a69be51 100644 --- a/frontend/src/hooks/useTickets/index.js +++ b/frontend/src/hooks/useTickets/index.js @@ -1,7 +1,6 @@ import { useState, useEffect } from "react"; import { getHoursCloseTicketsAuto } from "../../config"; import toastError from "../../errors/toastError"; - import api from "../../services/api"; const useTickets = ({ @@ -17,11 +16,16 @@ const useTickets = ({ const [hasMore, setHasMore] = useState(false); const [tickets, setTickets] = useState([]); const [count, setCount] = useState(0); + const [ticketsByUser, setTicketsByUser] = useState({}); + const [ticketsByConnection, setTicketsByConnection] = useState({}); + const [ticketsByQueue, setTicketsByQueue] = useState({}); + const [newContactsByDay, setNewContactsByDay] = useState({}); + const [contactsWithTicketsByDay, setContactsWithTicketsByDay] = useState({}); useEffect(() => { setLoading(true); const delayDebounceFn = setTimeout(() => { - const fetchTickets = async() => { + const fetchTickets = async () => { try { const { data } = await api.get("/tickets", { params: { @@ -33,45 +37,103 @@ const useTickets = ({ queueIds, withUnreadMessages, }, - }) - setTickets(data.tickets) + }); + setTickets(data.tickets); + // Contagem de contatos que criaram tickets por dia + const contactsByDay = data.tickets.reduce((acc, ticket) => { + const contactName = ticket.contact?.name || "Contato desconhecido"; + const createdAtDate = new Date(ticket.createdAt).toLocaleDateString(); + + if (!acc[createdAtDate]) { + acc[createdAtDate] = new Set(); + } + acc[createdAtDate].add(contactName); + + return acc; + }, {}); + + const contactsWithTicketsByDay = Object.entries(contactsByDay).map( + ([date, contacts]) => ({ + date, + count: contacts.size, + }) + ); - let horasFecharAutomaticamente = getHoursCloseTicketsAuto(); + setContactsWithTicketsByDay(contactsWithTicketsByDay); - if (status === "open" && horasFecharAutomaticamente && horasFecharAutomaticamente !== "" && - horasFecharAutomaticamente !== "0" && Number(horasFecharAutomaticamente) > 0) { + // Lógica para calcular os contatos novos por dia + const contactsPerDay = data.tickets.reduce((acc, ticket) => { + const createdDate = new Date(ticket.createdAt).toLocaleDateString(); + acc[createdDate] = acc[createdDate] ? acc[createdDate] + 1 : 1; + return acc; + }, {}); + setNewContactsByDay(contactsPerDay); - let dataLimite = new Date() - dataLimite.setHours(dataLimite.getHours() - Number(horasFecharAutomaticamente)) + // Contagem de tickets por fila + const ticketsCountByQueue = data.tickets.reduce((acc, ticket) => { + const queueName = ticket.queue?.name || "Fila desconhecida"; + acc[queueName] = acc[queueName] ? acc[queueName] + 1 : 1; + return acc; + }, {}); + setTicketsByQueue(ticketsCountByQueue); + + + // Contagem de tickets por conexão + const ticketsCountByConnection = data.tickets.reduce((acc, ticket) => { + const connectionName = ticket.connection?.name || "Conexão desconhecida"; + acc[connectionName] = acc[connectionName] ? acc[connectionName] + 1 : 1; + return acc; + }, {}); + setTicketsByConnection(ticketsCountByConnection); + + // Contagem de tickets por usuário usando o nome + const ticketsCountByUser = data.tickets.reduce((acc, ticket) => { + const userName = ticket.user?.name || "Usuário desconhecido"; + acc[userName] = acc[userName] ? acc[userName] + 1 : 1; + return acc; + }, {}); + setTicketsByUser(ticketsCountByUser); + + // Fechamento automático de tickets + let horasFecharAutomaticamente = getHoursCloseTicketsAuto(); + if ( + status === "open" && + horasFecharAutomaticamente && + horasFecharAutomaticamente !== "" && + 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) + let dataUltimaInteracaoChamado = new Date(ticket.updatedAt); if (dataUltimaInteracaoChamado < dataLimite) - closeTicket(ticket) + closeTicket(ticket); } - }) + }); } - setHasMore(data.hasMore) - setCount(data.count) - setLoading(false) + setHasMore(data.hasMore); + setCount(data.count); + setLoading(false); } catch (err) { - setLoading(false) - toastError(err) + setLoading(false); + toastError(err); } - } + }; - const closeTicket = async(ticket) => { + const closeTicket = async (ticket) => { await api.put(`/tickets/${ticket.id}`, { status: "closed", userId: ticket.userId || null, - }) - } + }); + }; - fetchTickets() - }, 500) - return () => clearTimeout(delayDebounceFn) + fetchTickets(); + }, 500); + return () => clearTimeout(delayDebounceFn); }, [ searchParam, pageNumber, @@ -80,9 +142,19 @@ const useTickets = ({ showAll, queueIds, withUnreadMessages, - ]) + ]); - return { tickets, loading, hasMore, count }; + return { + tickets, + loading, + hasMore, + count, + ticketsByUser, + ticketsByConnection, + ticketsByQueue, + newContactsByDay, + contactsWithTicketsByDay, + }; }; -export default useTickets; \ No newline at end of file +export default useTickets; diff --git a/frontend/src/layout/index.js b/frontend/src/layout/index.js index 589b1331b..1903de46b 100644 --- a/frontend/src/layout/index.js +++ b/frontend/src/layout/index.js @@ -1,6 +1,5 @@ import React, { useState, useContext, useEffect } from "react"; import clsx from "clsx"; - import { makeStyles, Drawer, @@ -12,11 +11,16 @@ import { MenuItem, IconButton, Menu, + CssBaseline, + Switch, + FormControlLabel, } from "@material-ui/core"; +import { WbSunny, NightsStay } from '@material-ui/icons'; import MenuIcon from "@material-ui/icons/Menu"; import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; import AccountCircle from "@material-ui/icons/AccountCircle"; +import { ThemeProvider, createTheme } from "@material-ui/core/styles"; import MainListItems from "./MainListItems"; import NotificationsPopOver from "../components/NotificationsPopOver"; @@ -109,6 +113,30 @@ const useStyles = makeStyles((theme) => ({ }, })); +const lightTheme = createTheme({ + palette: { + type: 'light', + primary: { + main: '#1976d2', + }, + secondary: { + main: '#f44336', + }, + }, +}); + +const darkTheme = createTheme({ + palette: { + type: 'dark', + primary: { + main: '#90caf9', + }, + secondary: { + main: '#f48fb1', + }, + }, +}); + const LoggedInLayout = ({ children }) => { const classes = useStyles(); const [userModalOpen, setUserModalOpen] = useState(false); @@ -118,6 +146,18 @@ const LoggedInLayout = ({ children }) => { const [drawerOpen, setDrawerOpen] = useState(false); const [drawerVariant, setDrawerVariant] = useState("permanent"); const { user } = useContext(AuthContext); + const [darkMode, setDarkMode] = useState(() => { + const savedMode = localStorage.getItem('darkMode'); + return savedMode ? JSON.parse(savedMode) : false; + }); + + useEffect(() => { + localStorage.setItem('darkMode', JSON.stringify(darkMode)); + }, [darkMode]); + + const toggleDarkMode = () => { + setDarkMode(!darkMode); + }; useEffect(() => { if (document.body.offsetWidth > 600) { @@ -164,104 +204,124 @@ const LoggedInLayout = ({ children }) => { } return ( -
- -
- setDrawerOpen(!drawerOpen)}> - - -
- - - - - -
- setUserModalOpen(false)} - userId={user?.id} - /> - - - setDrawerOpen(!drawerOpen)} - className={clsx( - classes.menuButton, - drawerOpen && classes.menuButtonHidden - )} - > - - - - WhaTicket - - {user.id && } - -
+ + +
+ +
+ setDrawerOpen(!drawerOpen)}> + + +
+ + + + + +
+ setUserModalOpen(false)} + userId={user?.id} + /> + + setDrawerOpen(!drawerOpen)} + className={clsx( + classes.menuButton, + drawerOpen && classes.menuButtonHidden + )} > - + - - - {i18n.t("mainDrawer.appBar.user.profile")} - - - {i18n.t("mainDrawer.appBar.user.logout")} - - -
- - -
-
+ WhaTicket + + + + } + label={ + + { darkMode ? : } + + } + labelPlacement="end" + /> + {user.id && } + +
+ + + + + + {i18n.t("mainDrawer.appBar.user.profile")} + + + {i18n.t("mainDrawer.appBar.user.logout")} + + +
+ + +
+
- {children ? children : null} -
-
+ {children ? children : null} +
+
+ ); }; diff --git a/frontend/src/pages/Dashboard/ChartPerConnection.js b/frontend/src/pages/Dashboard/ChartPerConnection.js new file mode 100644 index 000000000..215a59dd0 --- /dev/null +++ b/frontend/src/pages/Dashboard/ChartPerConnection.js @@ -0,0 +1,78 @@ +import React, { useState, useEffect } from "react"; +import { useTheme } from "@material-ui/core/styles"; +import { + BarChart, + CartesianGrid, + Bar, + XAxis, + YAxis, + Label, + Tooltip, + ResponsiveContainer, +} from "recharts"; + +import { i18n } from "../../translate/i18n"; +import useTickets from "../../hooks/useTickets"; +import Title from "./Title"; + +const ChartPerConnection = ({ startDate, endDate }) => { + const theme = useTheme(); + const { ticketsByConnection } = useTickets({ startDate, endDate }); + const [connectionChartData, setConnectionChartData] = useState([]); + + useEffect(() => { + // Verifica se há dados de tickets e formata para o gráfico + if (ticketsByConnection && Object.keys(ticketsByConnection).length > 0) { + const connectionData = Object.entries(ticketsByConnection).map(([connectionName, count]) => ({ + connectionName, + count, + })); + + setConnectionChartData(connectionData); + } else { + setConnectionChartData([]); // Define como vazio caso não haja dados + } + }, [ticketsByConnection]); + + return ( + + {i18n.t("dashboard.chartPerConnection.perConnection.title")} + + + + + + + + + + + + + + ); +}; + +export default ChartPerConnection; diff --git a/frontend/src/pages/Dashboard/ChartPerUser.js b/frontend/src/pages/Dashboard/ChartPerUser.js new file mode 100644 index 000000000..a72412229 --- /dev/null +++ b/frontend/src/pages/Dashboard/ChartPerUser.js @@ -0,0 +1,83 @@ +import React, { useState, useEffect } from "react"; +import { useTheme } from "@material-ui/core/styles"; +import { + BarChart, + CartesianGrid, + Bar, + XAxis, + YAxis, + Label, + Tooltip, + ResponsiveContainer, +} from "recharts"; + +import { i18n } from "../../translate/i18n"; +import useTickets from "../../hooks/useTickets"; +import useAuth from "../../hooks/useAuth.js"; +import Title from "./Title"; + +const ChartPerUser = ({ startDate, endDate }) => { + const theme = useTheme(); + const { ticketsByUser } = useTickets({ startDate, endDate }); + const { user } = useAuth(); + const [userChartData, setUserChartData] = useState([]); + + useEffect(() => { + // Verifica se há dados de tickets e formata para o gráfico + if (ticketsByUser && Object.keys(ticketsByUser).length > 0) { + const userData = Object.entries(ticketsByUser).map(([userId, count]) => { + const userName = userId === user.id ? user.name : "Usuário desconhecido"; + return { + userName, + count, + }; + }); + + setUserChartData(userData); + } else { + setUserChartData([]); // Define como vazio caso não haja dados + } + }, [ticketsByUser, user]); + + return ( + + {i18n.t("dashboard.chartPerUser.perUser.title")} + + + + + + + + + + + + + + ); +}; + +export default ChartPerUser; \ No newline at end of file diff --git a/frontend/src/pages/Dashboard/ChatsPerQueue.js b/frontend/src/pages/Dashboard/ChatsPerQueue.js new file mode 100644 index 000000000..589066227 --- /dev/null +++ b/frontend/src/pages/Dashboard/ChatsPerQueue.js @@ -0,0 +1,76 @@ +import React, { useState, useEffect } from "react"; +import { useTheme } from "@material-ui/core/styles"; +import { + BarChart, + CartesianGrid, + Bar, + XAxis, + YAxis, + Label, + Tooltip, + ResponsiveContainer, +} from "recharts"; + +import { i18n } from "../../translate/i18n"; +import useTickets from "../../hooks/useTickets"; +import Title from "./Title"; + +const ChartPerQueue = ({ startDate, endDate }) => { + const theme = useTheme(); + const { ticketsByQueue } = useTickets({ startDate, endDate }); + const [queueChartData, setQueueChartData] = useState([]); + + useEffect(() => { + if (ticketsByQueue && Object.keys(ticketsByQueue).length > 0) { + const queueData = Object.entries(ticketsByQueue).map(([queueName, count]) => ({ + queueName, + count, + })); + setQueueChartData(queueData); + } else { + setQueueChartData([]); + } + }, [ticketsByQueue]); + + return ( + + {i18n.t("dashboard.chartPerQueue.perQueue.title")} + + + + + + + + + + + + + + ); +}; + +export default ChartPerQueue; diff --git a/frontend/src/pages/Dashboard/ContactsWithTicketsChart.js b/frontend/src/pages/Dashboard/ContactsWithTicketsChart.js new file mode 100644 index 000000000..163cb0ff0 --- /dev/null +++ b/frontend/src/pages/Dashboard/ContactsWithTicketsChart.js @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from "react"; +import { useTheme } from "@material-ui/core/styles"; +import { + BarChart, + CartesianGrid, + Bar, + XAxis, + YAxis, + Label, + Tooltip, + ResponsiveContainer, +} from "recharts"; +import { i18n } from "../../translate/i18n"; +import useTickets from "../../hooks/useTickets"; +import Title from "./Title"; + +const ContactsWithTicketsChart = ({ startDate, endDate }) => { + const theme = useTheme(); + const { contactsWithTicketsByDay } = useTickets({ startDate, endDate }); + const [chartData, setChartData] = useState([]); + + useEffect(() => { + if (contactsWithTicketsByDay) { + setChartData(contactsWithTicketsByDay); + } + }, [contactsWithTicketsByDay]); + + return ( + + {i18n.t("dashboard.contactsWithTickets.title")} + + + + + + + + + + + + + + ); +}; + +export default ContactsWithTicketsChart; diff --git a/frontend/src/pages/Dashboard/NewContactsChart.js b/frontend/src/pages/Dashboard/NewContactsChart.js new file mode 100644 index 000000000..35df43df7 --- /dev/null +++ b/frontend/src/pages/Dashboard/NewContactsChart.js @@ -0,0 +1,67 @@ +import React, { useEffect, useState } from "react"; +import { useTheme } from "@material-ui/core/styles"; +import { + LineChart, + Line, + CartesianGrid, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + Label, +} from "recharts"; +import { i18n } from "../../translate/i18n"; +import useTickets from "../../hooks/useTickets"; +import Title from "./Title"; + +const NewContactsChart = ({ startDate, endDate }) => { + const theme = useTheme(); + const { newContactsByDay } = useTickets({ startDate, endDate }); + const [contactsChartData, setContactsChartData] = useState([]); + + useEffect(() => { + if (newContactsByDay && Object.keys(newContactsByDay).length > 0) { + const formattedData = Object.entries(newContactsByDay).map(([date, count]) => ({ + date, + count, + })); + setContactsChartData(formattedData); + } else { + setContactsChartData([]); + } + }, [newContactsByDay]); + + return ( + + {i18n.t("dashboard.newContacts.title")} + + + + + + + + + + + + + + ); +}; + +export default NewContactsChart; diff --git a/frontend/src/pages/Dashboard/index.js b/frontend/src/pages/Dashboard/index.js index eb11d1907..6ff0e3f3e 100644 --- a/frontend/src/pages/Dashboard/index.js +++ b/frontend/src/pages/Dashboard/index.js @@ -1,117 +1,223 @@ -import React, { useContext } from "react" - -import Paper from "@material-ui/core/Paper" -import Container from "@material-ui/core/Container" -import Grid from "@material-ui/core/Grid" -import { makeStyles } from "@material-ui/core/styles" +import React, { useContext, useState } from "react"; +import Paper from "@material-ui/core/Paper"; +import Container from "@material-ui/core/Container"; +import Grid from "@material-ui/core/Grid"; +import { makeStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; - -import useTickets from "../../hooks/useTickets" - +import Button from "@material-ui/core/Button"; +import useTickets from "../../hooks/useTickets"; import { AuthContext } from "../../context/Auth/AuthContext"; - import { i18n } from "../../translate/i18n"; - -import Chart from "./Chart" +import Chart from "./Chart"; +import { isBefore, parseISO } from "date-fns"; +import { TextField } from "@material-ui/core"; +import ChartPerUser from "./ChartPerUser"; +import ChartPerConnection from "./ChartPerConnection"; +import ChartPerQueue from "./ChatsPerQueue"; +import NewContactsChart from "./NewContactsChart"; +import ContactsWithTicketsChart from "./ContactsWithTicketsChart"; const useStyles = makeStyles(theme => ({ - container: { - paddingTop: theme.spacing(4), - paddingBottom: theme.spacing(4), - }, - fixedHeightPaper: { - padding: theme.spacing(2), - display: "flex", - overflow: "auto", - flexDirection: "column", - height: 240, - }, - customFixedHeightPaper: { - padding: theme.spacing(2), - display: "flex", - overflow: "auto", - flexDirection: "column", - height: 120, - }, - customFixedHeightPaperLg: { - padding: theme.spacing(2), - display: "flex", - overflow: "auto", - flexDirection: "column", - height: "100%", - }, -})) + container: { + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + }, + fixedHeightPaper: { + padding: theme.spacing(2), + display: "flex", + overflow: "auto", + flexDirection: "column", + height: 240, + }, + customFixedHeightPaper: { + padding: theme.spacing(2), + display: "flex", + overflow: "auto", + flexDirection: "column", + height: 120, + }, +})); const Dashboard = () => { - const classes = useStyles() + const classes = useStyles(); + const { user } = useContext(AuthContext); + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + const [selectedStartDate, setSelectedStartDate] = useState(''); + const [selectedEndDate, setSelectedEndDate] = useState(''); + const [error, setError] = useState(false); + const userQueueIds = user.queues?.map(q => q.id) || []; + + const handleStartDateChange = (event) => { + setSelectedStartDate(event.target.value); + }; + + const handleEndDateChange = (event) => { + setSelectedEndDate(event.target.value); + }; + + const validateDates = (start, end) => { + if (start && end && isBefore(parseISO(end), parseISO(start))) { + setError(true); + } else { + setError(false); + } + }; + + const handleFilterClick = () => { + validateDates(selectedStartDate, selectedEndDate); + if (!error) { + setStartDate(selectedStartDate); + setEndDate(selectedEndDate); + } + }; + + const ticketsInAttendance = useTickets({ + status: "open", + showAll: "true", + withUnreadMessages: "false", + queueIds: JSON.stringify(userQueueIds), + startDate, + endDate, + }); + + const ticketsWaiting = useTickets({ + status: "pending", + showAll: "true", + withUnreadMessages: "false", + queueIds: JSON.stringify(userQueueIds), + startDate, + endDate, + }); + + const ticketsClosed = useTickets({ + status: "closed", + showAll: "true", + withUnreadMessages: "false", + queueIds: JSON.stringify(userQueueIds), + startDate, + endDate, + }); - const { user } = useContext(AuthContext); - var userQueueIds = []; + return ( + + + + + + + + + + + + + + {/* Adiciona espaço entre as linhas */} + {error && ( + + A data inicial não pode ser posterior à data final. + + )} + + - if (user.queues && user.queues.length > 0) { - userQueueIds = user.queues.map(q => q.id); - } + + + + + {i18n.t("dashboard.messages.inAttendance.title")} + + + {ticketsInAttendance.count} + + + + + + + {i18n.t("dashboard.messages.waiting.title")} + + + {ticketsWaiting.count} + + + + + + + {i18n.t("dashboard.messages.closed.title")} + + + {ticketsClosed.count} + + + + - const GetTickets = (status, showAll, withUnreadMessages) => { + + + + + + + + + + + + - const { count } = useTickets({ - status: status, - showAll: showAll, - withUnreadMessages: withUnreadMessages, - queueIds: JSON.stringify(userQueueIds) - }); - return count; - } + + + + + + + + + + + + - return ( -
- - - - - - {i18n.t("dashboard.messages.inAttendance.title")} - - - - {GetTickets("open", "true", "false")} - - - - - - - - {i18n.t("dashboard.messages.waiting.title")} - - - - {GetTickets("pending", "true", "false")} - - - - - - - - {i18n.t("dashboard.messages.closed.title")} - - - - {GetTickets("closed", "true", "false")} - - - - - - - - - - - -
- ) -} + + + + + + + + + + + + +
+ ); +}; -export default Dashboard \ No newline at end of file +export default Dashboard; diff --git a/frontend/src/pages/Tickets/index.js b/frontend/src/pages/Tickets/index.js index 80eb90949..2d38c4021 100644 --- a/frontend/src/pages/Tickets/index.js +++ b/frontend/src/pages/Tickets/index.js @@ -46,7 +46,7 @@ const useStyles = makeStyles((theme) => ({ flexDirection: "column", }, welcomeMsg: { - backgroundColor: "#eee", + backgroundColor: theme.palette.background.default, display: "flex", justifyContent: "space-evenly", alignItems: "center", diff --git a/frontend/src/translate/languages/en.js b/frontend/src/translate/languages/en.js index 573e952d9..aa85d870c 100644 --- a/frontend/src/translate/languages/en.js +++ b/frontend/src/translate/languages/en.js @@ -49,7 +49,28 @@ const messages = { closed: { title: "Closed" } - } + }, + chartPerUser: { + perUser: { + title: "Tickets per user" + } + }, + chartPerConnection: { + perConnection: { + title: "Tickets per connection" + } + }, + chartPerQueue: { + perQueue: { + title: "Tickets per queue" + } + }, + newContacts: { + title: "New Contacts per Days" + }, + contactsWithTickets: { + title: "Contacts who created tickets on the day" + }, }, connections: { title: "Connections", diff --git a/frontend/src/translate/languages/es.js b/frontend/src/translate/languages/es.js index 9af0ab4ac..cbde4ad13 100644 --- a/frontend/src/translate/languages/es.js +++ b/frontend/src/translate/languages/es.js @@ -50,7 +50,28 @@ const messages = { closed: { title: "Finalizado" } - } + }, + chartPerUser: { + perUser: { + title: "Entradas por usuario" + } + }, + chartPerConnection: { + perConnection: { + title: "Billetes por conexión" + } + }, + chartPerQueue: { + perQueue: { + title: "Entradas por cola" + } + }, + newContacts: { + title: "Nuevos contactos por día" + }, + contactsWithTickets: { + title: "Contactos que crearon tickets ese día" + }, }, connections: { title: "Conexiones", diff --git a/frontend/src/translate/languages/pt.js b/frontend/src/translate/languages/pt.js index 1c3b025c3..32442d11b 100644 --- a/frontend/src/translate/languages/pt.js +++ b/frontend/src/translate/languages/pt.js @@ -1,3 +1,4 @@ + const messages = { pt: { translations: { @@ -49,7 +50,28 @@ const messages = { closed: { title: "Finalizado" } - } + }, + chartPerUser: { + perUser: { + title: "Tickets por usuário" + } + }, + chartPerConnection: { + perConnection: { + title: "Tickets por conexão" + } + }, + chartPerQueue: { + perQueue: { + title: "Tickets por Fila" + } + }, + newContacts: { + title: "Contatos Novos por Dia" + }, + contactsWithTickets: { + title: "Contatos que criaram tickets no dia" + }, }, connections: { title: "Conexões",