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
+ )}
>
-
+
-
-
-
-
-
-
+ WhaTicket
+
+
+
+ }
+ label={
+
+ { darkMode ? : }
+
+ }
+ labelPlacement="end"
+ />
+ {user.id && }
+
+
+
+
+
+
- {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",